PageRenderTime 16ms CodeModel.GetById 0ms app.highlight 13ms RepoModel.GetById 0ms app.codeStats 0ms

/ftplugin/ATP_files/latextags.py

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