| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- // [fullLine, lineNumber, messageType, message]
- const LINE_SPLITTER_REGEX = /^\[(\d+)].*>\s(INFO|WARN|ERROR)\s-\s(.*)$/
- const MULTILINE_WARNING_REGEX = /^Warning--(.+)\n--line (\d+) of file (.+)$/m
- const SINGLELINE_WARNING_REGEX = /^Warning--(.+)$/m
- const MULTILINE_ERROR_REGEX =
- /^(.*)---line (\d+) of file (.*)\n([^]+?)\nI'm skipping whatever remains of this entry$/m
- const BAD_CROSS_REFERENCE_REGEX =
- /^(A bad cross reference---entry ".+?"\nrefers to entry.+?, which doesn't exist)$/m
- const MULTILINE_COMMAND_ERROR_REGEX =
- /^(.*)\n?---line (\d+) of file (.*)\n([^]+?)\nI'm skipping whatever remains of this command$/m
- // Errors hit in BST file have a slightly different format
- const BST_ERROR_REGEX = /^(.*?)\nwhile executing---line (\d+) of file (.*)/m
- const MESSAGE_LEVELS = {
- INFO: 'info',
- WARN: 'warning',
- ERROR: 'error',
- }
- const parserReducer = function (maxErrors) {
- return function (accumulator, parser) {
- const consume = function (logText, regex, process) {
- let match
- let text = logText
- const result = []
- const re = regex
- let iterationCount = 0
- while ((match = re.exec(text))) {
- iterationCount += 1
- const newEntry = process(match)
- // Too many log entries can cause browser crashes
- // Construct a too many files error from the last match
- if (iterationCount >= maxErrors) {
- const level = newEntry.level + 's'
- newEntry.message = [
- 'Over',
- maxErrors,
- level,
- 'returned. Download raw logs to see full list',
- ].join(' ')
- newEntry.line = undefined
- result.unshift(newEntry)
- return [result, '']
- }
- result.push(newEntry)
- text =
- match.input.slice(0, match.index) +
- match.input.slice(
- match.index + match[0].length + 1,
- match.input.length
- )
- }
- return [result, text]
- }
- const [currentErrors, text] = accumulator
- const [regex, process] = parser
- const [errors, _remainingText] = consume(text, regex, process)
- return [currentErrors.concat(errors), _remainingText]
- }
- }
- export default class BibLogParser {
- constructor(text, options) {
- if (typeof text !== 'string') {
- throw new Error('BibLogParser Error: text parameter must be a string')
- }
- this.text = text.replace(/(\r\n)|\r/g, '\n')
- this.options = options || {}
- this.lines = text.split('\n')
- // each parser is a pair of [regex, processFunction], where processFunction
- // describes how to transform the regex mactch into a log entry object.
- this.warningParsers = [
- [
- MULTILINE_WARNING_REGEX,
- function (match) {
- const [fullMatch, message, lineNumber, fileName] = match
- return {
- file: fileName,
- level: 'warning',
- message,
- line: lineNumber,
- raw: fullMatch,
- }
- },
- ],
- [
- SINGLELINE_WARNING_REGEX,
- function (match) {
- const [fullMatch, message] = match
- return {
- file: '',
- level: 'warning',
- message,
- line: '',
- raw: fullMatch,
- }
- },
- ],
- ]
- this.errorParsers = [
- [
- MULTILINE_ERROR_REGEX,
- function (match) {
- const [fullMatch, firstMessage, lineNumber, fileName, secondMessage] =
- match
- return {
- file: fileName,
- level: 'error',
- message: firstMessage + '\n' + secondMessage,
- line: lineNumber,
- raw: fullMatch,
- }
- },
- ],
- [
- BAD_CROSS_REFERENCE_REGEX,
- function (match) {
- const [fullMatch, message] = match
- return {
- file: '',
- level: 'error',
- message,
- line: '',
- raw: fullMatch,
- }
- },
- ],
- [
- MULTILINE_COMMAND_ERROR_REGEX,
- function (match) {
- const [fullMatch, firstMessage, lineNumber, fileName, secondMessage] =
- match
- return {
- file: fileName,
- level: 'error',
- message: firstMessage + '\n' + secondMessage,
- line: lineNumber,
- raw: fullMatch,
- }
- },
- ],
- [
- BST_ERROR_REGEX,
- function (match) {
- const [fullMatch, firstMessage, lineNumber, fileName] = match
- return {
- file: fileName,
- level: 'error',
- message: firstMessage,
- line: lineNumber,
- raw: fullMatch,
- }
- },
- ],
- ]
- }
- parseBibtex() {
- let allErrors
- const result = {
- all: [],
- errors: [],
- warnings: [],
- files: [], // not used
- typesetting: [], // not used
- }
- // reduce over the parsers, starting with the log text,
- let [allWarnings, remainingText] = this.warningParsers.reduce(
- parserReducer(this.options.maxErrors),
- [[], this.text]
- )
- ;[allErrors, remainingText] = this.errorParsers.reduce(
- parserReducer(this.options.maxErrors),
- [[], remainingText]
- )
- result.warnings = allWarnings
- result.errors = allErrors
- result.all = allWarnings.concat(allErrors)
- return result
- }
- parseBiber() {
- const result = {
- all: [],
- errors: [],
- warnings: [],
- files: [], // not used
- typesetting: [], // not used
- }
- this.lines.forEach(function (line) {
- const match = line.match(LINE_SPLITTER_REGEX)
- if (match) {
- const fullLine = match[0]
- const messageType = match[2]
- const message = match[3]
- const newEntry = {
- file: '',
- level: MESSAGE_LEVELS[messageType] || 'INFO',
- message,
- line: '',
- raw: fullLine,
- }
- // try extract file, line-number and the 'real' message from lines like:
- // BibTeX subsystem: /.../original.bib_123.utf8, line 8, syntax error: it's bad
- const lineMatch = newEntry.message.match(
- /^BibTeX subsystem: \/.+\/(\w+\.\w+)_.+, line (\d+), (.+)$/
- )
- if (lineMatch && lineMatch.length === 4) {
- const fileName = lineMatch[1]
- const lineNumber = lineMatch[2]
- const realMessage = lineMatch[3]
- newEntry.file = fileName
- newEntry.line = lineNumber
- newEntry.message = realMessage
- }
- result.all.push(newEntry)
- switch (newEntry.level) {
- case 'error':
- return result.errors.push(newEntry)
- case 'warning':
- return result.warnings.push(newEntry)
- }
- }
- })
- return result
- }
- parse() {
- const firstLine = this.lines[0]
- if (firstLine.match(/^.*INFO - This is Biber.*$/)) {
- return this.parseBiber()
- } else if (firstLine.match(/^This is BibTeX, Version.+$/)) {
- return this.parseBibtex()
- } else {
- throw new Error(
- 'BibLogParser Error: cannot determine whether text is biber or bibtex output'
- )
- }
- }
- }
|