mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-15 14:43:46 +01:00
Merge pull request #27819 from overleaf/mj-human-readable-logs-typescript
[web] Convert logs parsing to typescript GitOrigin-RevId: 7a338740db50c8a3a0b70dd2212083f17348d4f1
This commit is contained in:
committed by
Copybot
parent
129914a1d9
commit
44f4c835c8
+37
-9
@@ -18,12 +18,21 @@ const MESSAGE_LEVELS = {
|
||||
ERROR: 'error',
|
||||
}
|
||||
|
||||
const parserReducer = function (maxErrors) {
|
||||
return function (accumulator, parser) {
|
||||
const consume = function (logText, regex, process) {
|
||||
type Parser<T> = [RegExp, (match: RegExpExecArray) => T]
|
||||
|
||||
const parserReducer = function (maxErrors: number | null) {
|
||||
return function <T>(
|
||||
accumulator: [T[], string],
|
||||
parser: Parser<T>
|
||||
): [T[], string] {
|
||||
const consume = function (
|
||||
logText: string,
|
||||
regex: RegExp,
|
||||
process: (match: RegExpExecArray) => T
|
||||
): [T[], string] {
|
||||
let match
|
||||
let text = logText
|
||||
const result = []
|
||||
const result: T[] = []
|
||||
let iterationCount = 0
|
||||
|
||||
while ((match = regex.exec(text))) {
|
||||
@@ -55,8 +64,22 @@ const parserReducer = function (maxErrors) {
|
||||
}
|
||||
}
|
||||
|
||||
type BibLogEntry = {
|
||||
file: string
|
||||
level: string
|
||||
message: string
|
||||
line: string
|
||||
raw: string
|
||||
}
|
||||
|
||||
export default class BibLogParser {
|
||||
constructor(text, options = {}) {
|
||||
text: string
|
||||
options: Record<string, any>
|
||||
lines: string[]
|
||||
warningParsers: Parser<BibLogEntry>[]
|
||||
errorParsers: Parser<BibLogEntry>[]
|
||||
|
||||
constructor(text: string, options = {}) {
|
||||
if (typeof text !== 'string') {
|
||||
throw new Error('BibLogParser Error: text parameter must be a string')
|
||||
}
|
||||
@@ -156,11 +179,11 @@ export default class BibLogParser {
|
||||
// reduce over the parsers, starting with the log text,
|
||||
const [allWarnings, remainingText] = this.warningParsers.reduce(
|
||||
parserReducer(this.options.maxErrors),
|
||||
[[], this.text]
|
||||
[[] as BibLogEntry[], this.text]
|
||||
)
|
||||
const [allErrors] = this.errorParsers.reduce(
|
||||
parserReducer(this.options.maxErrors),
|
||||
[[], remainingText]
|
||||
[[] as BibLogEntry[], remainingText]
|
||||
)
|
||||
|
||||
return {
|
||||
@@ -173,7 +196,10 @@ export default class BibLogParser {
|
||||
}
|
||||
|
||||
parseBiber() {
|
||||
const result = {
|
||||
const result: Record<
|
||||
'all' | 'errors' | 'warnings' | 'files' | 'typesetting',
|
||||
BibLogEntry[]
|
||||
> = {
|
||||
all: [],
|
||||
errors: [],
|
||||
warnings: [],
|
||||
@@ -186,7 +212,9 @@ export default class BibLogParser {
|
||||
const [fullLine, , messageType, message] = match
|
||||
const newEntry = {
|
||||
file: '',
|
||||
level: MESSAGE_LEVELS[messageType] || 'INFO',
|
||||
level:
|
||||
MESSAGE_LEVELS[messageType as keyof typeof MESSAGE_LEVELS] ||
|
||||
'INFO',
|
||||
message,
|
||||
line: '',
|
||||
raw: fullLine,
|
||||
+85
-13
@@ -14,8 +14,54 @@ const STATE = {
|
||||
ERROR: 1,
|
||||
}
|
||||
|
||||
export type LatexParserOptions = {
|
||||
fileBaseNames?: RegExp[]
|
||||
ignoreDuplicates?: boolean
|
||||
}
|
||||
|
||||
type LatexLogEntry = {
|
||||
line: string | number | null
|
||||
file: string | undefined
|
||||
level: 'error' | 'warning' | 'typesetting'
|
||||
message: string
|
||||
content?: string
|
||||
raw: string
|
||||
ruleId?: string
|
||||
contentDetails?: string[]
|
||||
command?: string
|
||||
suppressed?: boolean
|
||||
}
|
||||
|
||||
type File = {
|
||||
path: string
|
||||
files: File[]
|
||||
}
|
||||
|
||||
export type ParseResult = {
|
||||
all: LatexLogEntry[]
|
||||
errors: LatexLogEntry[]
|
||||
warnings: LatexLogEntry[]
|
||||
typesetting: LatexLogEntry[]
|
||||
files: File[]
|
||||
}
|
||||
export default class LatexParser {
|
||||
constructor(text, options = {}) {
|
||||
state: number
|
||||
fileBaseNames: RegExp[]
|
||||
ignoreDuplicates?: boolean
|
||||
data: LatexLogEntry[]
|
||||
fileStack: File[]
|
||||
rootFileList: File[]
|
||||
currentFileList: File[]
|
||||
openParens: number
|
||||
latexWarningRegex: RegExp
|
||||
packageWarningRegex: RegExp
|
||||
packageRegex: RegExp
|
||||
log: LogText
|
||||
currentLine: string
|
||||
currentFilePath: string | undefined
|
||||
currentError: LatexLogEntry | undefined
|
||||
|
||||
constructor(text: string, options: LatexParserOptions = {}) {
|
||||
this.state = STATE.NORMAL
|
||||
this.fileBaseNames = options.fileBaseNames || [/compiles/, /\/usr\/local/]
|
||||
this.ignoreDuplicates = options.ignoreDuplicates
|
||||
@@ -26,11 +72,14 @@ export default class LatexParser {
|
||||
this.latexWarningRegex = LATEX_WARNING_REGEX
|
||||
this.packageWarningRegex = PACKAGE_WARNING_REGEX
|
||||
this.packageRegex = PACKAGE_REGEX
|
||||
this.currentLine = ''
|
||||
this.log = new LogText(text)
|
||||
}
|
||||
|
||||
parse() {
|
||||
while ((this.currentLine = this.log.nextLine()) !== false) {
|
||||
parse(): ParseResult {
|
||||
let nextLine: string | false
|
||||
while ((nextLine = this.log.nextLine()) !== false) {
|
||||
this.currentLine = nextLine
|
||||
if (this.state === STATE.NORMAL) {
|
||||
if (this.currentLineIsError()) {
|
||||
this.state = STATE.ERROR
|
||||
@@ -58,6 +107,9 @@ export default class LatexParser {
|
||||
}
|
||||
}
|
||||
if (this.state === STATE.ERROR) {
|
||||
if (!this.currentError) {
|
||||
throw new Error('LatexParser Error: currentError is undefined')
|
||||
}
|
||||
this.currentError.content += this.log
|
||||
.linesUpToNextMatchingLine(/^l\.[0-9]+/)
|
||||
.join('\n')
|
||||
@@ -111,6 +163,9 @@ export default class LatexParser {
|
||||
|
||||
parseFileLineError() {
|
||||
const result = this.currentLine.match(FILE_LINE_ERROR_REGEX)
|
||||
if (!result) {
|
||||
throw new Error('LatexParser Error: Unable to extract error from line.')
|
||||
}
|
||||
this.currentError = {
|
||||
line: result[2],
|
||||
file: result[1],
|
||||
@@ -145,7 +200,7 @@ export default class LatexParser {
|
||||
return this.data.push(this.currentError)
|
||||
}
|
||||
|
||||
parseSingleWarningLine(prefixRegex) {
|
||||
parseSingleWarningLine(prefixRegex: RegExp) {
|
||||
const warningMatch = this.currentLine.match(prefixRegex)
|
||||
if (!warningMatch) {
|
||||
return
|
||||
@@ -173,6 +228,11 @@ export default class LatexParser {
|
||||
let lineMatch = this.currentLine.match(LINES_REGEX)
|
||||
let line = lineMatch ? parseInt(lineMatch[1], 10) : null
|
||||
const packageMatch = this.currentLine.match(this.packageRegex)
|
||||
if (!packageMatch) {
|
||||
throw new Error(
|
||||
'LatexParser Error: Unable to extract package name from warning.'
|
||||
)
|
||||
}
|
||||
const packageName = packageMatch[1]
|
||||
// Regex to get rid of the unnecesary (packagename) prefix in most multi-line warnings
|
||||
const prefixRegex = new RegExp(
|
||||
@@ -180,10 +240,15 @@ export default class LatexParser {
|
||||
'i'
|
||||
)
|
||||
// After every warning message there's a blank line, let's use it
|
||||
while ((this.currentLine = this.log.nextLine())) {
|
||||
let currentLine: string | false
|
||||
while ((currentLine = this.log.nextLine())) {
|
||||
this.currentLine = currentLine
|
||||
lineMatch = this.currentLine.match(LINES_REGEX)
|
||||
line = lineMatch ? parseInt(lineMatch[1], 10) : line
|
||||
warningMatch = this.currentLine.match(prefixRegex)
|
||||
if (!warningMatch) {
|
||||
throw new Error('LatexParser Error: Unable to extract warning message.')
|
||||
}
|
||||
warningLines.push(warningMatch[1])
|
||||
}
|
||||
const rawMessage = warningLines.join(' ')
|
||||
@@ -291,16 +356,19 @@ export default class LatexParser {
|
||||
return path
|
||||
}
|
||||
|
||||
postProcess(data) {
|
||||
const all = []
|
||||
const errorsByLevel = {
|
||||
postProcess(data: LatexLogEntry[]) {
|
||||
const all: LatexLogEntry[] = []
|
||||
const errorsByLevel: Record<
|
||||
'error' | 'warning' | 'typesetting',
|
||||
LatexLogEntry[]
|
||||
> = {
|
||||
error: [],
|
||||
warning: [],
|
||||
typesetting: [],
|
||||
}
|
||||
const hashes = new Set()
|
||||
|
||||
const hashEntry = entry => entry.raw
|
||||
const hashEntry = (entry: LatexLogEntry) => entry.raw
|
||||
|
||||
data.forEach(item => {
|
||||
const hash = hashEntry(item)
|
||||
@@ -326,7 +394,11 @@ export default class LatexParser {
|
||||
}
|
||||
|
||||
class LogText {
|
||||
constructor(text) {
|
||||
text: string
|
||||
lines: string[]
|
||||
row: number
|
||||
|
||||
constructor(text: string) {
|
||||
this.text = text.replace(/(\r\n)|\r/g, '\n')
|
||||
// Join any lines which look like they have wrapped.
|
||||
const wrappedLines = this.text.split('\n')
|
||||
@@ -355,7 +427,7 @@ class LogText {
|
||||
this.row = 0
|
||||
}
|
||||
|
||||
nextLine() {
|
||||
nextLine(): string | false {
|
||||
this.row++
|
||||
if (this.row >= this.lines.length) {
|
||||
return false
|
||||
@@ -368,11 +440,11 @@ class LogText {
|
||||
this.row--
|
||||
}
|
||||
|
||||
linesUpToNextWhitespaceLine(stopAtError) {
|
||||
linesUpToNextWhitespaceLine(stopAtError: boolean = false) {
|
||||
return this.linesUpToNextMatchingLine(/^ *$/, stopAtError)
|
||||
}
|
||||
|
||||
linesUpToNextMatchingLine(match, stopAtError) {
|
||||
linesUpToNextMatchingLine(match: RegExp, stopAtError: boolean = false) {
|
||||
const lines = []
|
||||
|
||||
while (true) {
|
||||
Reference in New Issue
Block a user