PageRenderTime 375ms CodeModel.GetById 33ms RepoModel.GetById 12ms app.codeStats 0ms

/ftplugin/ATP_files/latextags.py

https://github.com/vim-scripts/AutomaticLaTexPlugin
Python | 314 lines | 258 code | 11 blank | 45 comment | 18 complexity | 6b7e88c049019bb79f66c647e921baa5 MD5 | raw file
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. # Author: Marcin Szamotulski
  4. # http://atp-vim.sourceforge.net
  5. #
  6. # Copyright Statement:
  7. # This file is a part of Automatic Tex Plugin for Vim.
  8. #
  9. # Automatic Tex Plugin for Vim is free software: you can redistribute it
  10. # and/or modify it under the terms of the GNU General Public License as
  11. # published by the Free Software Foundation, either version 3 of the
  12. # License, or (at your option) any later version.
  13. #
  14. # Automatic Tex Plugin for Vim is distributed in the hope that it will be
  15. # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  17. # General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License along
  20. # with Automatic Tex Plugin for Vim. If not, see <http://www.gnu.org/licenses/>.
  21. import re, optparse, subprocess, os, traceback, sys
  22. from optparse import OptionParser
  23. from time import strftime, localtime
  24. import locale
  25. encoding = locale.getpreferredencoding()
  26. # Usage:
  27. # latextags.py --files main_file.tex;input_file1.tex;input_file2.tex --auxfile main_file.aux --bibfiles bibfile.bib --dir ./
  28. # --files : specifies all the tex files in a ";"-separated list to parse (all input files) [this option is necessary]
  29. # --auxfile : the aux file [this option is necessary]
  30. # --bibfiles : specifies bibfiles
  31. # --hyperref : if added \hypertarget{}{} commands are scanned too.
  32. # --cite : has values: "natbib"/"biblatex"/"" (or not given). It sets apropriate pattern to find all \cite commands.
  33. # --dir : directory where to put the tag file
  34. # --silent : be silent
  35. # --servername : specifies the server name of vim, if given error messages are send to (g)vim
  36. # --progname : aparentely only two values are supported: "vim"/"gvim".
  37. # ToDoList:
  38. # (1) Use synstack function remotely to get tag_type.
  39. # (2) Scan bib files to get bibkeys (but this might be slow)!
  40. # this could be written to seprate file.
  41. class Dict(dict):
  42. """ 2to3 Python transition. """
  43. def iterkeys(self):
  44. if sys.version_info < (3,0):
  45. return super(type(self), self).iterkeys()
  46. else:
  47. return self.keys()
  48. def iteritems(self):
  49. if sys.version_info < (3,0):
  50. return super(type(self), self).iteritems()
  51. else:
  52. return self.items()
  53. def itervalues(self):
  54. if sys.version_info < (3,0):
  55. return super(type(self), self).itervalues()
  56. else:
  57. return self.values()
  58. # OPTIONS:
  59. usage = "usage: %prog [options]"
  60. parser = OptionParser(usage=usage)
  61. parser.add_option("--files", dest="files" )
  62. parser.add_option("--silent", dest="silent", default=False, action="store_true")
  63. parser.add_option("--auxfile", dest="auxfile" )
  64. parser.add_option("--hyperref", dest="hyperref", default=False, action="store_true")
  65. parser.add_option("--servername", dest="servername", default="" )
  66. parser.add_option("--progname", dest="progname", default="gvim" )
  67. parser.add_option("--bibfiles", dest="bibfiles", default="" )
  68. parser.add_option("--bibtags", dest="bibtags", default=False, action="store_true")
  69. parser.add_option("--bibtags_env", dest="bibtags_env", default=False, action="store_true")
  70. parser.add_option("--dir", dest="directory")
  71. parser.add_option("--cite", dest="cite", default="default" )
  72. (options, args) = parser.parse_args()
  73. file_list=options.files.split(";")
  74. bib_list=options.bibfiles.split(";")
  75. # Cite Pattern:
  76. if options.cite == "natbib":
  77. cite_pattern=re.compile('^(?:[^%]|\\\\%)*\\\\(?:c|C)ite(?:(?:al)?[tp]\*?|year(?:par)?|(?:full)?author\*?|num)?(?:\[.*\])?{([^}]*)}')
  78. elif options.cite == "biblatex":
  79. # there is no pattern for \[aA]utocites, \[tT]extcites, \[sS]martcites,
  80. # \[pP]arencites, \[cC]ites, \footcites, \footcitetexts commands
  81. cite_pattern=re.compile('^(?:[^%]|\\\\%)*\\\\(?:[cC]ite\*?|[pP]arencite\*?|footcite(?:text)?|[tT]extcite|[sS]martcite|supercite|[aA]utocite\*?|[cC]iteauthor|citetitle\*?|cite(?:year|date|url)|nocite|(?:foot)?fullcite|(?:[vV]ol|fvol|ftvol|[sStTpP]vol)cite(?:\[.*\])?{(?:[^}]*)}|[nN]otecite|[pP]nocite|citename|citefield)(?:\[.*\])?{([^}]*)}')
  82. # (?:[aA]utoc|[tT]extc|[sS]martc|[pP]arenc|[cC]|footc)ites(?:(?:\[.*\]{([^}]*)]))*
  83. else:
  84. cite_pattern=re.compile('^(?:[^%]|\\\\%)*\\\\(?:no)?cite(?:\[.*\])?{([^}]*)}')
  85. def vim_remote_expr(servername, expr):
  86. """Send <expr> to vim server,
  87. expr must be well quoted:
  88. vim_remote_expr('GVIM', "atplib#callback#TexReturnCode()")"""
  89. cmd=[options.progname, '--servername', servername, '--remote-expr', expr]
  90. subprocess.Popen(cmd)
  91. def get_tag_type(line, match, label):
  92. """Find tag type,
  93. line is a string, match is an element of a MatchingObject."""
  94. tag_type=""
  95. if label == 'label':
  96. pat='(?:\\\\hypertarget{.*})?\s*\\\\label'
  97. else:
  98. pat='(?:\\\\label{.*})?\s*\\\\hypertarget'
  99. if re.search('\\\\part{.*}\s*%s{%s}' % (pat, match), line):
  100. tag_type="part"
  101. elif re.search('\\\\chapter(?:\[.*\])?{.*}\s*%s{%s}' % (pat, match), line):
  102. tag_type="chapter"
  103. elif re.search('\\\\section(?:\[.*\])?{.*}\s*%s{%s}' % (pat, match), line):
  104. tag_type="section"
  105. elif re.search('\\\\subsection(?:\[.*\])?{.*}\s*%s{%s}' % (pat, match), line):
  106. tag_type="subsection"
  107. elif re.search('\\\\subsubsection(?:\[.*\])?{.*}\s*%s{%s}' % (pat, match), line):
  108. tag_type="subsubsection"
  109. elif re.search('\\\\paragraph(?:\[.*\])?{.*}\s*%s{%s}' % (pat, match), line):
  110. tag_type="paragraph"
  111. elif re.search('\\\\subparagraph(?:\[.*\])?{.*}\s*%s{%s}' % (pat, match), line):
  112. tag_type="subparagraph"
  113. elif re.search('\\\\begin{[^}]*}', line):
  114. # \label command must be in the same line,
  115. # To do: I should add searching in next line too.
  116. # Find that it is inside \begin{equation}:\end{equation}.
  117. type_match=re.search('\\\\begin\s*{\s*([^}]*)\s*}(?:\s*{.*})?\s*(?:\[.*\])?\s*%s{%s}' % (pat, match), line)
  118. try:
  119. # Use the last found match (though it should be just one).
  120. tag_type=type_match.group(len(type_match.groups()))
  121. except AttributeError:
  122. tag_type=""
  123. return tag_type
  124. def find_in_filelist(match, file_dict, get_type=False, type_pattern=None):
  125. """find match in list of files,
  126. file_dict is a dictionary with { 'file_name' : file }."""
  127. r_file = ""
  128. r_type = ""
  129. for file in file_dict.iterkeys():
  130. flinenr=1
  131. for line in file_dict[file]:
  132. pat_match=re.search(match, line)
  133. if pat_match:
  134. r_file=file
  135. if get_type:
  136. r_type=re.match(type_pattern, line).group(1)
  137. break
  138. flinenr+=1
  139. if get_type:
  140. return [ r_file, flinenr, r_type ]
  141. else:
  142. return [ r_file, flinenr ]
  143. def comma_split(arg_list):
  144. ret_list = []
  145. for element in arg_list:
  146. ret_list.extend(element.split(","))
  147. return ret_list
  148. try:
  149. # Read tex files:
  150. file_dict=Dict({})
  151. # { 'file_name' : list_of_lines }
  152. for file in file_list:
  153. try:
  154. if sys.version_info.major < 3:
  155. with open(file, "r") as file_obj:
  156. file_data=file_obj.read().decode(encoding, errors="replace")
  157. else:
  158. with open(file, "r", encoding=enconding, errors="replace") as file_obj:
  159. file_data=file_obj.read()
  160. file_dict[file]=file_data.split("\n")
  161. except IOError:
  162. if options.servername != "":
  163. vim_remote_expr(options.servername, "atplib#callback#Echo(\"[LatexTags:] file %s not found.\",'echomsg','WarningMsg')" % file)
  164. file_dict[file]=[]
  165. pass
  166. # Read bib files:
  167. if len(bib_list) > 1:
  168. bib_dict=Dict({})
  169. # { 'bib_name' : list_of_lines }
  170. for bibfile in bib_list:
  171. if sys.version_info.major < 3 :
  172. with open(bibfile, "r") as file_obj:
  173. bib_data=file_obj.read().decode(encoding, errors="replace")
  174. else:
  175. with open(bibfile, "r", encoding=encoding, errors="replace") as file_obj:
  176. bib_data=file_obj.read()
  177. bib_dict[bibfile]=bib_data.split("\n")
  178. # GENERATE TAGS:
  179. # From \label{} and \hypertarget{}{} commands:
  180. tags=[]
  181. tag_dict=Dict({})
  182. for file_name in file_list:
  183. file_ll=file_dict[file_name]
  184. linenr=0
  185. p_line=""
  186. for line in file_ll:
  187. linenr+=1
  188. # Find LABELS in the current line:
  189. matches=re.findall('^(?:[^%]|\\\\%)*\\\\label{([^}]*)}', line)
  190. for match in matches:
  191. tag="%s\t%s\t%d" % (match, file_name, linenr)
  192. # Set the tag type:
  193. tag_type=get_tag_type(line, match, "label")
  194. if tag_type == "":
  195. tag_type=get_tag_type(p_line+line, match, "label")
  196. tag+=";\"\tinfo:%s\tkind:label" % tag_type
  197. # Add tag:
  198. tags.extend([tag])
  199. tag_dict[str(match)]=[str(linenr), file_name, tag_type, 'label']
  200. # Find HYPERTARGETS in the current line: /this could be switched on/off depending on useage of hyperref/
  201. if options.hyperref:
  202. matches=re.findall('^(?:[^%]|\\\\%)*\\\\hypertarget{([^}]*)}', line)
  203. for match in matches:
  204. # Add only if not yet present in tag list:
  205. if not str(match) in tag_dict:
  206. tag_dict[str(match)]=[str(linenr), file_name, tag_type, 'hyper']
  207. tag_type=get_tag_type(line, match, 'hypertarget')
  208. if tag_type == "":
  209. tag_type=get_tag_type(p_line+line, match, "label")
  210. tags.extend(["%s\t%s\t%d;\"\tinfo:%s\tkind:hyper" % (match,file_name,linenr,tag_type)])
  211. # Find CITATIONS in the current line:
  212. if options.bibtags and not options.bibtags_env:
  213. # There is no support for \citealias comman in natbib.
  214. # complex matches are slower so I should pass an option if one uses natbib.
  215. matches=re.findall(cite_pattern, line)
  216. matches=comma_split(matches)
  217. for match in matches:
  218. if not str(match) in tag_dict:
  219. if len(bib_list) == 1:
  220. tag="%s\t%s\t/%s/;\"\tkind:cite" % (match, bib_list[0], match)
  221. tag_dict[str(match)]=['', bib_list[0], '', 'cite']
  222. tags.extend([tag])
  223. elif len(bib_list) > 1:
  224. bib_file=""
  225. [ bib_file, bib_linenr, bib_type ] = find_in_filelist(re.compile(str(match)), bib_dict, True, re.compile('\s*@(.*){'))
  226. if bib_file != "":
  227. tag="%s\t%s\t%d;\"\tkind:cite\tinfo:%s" % (match, bib_file, bib_linenr, bib_type)
  228. tag_dict[str(match)]=['', bib_file, bib_type, 'cite']
  229. tags.extend([tag])
  230. if options.bibtags and options.bibtags_env:
  231. matches=re.findall(cite_pattern, line)
  232. matches=comma_split(matches)
  233. for match in matches:
  234. if not str(match) in tag_dict:
  235. [ r_file, r_linenr ] = find_in_filelist(re.compile("\\\\bibitem(?:\s*\[.*\])?\s*{"+str(match)+"}"), file_dict)
  236. tag="%s\t%s\t%d;\"\tkind:cite" % (match, r_file, r_linenr)
  237. tag_dict[str(match)]=[str(r_linenr), r_file, '', 'cite']
  238. tags.extend([tag])
  239. p_line=line
  240. # From aux file:
  241. ioerror=False
  242. try:
  243. if sys.version_info.major < 3:
  244. with open(options.auxfile, "r") as file_obj:
  245. aux_data=file_obj.read().decode(encoding, errors="replace").split("\n")
  246. else:
  247. with open(options.auxfile, "r", encoding=encoding, errors="replace") as file_obj:
  248. aux_data=file_obj.readlines()
  249. for line in aux_data:
  250. if re.match('\\\\newlabel{[^}]*}{{[^}]*}', line):
  251. [label, counter]=re.match('\\\\newlabel{([^}]*)}{{([^}]*)}', line).group(1,2)
  252. counter=re.sub('{', '', counter)
  253. if re.search('[0-9.]+', counter):
  254. # Arabic Numbered counter:
  255. counter=re.search('[0-9.]+', counter).group(0)
  256. elif re.search('[ivx]+\)?$', counter):
  257. # Roman numberd counter:
  258. counter=re.search('\(?([ivx]+)\)?$', counter).group(1)
  259. else:
  260. counter=""
  261. try:
  262. [linenr, file, tag_type, kind]=tag_dict[label]
  263. except KeyError:
  264. [linenr, file, tag_type, kind]=["no_label", "no_label", "", ""]
  265. except ValueError:
  266. [linenr, file, tag_type, kind]=["no_label", "no_label", "", ""]
  267. if linenr != "no_label" and counter != "":
  268. tags.extend(["%s\t%s\t%s;\"\tinfo:%s\tkind:%s" % (counter, file, linenr, tag_type, kind)])
  269. except IOError:
  270. ioerror=True
  271. pass
  272. # SORT (vim works faster when tag file is sorted) AND WRITE TAGS
  273. time=strftime("%a, %d %b %Y %H:%M:%S +0000", localtime())
  274. tags = map(lambda u: u.encode(encoding, errors="replace"), tags)
  275. tags_sorted=sorted(tags, key=str.lower)
  276. tags_sorted=['!_TAG_FILE_SORTED\t1\t/'+time]+tags_sorted
  277. os.chdir(options.directory)
  278. with open("tags", 'w') as file_obj:
  279. file_obj.write("\n".join(tags_sorted))
  280. # Communicate to Vim:
  281. if not options.silent:
  282. if options.servername != "":
  283. vim_remote_expr(options.servername, "atplib#callback#Echo(\"[LatexTags:] tags file written.\",'echo','')")
  284. if ioerror and options.servername:
  285. vim_remote_expr(options.servername, "atplib#callback#Echo(\"[LatexTags:] no aux file.\",'echomsg', 'WarningMsg')")
  286. except Exception:
  287. # Send errors to vim is options.servername is non empty.
  288. error_str=re.sub("'", "''",re.sub('"', '\\"', traceback.format_exc()))
  289. if options.servername != "":
  290. vim_remote_expr(options.servername, "atplib#callback#Echo(\"[ATP:] error in latextags.py, catched python exception:\n%s\",'echo','ErrorMsg')" % error_str)
  291. else:
  292. print(error_str)