bib-log-parser.coffee 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. define ->
  2. # [fullLine, lineNumber, messageType, message]
  3. LINE_SPLITTER_REGEX = /^\[(\d+)].*>\s(INFO|WARN|ERROR)\s-\s(.*)$/
  4. MESSAGE_LEVELS = {
  5. "INFO": "info"
  6. "WARN": "warning"
  7. "ERROR": "error"
  8. }
  9. BibLogParser = (text, options) ->
  10. if typeof text != 'string'
  11. throw new Error("BibLogParser Error: text parameter must be a string")
  12. @text = text.replace(/(\r\n)|\r/g, '\n')
  13. @options = options || {}
  14. @lines = text.split('\n')
  15. return
  16. consume = (logText, regex, process) ->
  17. text = logText
  18. result = []
  19. re = regex
  20. iterationCount = 0
  21. while match = re.exec(text)
  22. iterationCount += 1
  23. if iterationCount >= 10000
  24. return result
  25. newEntry = process(match)
  26. result.push newEntry
  27. text = (
  28. (match.input.slice(0, match.index)) +
  29. (match.input.slice(match.index+match[0].length+1, match.input.length))
  30. )
  31. return [result, text]
  32. MULTILINE_WARNING_REGEX = /^Warning--(.+)\n--line (\d+) of file (.+)$/m
  33. SINGLELINE_WARNING_REGEX = /^Warning--(.+)$/m
  34. MULTILINE_ERROR_REGEX = /^(.*)---line (\d+) of file (.*)\n([^]+?)\nI'm skipping whatever remains of this entry$/m
  35. BAD_CROSS_REFERENCE_REGEX = /^(A bad cross reference---entry ".+?"\nrefers to entry.+?, which doesn't exist)$/m
  36. MULTILINE_COMMAND_ERROR_REGEX = /^(.*)\n?---line (\d+) of file (.*)\n([^]+?)\nI'm skipping whatever remains of this command$/m
  37. # each parser is a pair of [regex, processFunction], where processFunction
  38. # describes how to transform the regex mactch into a log entry object.
  39. warningParsers = [
  40. [
  41. MULTILINE_WARNING_REGEX,
  42. (match) ->
  43. [fullMatch, message, lineNumber, fileName] = match
  44. {
  45. file: fileName,
  46. level: "warning",
  47. message: message,
  48. line: lineNumber,
  49. raw: fullMatch
  50. }
  51. ],
  52. [
  53. SINGLELINE_WARNING_REGEX,
  54. (match) ->
  55. [fullMatch, message] = match
  56. {
  57. file: '',
  58. level: "warning",
  59. message: message,
  60. line: '',
  61. raw: fullMatch
  62. }
  63. ]
  64. ]
  65. errorParsers = [
  66. [
  67. MULTILINE_ERROR_REGEX,
  68. (match) ->
  69. [fullMatch, firstMessage, lineNumber, fileName, secondMessage] = match
  70. {
  71. file: fileName,
  72. level: "error",
  73. message: firstMessage + '\n' + secondMessage,
  74. line: lineNumber,
  75. raw: fullMatch
  76. }
  77. ],
  78. [
  79. BAD_CROSS_REFERENCE_REGEX,
  80. (match) ->
  81. [fullMatch, message] = match
  82. {
  83. file: '',
  84. level: "error",
  85. message: message,
  86. line: '',
  87. raw: fullMatch
  88. }
  89. ],
  90. [
  91. MULTILINE_COMMAND_ERROR_REGEX,
  92. (match) ->
  93. [fullMatch, firstMessage, lineNumber, fileName, secondMessage] = match
  94. {
  95. file: fileName,
  96. level: "error",
  97. message: firstMessage + '\n' + secondMessage,
  98. line: lineNumber,
  99. raw: fullMatch
  100. }
  101. ]
  102. ]
  103. (->
  104. @parseBibtex = () ->
  105. result = {
  106. all: [],
  107. errors: [],
  108. warnings: [],
  109. files: [], # not used
  110. typesetting: [] # not used
  111. }
  112. # reduce over the parsers, starting with the log text,
  113. [allWarnings, remainingText] = warningParsers.reduce(
  114. (accumulator, parser) ->
  115. [currentWarnings, text] = accumulator
  116. [regex, process] = parser
  117. [warnings, _remainingText] = consume text, regex, process
  118. return [currentWarnings.concat(warnings), _remainingText]
  119. , [[], @text]
  120. )
  121. [allErrors, remainingText] = errorParsers.reduce(
  122. (accumulator, parser) ->
  123. [currentErrors, text] = accumulator
  124. [regex, process] = parser
  125. [errors, _remainingText] = consume text, regex, process
  126. return [currentErrors.concat(errors), _remainingText]
  127. , [[], remainingText]
  128. )
  129. result.warnings = allWarnings
  130. result.errors = allErrors
  131. result.all = allWarnings.concat(allErrors)
  132. return result
  133. @parseBiber = () ->
  134. result = {
  135. all: [],
  136. errors: [],
  137. warnings: [],
  138. files: [], # not used
  139. typesetting: [] # not used
  140. }
  141. @lines.forEach (line) ->
  142. match = line.match(LINE_SPLITTER_REGEX)
  143. if match
  144. [fullLine, lineNumber, messageType, message] = match
  145. newEntry = {
  146. file: '',
  147. level: MESSAGE_LEVELS[messageType] || "INFO",
  148. message: message,
  149. line: '',
  150. raw: fullLine
  151. }
  152. # try extract file, line-number and the 'real' message from lines like:
  153. # BibTeX subsystem: /.../original.bib_123.utf8, line 8, syntax error: it's bad
  154. lineMatch = newEntry.message.match(/^BibTeX subsystem: \/.+\/(\w+\.\w+)_.+, line (\d+), (.+)$/)
  155. if lineMatch && lineMatch.length == 4
  156. [_, fileName, lineNumber, realMessage] = lineMatch
  157. newEntry.file = fileName
  158. newEntry.line = lineNumber
  159. newEntry.message = realMessage
  160. result.all.push newEntry
  161. switch newEntry.level
  162. when 'error' then result.errors.push newEntry
  163. when 'warning' then result.warnings.push newEntry
  164. return result
  165. @parse = () ->
  166. firstLine = @lines[0]
  167. if firstLine.match(/^.*INFO - This is Biber.*$/)
  168. @parseBiber()
  169. else if firstLine.match(/^This is BibTeX, Version.+$/)
  170. @parseBibtex()
  171. else
  172. throw new Error("BibLogParser Error: cannot determine whether text is biber or bibtex output")
  173. ).call(BibLogParser.prototype)
  174. BibLogParser.parse = (text, options) ->
  175. new BibLogParser(text, options).parse()
  176. return BibLogParser