PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/Support/bin/texparser.py

http://github.com/hesstobi/latex.tmbundle
Python | 397 lines | 385 code | 7 blank | 5 comment | 2 complexity | 50f8aa1fbcbf7d8b8506ee5b63ad6519 MD5 | raw file
  1. import sys
  2. import re
  3. import os.path
  4. import os
  5. import tmprefs
  6. from struct import *
  7. import urllib
  8. def percent_escape(str):
  9. return re.sub('[\x80-\xff /&]', lambda x: '%%%02X' % unpack('B', x.group(0))[0], str)
  10. # Swapped call to percent_escape with urllib.quote. Was causing links to fail in TM2
  11. def make_link(file, line):
  12. return 'txmt://open/?url=file://' + urllib.quote(file) + '&line=' + line
  13. def shell_quote(string):
  14. return '"' + re.sub(r'([`$\\"])', r'\\\1', string) + '"'
  15. class TexParser(object):
  16. """Master Class for Parsing Tex Typsetting Streams"""
  17. def __init__(self, input_stream, verbose):
  18. super(TexParser, self).__init__()
  19. self.input_stream = input_stream
  20. self.patterns = []
  21. self.done = False
  22. self.verbose = verbose
  23. self.numErrs = 0
  24. self.numWarns = 0
  25. self.isFatal = False
  26. self.fileStack = [] #TODO: long term - can improve currentFile handling by keeping track of (xxx and )
  27. def getRewrappedLine(self):
  28. """Sometimes TeX breaks up lines with hard linebreaks. This is annoying.
  29. Even more annoying is that it sometime does not break line, for two distinct
  30. warnings. This function attempts to return a single statement."""
  31. statement = ""
  32. while True:
  33. line = self.input_stream.readline()
  34. if not line:
  35. if statement:
  36. return statement
  37. else:
  38. return ""
  39. statement += line.rstrip("\n")
  40. if len(line) != 80: # including line break
  41. break
  42. return statement+"\n"
  43. def parseStream(self):
  44. """Process the input_stream one line at a time, matching against
  45. each pattern in the patterns dictionary. If a pattern matches
  46. call the corresponding method in the dictionary. The dictionary
  47. is organized with patterns as the keys and methods as the values."""
  48. line = self.getRewrappedLine()
  49. while line and not self.done:
  50. line = line.rstrip("\n")
  51. foundMatch = False
  52. # process matching patterns until we find one
  53. for pat,fun in self.patterns:
  54. myMatch = pat.match(line)
  55. if myMatch:
  56. fun(myMatch,line)
  57. sys.stdout.flush()
  58. foundMatch = True
  59. break
  60. if self.verbose and not foundMatch:
  61. print line
  62. line = self.getRewrappedLine()
  63. if self.done == False:
  64. self.badRun()
  65. return self.isFatal, self.numErrs, self.numWarns
  66. def info(self,m,line):
  67. print '<p class="info">'
  68. print line
  69. print '</p>'
  70. def error(self,m,line):
  71. print '<p class="error">'
  72. print line
  73. print '</p>'
  74. self.numErrs += 1
  75. def warning(self,m,line):
  76. print '<p class="warning">'
  77. print line
  78. print '</p>'
  79. self.numWarns += 1
  80. def warn2(self,m,line):
  81. print '<p class="fmtWarning">'
  82. print line
  83. print '</p>'
  84. def fatal(self,m,line):
  85. print '<p class="error">'
  86. print line
  87. print '</p>'
  88. self.isFatal = True
  89. def badRun(self):
  90. """docstring for finishRun"""
  91. pass
  92. class BibTexParser(TexParser):
  93. """Parse and format Error Messages from bibtex"""
  94. def __init__(self, btex, verbose):
  95. super(BibTexParser, self).__init__(btex,verbose)
  96. self.patterns += [
  97. (re.compile("Warning--I didn't find a database entry") , self.warning),
  98. (re.compile(r'I found no \\\w+ command') , self.error),
  99. (re.compile(r"I couldn't open style file"), self.error),
  100. (re.compile(r"You're missing a field name---line (\d+)"), self.error),
  101. (re.compile(r'Too many commas in name \d+ of'), self.error),
  102. (re.compile(r'I was expecting a'),self.error),
  103. (re.compile('This is BibTeX') , self.info),
  104. (re.compile('The style') , self.info),
  105. (re.compile('Database') , self.info),
  106. (re.compile('---') , self.finishRun)
  107. ]
  108. def finishRun(self,m,line):
  109. self.done = True
  110. print '</div>'
  111. class BiberParser(TexParser):
  112. """Parse and format Error Messages from biber"""
  113. def __init__(self, btex, verbose):
  114. super(BiberParser, self).__init__(btex,verbose)
  115. self.patterns += [
  116. (re.compile('^.*WARN') , self.warning),
  117. (re.compile('^.*ERROR') , self.error),
  118. (re.compile('^.*FATAL'), self.fatal),
  119. (re.compile('^.*Output to (.*)$') , self.finishRun),
  120. ]
  121. def warning(self,m,line):
  122. """Using one print command works more reliably
  123. than using several lines"""
  124. print '<p class="warning">' + line + '</p>'
  125. self.numWarns += 1
  126. def finishRun(self,m,line):
  127. logFile = m.group(1)[:-3] + 'blg'
  128. print '<p> Complete transcript is in '
  129. print '<a href="' + make_link(os.path.join(os.getcwd(),logFile),'1') + '">' + logFile + '</a>'
  130. print '</p>'
  131. self.done = True
  132. print '</div>'
  133. class MakeGlossariesParser(TexParser):
  134. """Parse and format Error Messages from makeglossaries"""
  135. def __init__(self, btex, verbose):
  136. super(MakeGlossariesParser, self).__init__(btex,verbose)
  137. self.patterns += [
  138. (re.compile('^.*makeglossaries version (.*)$') , self.beginRun),
  139. (re.compile('^.*added glossary type \'(.*)\' \((.*)\).*$') , self.addType),
  140. (re.compile('^.*Markup written into file "(.*)".$') , self.finishMarkup),
  141. (re.compile('^.*xindy.*-L (.*) -I.*-t ".*\.(.*)" -o.*$'), self.runXindy),
  142. (re.compile('Cannot locate xindy module') , self.warning),
  143. (re.compile('ERROR'),self.error),
  144. (re.compile('Warning'),self.warning),
  145. (re.compile('^\*\*\*'),self.info),
  146. ]
  147. self.types = dict()
  148. def beginRun(self,m,line):
  149. version = m.group(1)
  150. print "<h2>Make Glossaries</h2>"
  151. print '<p class="info" >Version: <i>'+version+ "</i></p>"
  152. def addType(self,m,line):
  153. thisType = m.group(1)
  154. files = m.group(2)
  155. filesSet = files.split(',')
  156. for file in filesSet:
  157. self.types[file] = thisType
  158. print '<p class="info"> Add Glossary Type <strong>' + thisType +'</strong> <i>(Files: ' + files + ')</i></p>'
  159. def runXindy(self,m,line):
  160. lang = m.group(1)
  161. file = m.group(2)
  162. thisType = self.types[file]
  163. print '<h3>Run xindy for glossary type '+ thisType +'</h3>'
  164. print '<p class="info">Language: '+ lang +'</p>'
  165. def finishMarkup(self,m,line):
  166. mkFile = m.group(1)
  167. thisType = self.types[mkFile[-3:]]
  168. print '<p class="info"> Finished glossary for type <strong>'+ thisType+ '</strong>. Output is in <a href="' + make_link(os.path.join(os.getcwd(),mkFile),'1') + '">' + mkFile + '</a></p>'
  169. def warning(self,m,line):
  170. """Using one print command works more reliably
  171. than using several lines"""
  172. print '<p class="warning">' + line + '</p>'
  173. self.numWarns += 1
  174. def error(self,m,line):
  175. """Using one print command works more reliably
  176. than using several lines"""
  177. print '<p class="error">' + line + '</p>'
  178. self.numWarns += 1
  179. class LaTexParser(TexParser):
  180. """Parse Output From Latex"""
  181. def __init__(self, input_stream, verbose, fileName):
  182. super(LaTexParser, self).__init__(input_stream,verbose)
  183. self.suffix = fileName[fileName.rfind('.')+1:]
  184. self.currentFile = fileName
  185. self.patterns += [
  186. #(re.compile('^This is') , self.info),
  187. (re.compile('^Document Class') , self.info),
  188. (re.compile('.*?\((\.\/[^\)]*?\.(tex|'+self.suffix+')( |$))') , self.detectNewFile),
  189. (re.compile('.*\<use (.*?)\>') , self.detectInclude),
  190. (re.compile('^Output written') , self.info),
  191. (re.compile('LaTeX Warning:.*?input line (\d+)(\.|$)') , self.handleWarning),
  192. (re.compile('LaTeX Warning:.*') , self.warning),
  193. (re.compile('^([^:]*):(\d+):\s+(pdfTeX warning.*)') , self.handleFileLineWarning),
  194. (re.compile('.*pdfTeX warning.*') , self.warning),
  195. (re.compile('LaTeX Font Warning:.*') , self.warning),
  196. (re.compile('Overfull.*wide') , self.warn2),
  197. (re.compile('Underfull.*badness') , self.warn2),
  198. (re.compile('^([\.\/\w\x7f-\xff\- ]+(?:\.sty|\.tex|\.'+self.suffix+')):(\d+):\s+(.*)') , self.handleError),
  199. (re.compile('([^:]*):(\d+): LaTeX Error:(.*)') , self.handleError),
  200. (re.compile('([^:]*):(\d+): (Emergency stop)') , self.handleError),
  201. (re.compile('Runaway argument') , self.pdfLatexError),
  202. # We need the (.*) at the beginning of the regular expression
  203. # since in some edge cases cases the output about the transcript
  204. # might actually not start at the beginning of the line.
  205. (re.compile('(.*)Transcript written on (.*)\.$') , self.finishRun),
  206. (re.compile('^Error: pdflatex') , self.pdfLatexError),
  207. (re.compile('\!.*') , self.handleOldStyleErrors),
  208. (re.compile('^\s+==>') , self.fatal)
  209. ]
  210. self.blankLine = re.compile(r'^\s*$')
  211. def detectNewFile(self,m,line):
  212. self.currentFile = m.group(1).rstrip()
  213. print "<h4>Processing: " + self.currentFile + "</h4>"
  214. def detectInclude(self,m,line):
  215. print "<ul><li>Including: " + m.group(1)
  216. print "</li></ul>"
  217. def handleWarning(self,m,line):
  218. print '<p class="warning"><a href="' + make_link(os.path.join(os.getcwd(),self.currentFile), m.group(1)) + '">'+line+"</a></p>"
  219. self.numWarns += 1
  220. def handleFileLineWarning(self,m,line):
  221. """Display warning. match m should contain file, line, warning message"""
  222. print '<p class="warning"><a href="' + make_link(os.path.join(os.getcwd(), m.group(1)),m.group(2)) + '">' + m.group(3) + "</a></p>"
  223. self.numWarns += 1
  224. def handleError(self,m,line):
  225. print '<p class="error">'
  226. print 'Latex Error: <a href="' + make_link(os.path.join(os.getcwd(),m.group(1)),m.group(2)) + '">' + m.group(1)+":"+m.group(2) + '</a> '+m.group(3)+'</p>'
  227. self.numErrs += 1
  228. def finishRun(self,m,line):
  229. logFile = m.group(1).strip('"')
  230. print '<p> Complete transcript is in '
  231. print '<a href="' + make_link(os.path.join(os.getcwd(),logFile),'1') + '">' + logFile + '</a>'
  232. print '</p>'
  233. self.done = True
  234. def handleOldStyleErrors(self,m,line):
  235. if re.search('[Ee]rror', line):
  236. print '<p class="error">'
  237. print line
  238. print '</p>'
  239. self.numErrs += 1
  240. else:
  241. print '<p class="warning">'
  242. print line
  243. print '</p>'
  244. self.numWarns += 1
  245. def pdfLatexError(self,m,line):
  246. """docstring for pdfLatexError"""
  247. self.numErrs += 1
  248. print '<p class="error">'
  249. print line
  250. line = self.input_stream.readline()
  251. if line and re.match('^ ==> Fatal error occurred', line):
  252. print line.rstrip("\n")
  253. print '</p>'
  254. self.isFatal = True
  255. else:
  256. if line:
  257. print '<pre> '+ line.rstrip("\n") + '</pre>'
  258. print '</p>'
  259. sys.stdout.flush()
  260. def badRun(self):
  261. """docstring for finishRun"""
  262. print '<p class="error">A fatal error occured, log file is in '
  263. logFile = os.path.basename(os.getenv('TM_FILEPATH'))
  264. logFile = logFile.replace(self.suffix,'log')
  265. print '<a href="' + make_link(os.path.join(os.getcwd(),logFile),'1') + '">' + logFile + '</a>'
  266. print '</p>'
  267. class ParseLatexMk(TexParser):
  268. """docstring for ParseLatexMk"""
  269. def __init__(self, input_stream, verbose,filename):
  270. super(ParseLatexMk, self).__init__(input_stream,verbose)
  271. self.fileName = filename
  272. self.patterns += [
  273. (re.compile('This is (pdfTeX|latex2e|latex|XeTeX)') , self.startLatex),
  274. (re.compile('This is BibTeX') , self.startBibtex),
  275. (re.compile('^.*This is biber') , self.startBiber),
  276. (re.compile('^Latexmk: All targets \(.*?\) are up-to-date') , self.finishRun),
  277. (re.compile('This is makeindex') , self.startBibtex),
  278. (re.compile('^Latexmk') , self.ltxmk),
  279. (re.compile('Run number') , self.newRun)
  280. ]
  281. self.numRuns = 0
  282. def startBibtex(self,m,line):
  283. print '<div class="bibtex">'
  284. print '<h3>' + line[:-1] + '</h3>'
  285. bp = BibTexParser(self.input_stream,self.verbose)
  286. f,e,w = bp.parseStream()
  287. self.numErrs += e
  288. self.numWarns += w
  289. def startBiber(self,m,line):
  290. print '<div class="biber">'
  291. print '<h3>' + line + '</h3>'
  292. bp = BiberParser(self.input_stream,self.verbose)
  293. f,e,w = bp.parseStream()
  294. self.numErrs += e
  295. self.numWarns += w
  296. def startLatex(self,m,line):
  297. print '<div class="latex">'
  298. print '<hr>'
  299. print '<h3>' + line[:-1] + '</h3>'
  300. bp = LaTexParser(self.input_stream,self.verbose,self.fileName)
  301. f,e,w = bp.parseStream()
  302. self.numErrs += e
  303. self.numWarns += w
  304. def newRun(self,m,line):
  305. if self.numRuns > 0:
  306. print '<hr />'
  307. print '<p>', self.numErrs, 'Errors', self.numWarns, 'Warnings', 'in this run.', '</p>'
  308. self.numWarns = 0
  309. self.numErrs = 0
  310. self.numRuns += 1
  311. def finishRun(self,m,line):
  312. self.ltxmk(m,line)
  313. self.done = True
  314. def ltxmk(self,m,line):
  315. print '<p class="ltxmk">%s</p>'%line
  316. class ChkTeXParser(TexParser):
  317. """Parse the output from chktex"""
  318. def __init__(self, input_stream, verbose, filename):
  319. super(ChkTeXParser, self).__init__(input_stream,verbose)
  320. self.fileName = filename
  321. self.patterns += [
  322. (re.compile('^ChkTeX') , self.info),
  323. (re.compile('Warning \d+ in (.*.tex) line (\d+):(.*)') , self.handleWarning),
  324. (re.compile('Error \d+ in (.*.tex) line (\d+):(.*)') , self.handleError),
  325. ]
  326. self.numRuns = 0
  327. def handleWarning(self,m,line):
  328. """Display warning. match m should contain file, line, warning message"""
  329. print '<p class="warning">Warning: <a href="' + make_link(os.path.join(os.getcwd(), m.group(1)),m.group(2)) + '">' + m.group(1)+ ": "+m.group(2)+":</a>"+m.group(3)+"</p>"
  330. warnDetail = self.input_stream.readline()
  331. if len(warnDetail) > 2:
  332. print '<pre>',warnDetail[:-1]
  333. print self.input_stream.readline()[:-1], '</pre>'
  334. self.numWarns += 1
  335. def handleError(self,m,line):
  336. print '<p class="error">'
  337. print 'Error: <a href="' + make_link(os.path.join(os.getcwd(),m.group(1)),m.group(2)) + '">' + m.group(1)+":"+m.group(2) + ':</a> '+m.group(3)+'</p>'
  338. print '<pre>', self.input_stream.readline()[:-1]
  339. print self.input_stream.readline()[:-1], '</pre>'
  340. self.numErrs += 1
  341. if __name__ == '__main__':
  342. # test
  343. stream = open('../tex/test.log')
  344. lp = LaTexParser(stream,False,"test.tex")
  345. lp = BiberParser(stream, False)
  346. f,e,w = lp.parseStream()