1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
// LaTeX compile log parser (extracted from src/mcp/lattex.mjs)
export interface LogEntry {
level: 'error' | 'warning'
message: string
file?: string
line?: number
}
export function parseCompileLog(raw: string): LogEntry[] {
const entries: LogEntry[] = []
const lines = raw.split('\n')
for (let i = 0; i < lines.length; i++) {
const ln = lines[i]
if (/^!/.test(ln) || /LaTeX Error:/.test(ln)) {
let msg = ln.replace(/^!\s*/, '')
while (i + 1 < lines.length && lines[i + 1] && !lines[i + 1].startsWith('l.') && !lines[i + 1].startsWith('!')) {
i++
if (lines[i].trim()) msg += ' ' + lines[i].trim()
}
let lineNum: number | undefined
if (i + 1 < lines.length && /^l\.(\d+)/.test(lines[i + 1])) {
i++
lineNum = parseInt(lines[i].match(/^l\.(\d+)/)![1])
}
entries.push({ level: 'error', message: msg.trim(), line: lineNum })
continue
}
const fileLineErr = ln.match(/^\.\/(.+?):(\d+):\s*(.+)/)
if (fileLineErr) {
const msg = fileLineErr[3]
const isWarning = /warning/i.test(msg)
entries.push({
level: isWarning ? 'warning' : 'error',
message: msg,
file: fileLineErr[1],
line: parseInt(fileLineErr[2])
})
continue
}
const pkgWarn = ln.match(/Package (\S+) Warning:\s*(.*)/)
if (pkgWarn) {
let msg = `[${pkgWarn[1]}] ${pkgWarn[2]}`
let warnLine: number | undefined
while (i + 1 < lines.length && /^\(/.test(lines[i + 1])) {
i++
msg += ' ' + lines[i].replace(/^\([^)]*\)\s*/, '').trim()
const lineMatch = lines[i].match(/on input line (\d+)/)
if (lineMatch) warnLine = parseInt(lineMatch[1])
}
if (!warnLine) {
const lineMatch = msg.match(/on input line (\d+)/)
if (lineMatch) warnLine = parseInt(lineMatch[1])
}
entries.push({ level: 'warning', message: msg.trim(), line: warnLine })
continue
}
const latexWarn = ln.match(/LaTeX Warning:\s*(.*)/)
if (latexWarn) {
let msg = latexWarn[1]
while (i + 1 < lines.length && lines[i + 1] && !lines[i + 1].match(/^[(!.]/) && lines[i + 1].startsWith(' ')) {
i++
msg += ' ' + lines[i].trim()
}
const lineMatch = msg.match(/on input line (\d+)/)
entries.push({ level: 'warning', message: msg.trim(), line: lineMatch ? parseInt(lineMatch[1]) : undefined })
continue
}
if (/^(Overfull|Underfull)/.test(ln)) {
const paraMatch = ln.match(/at lines (\d+)--(\d+)/) || ln.match(/in paragraph at lines (\d+)--(\d+)/)
entries.push({ level: 'warning', message: ln.trim(), line: paraMatch ? parseInt(paraMatch[1]) : undefined })
continue
}
if (/File .* not found/.test(ln)) {
entries.push({ level: 'error', message: ln.trim() })
}
}
// Deduplicate
const seen = new Set<string>()
return entries.filter((e) => {
const key = `${e.level}:${e.message}`
if (seen.has(key)) return false
seen.add(key)
return true
})
}
export function formatEntry(e: LogEntry): string {
const loc = [e.file, e.line].filter(Boolean).join(':')
return `[${e.level.toUpperCase()}]${loc ? ` ${loc}:` : ''} ${e.message}`
}
|