ProjectRootDocManager.coffee 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. ProjectEntityHandler = require "./ProjectEntityHandler"
  2. ProjectEntityUpdateHandler = require "./ProjectEntityUpdateHandler"
  3. ProjectGetter = require "./ProjectGetter"
  4. DocumentHelper = require "../Documents/DocumentHelper"
  5. Path = require "path"
  6. fs = require("fs")
  7. async = require("async")
  8. globby = require("globby")
  9. _ = require("underscore")
  10. module.exports = ProjectRootDocManager =
  11. setRootDocAutomatically: (project_id, callback = (error) ->) ->
  12. ProjectEntityHandler.getAllDocs project_id, (error, docs) ->
  13. return callback(error) if error?
  14. jobs = _.map docs, (doc, path)->
  15. return (cb)->
  16. if /\.R?tex$/.test(Path.extname(path)) && DocumentHelper.contentHasDocumentclass(doc.lines)
  17. cb(doc._id)
  18. else
  19. cb(null)
  20. async.series jobs, (root_doc_id)->
  21. if root_doc_id?
  22. ProjectEntityUpdateHandler.setRootDoc project_id, root_doc_id, callback
  23. else
  24. callback()
  25. findRootDocFileFromDirectory: (directoryPath, callback = (error, path, content) ->) ->
  26. filePathsPromise = globby([
  27. '**/*.{tex,Rtex}'
  28. ], {
  29. cwd: directoryPath,
  30. followSymlinkedDirectories: false,
  31. onlyFiles: true,
  32. case: false
  33. }
  34. )
  35. # the search order is such that we prefer files closer to the project root, then
  36. # we go by file size in ascending order, because people often have a main
  37. # file that just includes a bunch of other files; then we go by name, in
  38. # order to be deterministic
  39. filePathsPromise.then(
  40. (unsortedFiles) ->
  41. ProjectRootDocManager._sortFileList unsortedFiles, directoryPath, (err, files) ->
  42. return callback(err) if err?
  43. doc = null
  44. async.until(
  45. ->
  46. return doc? || files.length == 0
  47. (cb) ->
  48. file = files.shift()
  49. fs.readFile Path.join(directoryPath, file), 'utf8', (error, content) ->
  50. return cb(error) if error?
  51. content = (content || '').replace(/\r/g, '')
  52. if DocumentHelper.contentHasDocumentclass(content)
  53. doc = {path: file, content: content}
  54. cb(null)
  55. (err) ->
  56. callback(err, doc?.path, doc?.content)
  57. )
  58. (err) ->
  59. callback(err)
  60. )
  61. # coffeescript's implicit-return mechanism returns filePathsPromise from this method, which confuses mocha
  62. return null
  63. setRootDocFromName: (project_id, rootDocName, callback = (error) ->) ->
  64. ProjectEntityHandler.getAllDocPathsFromProjectById project_id, (error, docPaths) ->
  65. return callback(error) if error?
  66. # strip off leading and trailing quotes from rootDocName
  67. rootDocName = rootDocName.replace(/^\'|\'$/g,"")
  68. # prepend a slash for the root folder if not present
  69. rootDocName = "/#{rootDocName}" if rootDocName[0] isnt '/'
  70. # find the root doc from the filename
  71. root_doc_id = null
  72. for doc_id, path of docPaths
  73. # docpaths have a leading / so allow matching "folder/filename" and "/folder/filename"
  74. if path == rootDocName
  75. root_doc_id = doc_id
  76. # try a basename match if there was no match
  77. if !root_doc_id
  78. for doc_id, path of docPaths
  79. if Path.basename(path) == Path.basename(rootDocName)
  80. root_doc_id = doc_id
  81. # set the root doc id if we found a match
  82. if root_doc_id?
  83. ProjectEntityUpdateHandler.setRootDoc project_id, root_doc_id, callback
  84. else
  85. callback()
  86. ensureRootDocumentIsSet: (project_id, callback = (error) ->) ->
  87. ProjectGetter.getProject project_id, rootDoc_id: 1, (error, project) ->
  88. return callback(error) if error?
  89. if !project?
  90. return callback new Error("project not found")
  91. if project.rootDoc_id?
  92. callback()
  93. else
  94. ProjectRootDocManager.setRootDocAutomatically project_id, callback
  95. ensureRootDocumentIsValid: (project_id, callback = (error) ->) ->
  96. ProjectGetter.getProject project_id, rootDoc_id: 1, (error, project) ->
  97. return callback(error) if error?
  98. if !project?
  99. return callback new Error("project not found")
  100. if project.rootDoc_id?
  101. ProjectEntityHandler.getAllDocPathsFromProjectById project_id, (error, docPaths) ->
  102. return callback(error) if error?
  103. rootDocValid = false
  104. for doc_id, _path of docPaths
  105. if doc_id == project.rootDoc_id
  106. rootDocValid = true
  107. if rootDocValid
  108. callback()
  109. else
  110. ProjectEntityUpdateHandler.setRootDoc project_id, null, ->
  111. ProjectRootDocManager.setRootDocAutomatically project_id, callback
  112. else
  113. ProjectRootDocManager.setRootDocAutomatically project_id, callback
  114. _sortFileList: (listToSort, rootDirectory, callback = (error, result)->) ->
  115. async.mapLimit(
  116. listToSort
  117. 5
  118. (filePath, cb) ->
  119. fs.stat Path.join(rootDirectory, filePath), (err, stat) ->
  120. return cb(err) if err?
  121. cb(null,
  122. size: stat.size
  123. path: filePath
  124. elements: filePath.split(Path.sep).length
  125. name: Path.basename(filePath)
  126. )
  127. (err, files) ->
  128. return callback(err) if err?
  129. callback(null, _.map files.sort(ProjectRootDocManager._rootDocSort), (file)-> return file.path)
  130. )
  131. _rootDocSort: (a, b) ->
  132. # sort first by folder depth
  133. return a.elements - b.elements if a.elements != b.elements
  134. # ensure main.tex is at the start of each folder
  135. return -1 if (a.name == 'main.tex' && b.name != 'main.tex')
  136. return 1 if (a.name != 'main.tex' && b.name == 'main.tex')
  137. # prefer smaller files
  138. return a.size - b.size if a.size != b.size
  139. # otherwise, use the full path name
  140. return a.path.localeCompare(b.path)