PageRenderTime 171ms CodeModel.GetById 35ms RepoModel.GetById 1ms app.codeStats 0ms

/ftplugin/ATP_files/latex_log.py

https://github.com/vim-scripts/AutomaticLaTexPlugin
Python | 528 lines | 461 code | 14 blank | 53 comment | 12 complexity | 27a7d8d41586205bed520132eb1959e1 MD5 | raw file
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. # Usage: latex_log.py {tex_log_file}
  4. # Produces a "._log" file.
  5. # Author: Marcin Szamotulski
  6. # http://atp-vim.sourceforge.net
  7. #
  8. # Copyright Statement:
  9. # This file is a part of Automatic Tex Plugin for Vim.
  10. #
  11. # Automatic Tex Plugin for Vim is free software: you can redistribute it
  12. # and/or modify it under the terms of the GNU General Public License as
  13. # published by the Free Software Foundation, either version 3 of the
  14. # License, or (at your option) any later version.
  15. #
  16. # Automatic Tex Plugin for Vim is distributed in the hope that it will be
  17. # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  19. # General Public License for more details.
  20. #
  21. # You should have received a copy of the GNU General Public License along
  22. # with Automatic Tex Plugin for Vim. If not, see <http://www.gnu.org/licenses/>.
  23. # INFO:
  24. # This is a python script which reads latex log file (which path is gven as
  25. # the only argument) and it writes back a log messages which are in the
  26. # following format:
  27. # WARNING_TYPE::FILE::INPUT_LINE::INPUT_COL::MESSAGE (ADDITIONAL_INFO)
  28. # this was intendent to be used for vim quick fix:
  29. # set errorformat=LaTeX\ %tarning::%f::%l::%c::%m,Citation\ %tarning::%f::%l::%c::%m,Reference\ %tarning::%f::%l::%c::%m,Package\ %tarning::%f::%l::%c::%m,hbox\ %tarning::%f::%l::%c::%m,LaTeX\ %tnfo::%f::%l::%c::%m,LaTeX\ %trror::%f::%l::%c::%m
  30. #
  31. # The fowllowing WARNING_TYPEs are available:
  32. # LaTeX Warning
  33. # Citation Warning
  34. # Reference Warning
  35. # Package Warning
  36. # hbox Warning : Overfull and Underfull hbox warnings
  37. # LaTeX Font Warning
  38. # LaTeX Font Info
  39. # LaTeX Info
  40. # LaTeX Error
  41. # Input File
  42. # Input Package : includes packges and document class
  43. # Note: when FILE,INPUT_LINE,INPUT_COL doesn't exists 0 is put.
  44. # It will work well when the tex file was compiled with a big value of
  45. # max_print_line (for example with `max_print_line=2000 latex file.tex')
  46. # so that latex messages are not broken into lines.
  47. # The scripts assumes the default encoding to be utf-8. Though you will not see
  48. # errors since decode(errors='replace') is used, that is bytes not recognized
  49. # will be substituted with '?'.
  50. import sys, re, os, os.path, fnmatch
  51. from optparse import OptionParser
  52. __all__ = [ 'rewrite_log' ]
  53. class Dict(dict):
  54. """ 2to3 Python transition. """
  55. def iterkeys(self):
  56. if sys.version_info < (3,0):
  57. return super(type(self), self).iterkeys()
  58. else:
  59. return self.keys()
  60. def iteritems(self):
  61. if sys.version_info < (3,0):
  62. return super(type(self), self).iteritems()
  63. else:
  64. return self.items()
  65. def itervalues(self):
  66. if sys.version_info < (3,0):
  67. return super(type(self), self).itervalues()
  68. else:
  69. return self.values()
  70. def shift_dict( dictionary, nr ):
  71. '''
  72. Add nr to every value of dictionary.
  73. '''
  74. for key in dictionary.iterkeys():
  75. dictionary[key]+=nr
  76. return dictionary
  77. if sys.platform.startswith('linux'):
  78. log_to_path = "/tmp/latex_log.log"
  79. else:
  80. log_to_path = None
  81. def rewrite_log(input_fname, output_fname=None, check_path=False, project_dir="", project_tmpdir="", encoding="utf8"):
  82. # this function rewrites LaTeX log file (input_fname) to output_fname,
  83. # changeing its format to something readable by Vim.
  84. # check_path -- ATP process files in a temporary directory, with this
  85. # option the files under project_tmpdir will be written using project_dir
  86. # (this is for the aux file).
  87. if output_fname is None:
  88. output_fname = os.path.splitext(input_fname)[0]+"._log"
  89. try:
  90. if sys.version_info < (3, 0):
  91. log_file = open(input_fname, 'r')
  92. else:
  93. # We are assuming the default encoding (utf-8)
  94. log_file = open(input_fname, 'r', errors='replace')
  95. except IOError:
  96. print("IOError: cannot open %s file for reading" % input_fname)
  97. sys.exit(1)
  98. else:
  99. log_stream = log_file.read().decode(encoding, 'ignore')
  100. log_file.close()
  101. # Todo: In python3 there is UnicodeDecodeError. I should remove all the
  102. # bytes where python cannot decode the character.
  103. dir = os.path.dirname(os.path.abspath(input_fname))
  104. os.chdir(dir)
  105. # Filter the log_stream: remove all unbalanced brackets (:)
  106. # some times the log file contains unbalanced brackets!
  107. # This removes all the lines after 'Overfull \hbox' message until first non
  108. # empty line and all lines just after 'Runaway argument?'.
  109. log_lines = log_stream.split("\n")
  110. output_lines = []
  111. idx = 0
  112. remove = False
  113. prev_line = ""
  114. overfull = False
  115. runawayarg = False
  116. for line in log_lines:
  117. idx+=1
  118. match_overfull = re.match(r'(Over|Under)full \\hbox ',line)
  119. match_runawayarg = re.match('Runaway argument\?',prev_line)
  120. if match_overfull or match_runawayarg:
  121. if match_overfull:
  122. overfull = True
  123. if match_runawayarg:
  124. runawayarg = True
  125. remove = True
  126. elif re.match('^\s*$', line) and overfull:
  127. remove = False
  128. overfull = False
  129. elif runawayarg:
  130. remove = False
  131. runawayarg = False
  132. if not remove or match_overfull:
  133. output_lines.append(line)
  134. prev_line = line
  135. log_stream='\n'.join(output_lines)
  136. del output_lines
  137. output_data = []
  138. log_lines = log_stream.split("\n")
  139. global log_to_path
  140. if log_to_path:
  141. try:
  142. log_fo=open(log_to_path, 'w')
  143. except IOError:
  144. print("IOError: cannot open %s file for writting" % log_to_path)
  145. else:
  146. log_fo.write(log_stream.encode(encoding, 'ignore'))
  147. log_fo.close()
  148. # File stack
  149. file_stack = []
  150. line_nr = 1
  151. col_nr = 1
  152. # Message Patterns:
  153. latex_warning_pat = re.compile('(LaTeX Warning: )')
  154. latex_warning= "LaTeX Warning"
  155. font_warning_pat = re.compile('LaTeX Font Warning: ')
  156. font_warning = "LaTeX Font Warning"
  157. font_info_pat = re.compile('LaTeX Font Info: ')
  158. font_info = "LaTeX Font Info"
  159. package_warning_pat = re.compile('Package (\w+) Warning: ')
  160. package_warning = "Package Warning"
  161. package_info_pat = re.compile('Package (\w+) Info: ')
  162. package_info = "Package Info"
  163. hbox_info_pat = re.compile('(Over|Under)full \\\\hbox ')
  164. hbox_info = "hbox Warning"
  165. latex_info_pat = re.compile('LaTeX Info: ')
  166. latex_info = "LaTeX Info"
  167. latex_emergency_stop_pat = re.compile('\! Emergency stop\.')
  168. latex_emergency_stop = "LaTeX Error"
  169. latex_error_pat = re.compile('\! (?:LaTeX Error: |Package (\w+) Error: )?')
  170. latex_error = "LaTeX Error"
  171. input_package_pat = re.compile('(?:Package: |Document Class: )')
  172. input_package = 'Input Package'
  173. open_dict = Dict({})
  174. # This dictionary is of the form:
  175. # { file_name : number_of_brackets_opened_after_the_file_name_was_found ... }
  176. idx=-1
  177. line_up_to_col = ""
  178. # This variable stores the current line up to the current column.
  179. for char in log_stream:
  180. idx+=1
  181. if char == "\n":
  182. line_nr+=1
  183. col_nr=0
  184. line_up_to_col = ""
  185. else:
  186. col_nr+=1
  187. line_up_to_col += char
  188. if char == "(" and not re.match('l\.\d+', line_up_to_col):
  189. # If we are at the '(' bracket, check for the file name just after it.
  190. line = log_lines[line_nr-1][col_nr:]
  191. fname_re = re.match('([^\(\)]*\.(?:tex|sty|cls|cfg|def|aux|fd|out|bbl|blg|bcf|lof|toc|lot|ind|idx|thm|synctex\.gz|pdfsync|clo|lbx|mkii|run\.xml|spl|snm|nav|brf|mpx|ilg|maf|glo|mtc[0-9]+))', line)
  192. if fname_re:
  193. fname = os.path.abspath(fname_re.group(1))
  194. if check_path and fnmatch.fnmatch(fname, project_tmpdir+"*"):
  195. # ATP specific path rewritting:
  196. fname = os.path.normpath(os.path.join(project_dir, os.path.relpath(fname, project_tmpdir)))
  197. output_data.append(["Input File", fname, "0", "0", "Input File"])
  198. file_stack.append(fname)
  199. open_dict[fname]=0
  200. open_dict = shift_dict(open_dict, 1)
  201. elif char == ")" and not re.match('l\.\d+', line_up_to_col):
  202. if len(file_stack) and not( re.match('\!', log_lines[line_nr-1]) or re.match('\s{5,}',log_lines[line_nr-1]) or re.match('l\.\d+', log_lines[line_nr-1])):
  203. # If the ')' is in line that we check, then substrackt 1 from values of
  204. # open_dict and pop both the open_dict and the file_stack.
  205. open_dict = shift_dict(open_dict, -1)
  206. if open_dict[file_stack[-1]] == 0:
  207. open_dict.pop(file_stack[-1])
  208. file_stack.pop()
  209. line = log_lines[line_nr-1][col_nr:]
  210. if col_nr == 0:
  211. # Check for the error message in the current line
  212. # (that's why we only check it when col_nr == 0)
  213. try:
  214. last_file = file_stack[-1]
  215. except IndexError:
  216. last_file = "0"
  217. if check_path and fnmatch.fnmatch(last_file, project_tmpdir+"*"):
  218. # ATP specific path rewritting:
  219. last_file = os.path.normpath(os.path.join(project_dir, os.path.relpath(last_file, project_tmpdir)))
  220. if re.match(latex_warning_pat, line):
  221. # Log Message: 'LaTeX Warning: '
  222. input_line = re.search('on input line (\d+)', line)
  223. warning_type = re.match('LaTeX Warning: (Citation|Reference)', line)
  224. if warning_type:
  225. wtype = warning_type.group(1)
  226. else:
  227. wtype = ""
  228. msg = re.sub('\s+on input line (\d+)', '', re.sub(latex_warning_pat,'', line))
  229. if msg == "":
  230. msg = " "
  231. if input_line:
  232. output_data.append([wtype+" "+latex_warning, last_file, input_line.group(1), "0", msg])
  233. else:
  234. output_data.append([latex_warning, last_file, "0", "0", msg])
  235. elif re.match(font_warning_pat, line):
  236. # Log Message: 'LaTeX Font Warning: '
  237. input_line = re.search('on input line (\d+)', line)
  238. if not input_line and line_nr < len(log_lines) and re.match('\(Font\)', log_lines[line_nr]):
  239. input_line = re.search('on input line (\d+)', line)
  240. if not input_line and line_nr+1 < len(log_lines) and re.match('\(Font\)', log_lines[line_nr+1]):
  241. input_line = re.search('on input line (\d+)', log_lines[line_nr+1])
  242. msg = re.sub(' on input line \d+', '', re.sub(font_warning_pat,'', line))
  243. if msg == "":
  244. msg = " "
  245. i=0
  246. while line_nr+i < len(log_lines) and re.match('\(Font\)', log_lines[line_nr+i]):
  247. msg += re.sub(' on input line \d+', '', re.sub('\(Font\)\s*', ' ', log_lines[line_nr]))
  248. i+=1
  249. if not re.search("\.\s*$", msg):
  250. msg = re.sub("\s*$", ".", msg)
  251. if input_line:
  252. output_data.append([font_warning, last_file, input_line.group(1), "0", msg])
  253. else:
  254. output_data.append([font_warning, last_file, "0", "0", msg])
  255. elif re.match(font_info_pat, line):
  256. # Log Message: 'LaTeX Font Info: '
  257. input_line = re.search('on input line (\d+)', line)
  258. if not input_line and line_nr < len(log_lines) and re.match('\(Font\)', log_lines[line_nr]):
  259. input_line = re.search('on input line (\d+)', log_lines[line_nr])
  260. if not input_line and line_nr+1 < len(log_lines) and re.match('\(Font\)', log_lines[line_nr+1]):
  261. input_line = re.search('on input line (\d+)', log_lines[line_nr+1])
  262. msg = re.sub(' on input line \d+', '', re.sub(font_info_pat,'', line))
  263. if msg == "":
  264. msg = " "
  265. i=0
  266. while line_nr+i < len(log_lines) and re.match('\(Font\)', log_lines[line_nr+i]):
  267. msg += re.sub(' on input line \d+', '', re.sub('\(Font\)\s*', ' ', log_lines[line_nr]))
  268. i+=1
  269. if not re.search("\.\s*$", msg):
  270. msg = re.sub("\s*$", ".", msg)
  271. if input_line:
  272. output_data.append([font_info, last_file, input_line.group(1), "0", msg])
  273. else:
  274. output_data.append([font_info, last_file, "0", "0", msg])
  275. elif re.match(package_warning_pat, line):
  276. # Log Message: 'Package (\w+) Warning: '
  277. package = re.match(package_warning_pat, line).group(1)
  278. input_line = re.search('on input line (\d+)', line)
  279. msg = re.sub(package_warning_pat,'', line)
  280. if line_nr < len(log_lines):
  281. nline = log_lines[line_nr]
  282. i=0
  283. while re.match('\('+package+'\)',nline):
  284. msg+=re.sub('\('+package+'\)\s*', ' ', nline)
  285. if not input_line:
  286. input_line = re.search('on input line (\d+)', nline)
  287. i+=1
  288. if line_nr+i < len(log_lines):
  289. nline = log_lines[line_nr+i]
  290. else:
  291. break
  292. if msg == "":
  293. msg = " "
  294. msg = re.sub(' on input line \d+', '', msg)
  295. if input_line:
  296. output_data.append([package_warning, last_file, input_line.group(1), "0", "%s (%s package)" % (msg, package)])
  297. else:
  298. output_data.append([package_warning, last_file, "0", "0", "%s (%s package)" % (msg, package)])
  299. elif re.match(package_info_pat, line):
  300. # Log Message: 'Package (\w+) Info: '
  301. package = re.match(package_info_pat, line).group(1)
  302. input_line = re.search('on input line (\d+)', line)
  303. msg = re.sub(package_info_pat,'', line)
  304. if line_nr < len(log_lines):
  305. nline = log_lines[line_nr]
  306. i=0
  307. while re.match(('\(%s\)' % package), nline):
  308. msg+=re.sub(('\(%s\)\s*' % package), ' ', nline)
  309. if not input_line:
  310. input_line = re.search('on input line (\d+)', nline)
  311. i+=1
  312. if line_nr+i < len(log_lines):
  313. nline = log_lines[line_nr+i]
  314. else:
  315. break
  316. if msg == "":
  317. msg = " "
  318. msg = re.sub(' on input line \d+', '', msg)
  319. if input_line:
  320. output_data.append([package_info, last_file, input_line.group(1), "0", msg+" ("+package+")"])
  321. else:
  322. output_data.append([package_info, last_file, "0", "0", msg+" ("+package+")"])
  323. elif re.match(hbox_info_pat, line):
  324. # Log Message: '(Over|Under)full \\\\hbox'
  325. input_line = re.search('at lines? (\d+)(?:--(?:\d+))?', line)
  326. if re.match('Underfull', line):
  327. h_type = 'Underfull '
  328. else:
  329. h_type = 'Overfull '
  330. msg = h_type+'\\hbox '+str(re.sub(hbox_info_pat, '', line))
  331. if msg == "":
  332. msg = " "
  333. if input_line:
  334. output_data.append([hbox_info, last_file, input_line.group(1), "0", msg])
  335. else:
  336. output_data.append([hbox_info, last_file, "0", "0", msg])
  337. elif re.match(latex_info_pat, line):
  338. # Log Message: 'LaTeX Info: '
  339. input_line = re.search('on input line (\d+)', line)
  340. msg = re.sub(' on input line \d+', '', re.sub(latex_info_pat,'', line))
  341. if msg == "":
  342. msg = " "
  343. if input_line:
  344. output_data.append([latex_info, last_file, input_line.group(1), "0", msg])
  345. else:
  346. output_data.append([latex_info, last_file, "0", "0", msg])
  347. elif re.match(input_package_pat, line):
  348. # Log Message: 'Package: ', 'Document Class: '
  349. msg = re.sub(input_package_pat, '', line)
  350. if msg == "":
  351. msg = " "
  352. output_data.append([input_package, last_file, "0", "0", msg])
  353. elif re.match(latex_emergency_stop_pat, line):
  354. # Log Message: '! Emergency stop.'
  355. msg = "Emergency stop."
  356. nline = log_lines[line_nr]
  357. match = re.match('<\*>\s+(.*)', nline)
  358. if match:
  359. e_file = match.group(1)
  360. else:
  361. e_file = "0"
  362. i=-1
  363. while True:
  364. i+=1
  365. try:
  366. nline = log_lines[line_nr-1+i]
  367. line_m = re.match('\*\*\*\s+(.*)', nline)
  368. if line_m:
  369. rest = line_m.group(1)
  370. break
  371. elif i>50:
  372. rest = ""
  373. break
  374. except IndexError:
  375. rest = ""
  376. break
  377. msg += " "+rest
  378. output_data.append([latex_emergency_stop, e_file, "0", "0",msg])
  379. elif re.match(latex_error_pat, line):
  380. # Log Message: '\! (?:LaTeX Error: |Package (\w+) Error: )?'
  381. # get the line unmber of the error
  382. match = re.search('on input line (\d+)', line)
  383. input_line = (match and [match.group(1)] or [None])[0]
  384. i=-1
  385. while True:
  386. i+=1
  387. try:
  388. nline = log_lines[line_nr-1+i]
  389. line_m = re.match('l\.(\d+) (.*)', nline)
  390. if line_m:
  391. if input_line is None:
  392. input_line = line_m.group(1)
  393. rest = line_m.group(2)+re.sub('^\s*', ' ', log_lines[line_nr+i])
  394. break
  395. elif i>50:
  396. if input_line is None:
  397. input_line="0"
  398. rest = ""
  399. break
  400. except IndexError:
  401. if input_line is None:
  402. input_line="0"
  403. rest = ""
  404. break
  405. msg = re.sub(latex_error_pat, '', line)
  406. if msg == "":
  407. msg = " "
  408. p_match = re.match('! Package (\w+) Error', line)
  409. if p_match:
  410. info = p_match.group(1)
  411. elif rest:
  412. info = rest
  413. else:
  414. info = ""
  415. if re.match('\s*\\\\]\s*', info) or re.match('\s*$', info):
  416. info = ""
  417. if info != "":
  418. info = " |"+info
  419. if re.match('!\s+A <box> was supposed to be here\.', line) or \
  420. re.match('!\s+Infinite glue shrinkage found in a paragraph', line) or \
  421. re.match('!\s+Missing \$ inserted\.', line):
  422. info = ""
  423. verbose_msg = ""
  424. for j in range(1,i):
  425. if not re.match("See\s+the\s+\w+\s+manual\s+or\s+\w+\s+Companion\s+for\s+explanation\.|Type\s+[HI]", log_lines[line_nr-1+j]):
  426. verbose_msg+=re.sub("^\s*", " ", log_lines[line_nr-1+j])
  427. else:
  428. break
  429. if re.match('\s*<(?:inserted text|to be read again|recently read)>', verbose_msg) or \
  430. re.match('\s*See the LaTeX manual', verbose_msg) or \
  431. re.match('!\s+Infinite glue shrinkage found in a paragraph', line):
  432. verbose_msg = ""
  433. if not re.match('\s*$',verbose_msg):
  434. verbose_msg = " |"+verbose_msg
  435. if last_file == "0":
  436. i=-1
  437. while True:
  438. i+=1
  439. try:
  440. nline = log_lines[line_nr-1+i]
  441. line_m = re.match('<\*>\s+(.*)', nline)
  442. if line_m:
  443. e_file = line_m.group(1)
  444. break
  445. elif i>50:
  446. e_file="0"
  447. break
  448. except IndexError:
  449. e_file="0"
  450. break
  451. else:
  452. e_file = last_file
  453. if not match:
  454. index = len(output_data)
  455. else:
  456. # Find the correct place to put the error message:
  457. try:
  458. try:
  459. prev_element=filter(lambda d: d[1] == e_file and int(d[2]) <= int(input_line), output_data)[-1]
  460. index = output_data.index(prev_element)+1
  461. except IndexError:
  462. prev_element=filter(lambda d: d[1] == e_file and int(d[2]) > int(input_line), output_data)[0]
  463. index = output_data.index(prev_element)
  464. except IndexError:
  465. index = len(output_data)
  466. output_data.insert(index, [latex_error, e_file, input_line, "0", msg+info+verbose_msg])
  467. output_data=map(lambda x: "::".join(x), output_data)
  468. try:
  469. output_fo=open(output_fname, 'w')
  470. except IOError:
  471. print("IOError: cannot open %s file for writting" % output_fname)
  472. sys.exit(1)
  473. else:
  474. output_fo.write(('\n'.join(output_data)+'\n').encode(encoding, 'ignore'))
  475. output_fo.close()
  476. # Main call
  477. if __name__ == '__main__':
  478. usage = "%prog [options] {log_file}"
  479. parser = OptionParser(usage=usage)
  480. import locale
  481. encoding = locale.getpreferredencoding()
  482. parser.add_option("-e", "--encoding", dest="encoding", default=encoding, help="encoding to use (default=%s)" % encoding)
  483. (options, args) = parser.parse_args()
  484. try:
  485. rewrite_log(args[0], encoding=options.encoding)
  486. except IOError:
  487. pass