| Index: scons-2.0.1/engine/SCons/Scanner/LaTeX.py
|
| ===================================================================
|
| --- scons-2.0.1/engine/SCons/Scanner/LaTeX.py (revision 0)
|
| +++ scons-2.0.1/engine/SCons/Scanner/LaTeX.py (revision 0)
|
| @@ -0,0 +1,384 @@
|
| +"""SCons.Scanner.LaTeX
|
| +
|
| +This module implements the dependency scanner for LaTeX code.
|
| +
|
| +"""
|
| +
|
| +#
|
| +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 The SCons Foundation
|
| +#
|
| +# Permission is hereby granted, free of charge, to any person obtaining
|
| +# a copy of this software and associated documentation files (the
|
| +# "Software"), to deal in the Software without restriction, including
|
| +# without limitation the rights to use, copy, modify, merge, publish,
|
| +# distribute, sublicense, and/or sell copies of the Software, and to
|
| +# permit persons to whom the Software is furnished to do so, subject to
|
| +# the following conditions:
|
| +#
|
| +# The above copyright notice and this permission notice shall be included
|
| +# in all copies or substantial portions of the Software.
|
| +#
|
| +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
| +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
| +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| +#
|
| +
|
| +__revision__ = "src/engine/SCons/Scanner/LaTeX.py 5134 2010/08/16 23:02:40 bdeegan"
|
| +
|
| +import os.path
|
| +import re
|
| +
|
| +import SCons.Scanner
|
| +import SCons.Util
|
| +
|
| +# list of graphics file extensions for TeX and LaTeX
|
| +TexGraphics = ['.eps', '.ps']
|
| +LatexGraphics = ['.pdf', '.png', '.jpg', '.gif', '.tif']
|
| +
|
| +# Used as a return value of modify_env_var if the variable is not set.
|
| +class _Null(object):
|
| + pass
|
| +_null = _Null
|
| +
|
| +# The user specifies the paths in env[variable], similar to other builders.
|
| +# They may be relative and must be converted to absolute, as expected
|
| +# by LaTeX and Co. The environment may already have some paths in
|
| +# env['ENV'][var]. These paths are honored, but the env[var] paths have
|
| +# higher precedence. All changes are un-done on exit.
|
| +def modify_env_var(env, var, abspath):
|
| + try:
|
| + save = env['ENV'][var]
|
| + except KeyError:
|
| + save = _null
|
| + env.PrependENVPath(var, abspath)
|
| + try:
|
| + if SCons.Util.is_List(env[var]):
|
| + env.PrependENVPath(var, [os.path.abspath(str(p)) for p in env[var]])
|
| + else:
|
| + # Split at os.pathsep to convert into absolute path
|
| + env.PrependENVPath(var, [os.path.abspath(p) for p in str(env[var]).split(os.pathsep)])
|
| + except KeyError:
|
| + pass
|
| +
|
| + # Convert into a string explicitly to append ":" (without which it won't search system
|
| + # paths as well). The problem is that env.AppendENVPath(var, ":")
|
| + # does not work, refuses to append ":" (os.pathsep).
|
| +
|
| + if SCons.Util.is_List(env['ENV'][var]):
|
| + env['ENV'][var] = os.pathsep.join(env['ENV'][var])
|
| + # Append the trailing os.pathsep character here to catch the case with no env[var]
|
| + env['ENV'][var] = env['ENV'][var] + os.pathsep
|
| +
|
| + return save
|
| +
|
| +class FindENVPathDirs(object):
|
| + """A class to bind a specific *PATH variable name to a function that
|
| + will return all of the *path directories."""
|
| + def __init__(self, variable):
|
| + self.variable = variable
|
| + def __call__(self, env, dir=None, target=None, source=None, argument=None):
|
| + import SCons.PathList
|
| + try:
|
| + path = env['ENV'][self.variable]
|
| + except KeyError:
|
| + return ()
|
| +
|
| + dir = dir or env.fs._cwd
|
| + path = SCons.PathList.PathList(path).subst_path(env, target, source)
|
| + return tuple(dir.Rfindalldirs(path))
|
| +
|
| +
|
| +
|
| +def LaTeXScanner():
|
| + """Return a prototype Scanner instance for scanning LaTeX source files
|
| + when built with latex.
|
| + """
|
| + ds = LaTeX(name = "LaTeXScanner",
|
| + suffixes = '$LATEXSUFFIXES',
|
| + # in the search order, see below in LaTeX class docstring
|
| + graphics_extensions = TexGraphics,
|
| + recursive = 0)
|
| + return ds
|
| +
|
| +def PDFLaTeXScanner():
|
| + """Return a prototype Scanner instance for scanning LaTeX source files
|
| + when built with pdflatex.
|
| + """
|
| + ds = LaTeX(name = "PDFLaTeXScanner",
|
| + suffixes = '$LATEXSUFFIXES',
|
| + # in the search order, see below in LaTeX class docstring
|
| + graphics_extensions = LatexGraphics,
|
| + recursive = 0)
|
| + return ds
|
| +
|
| +class LaTeX(SCons.Scanner.Base):
|
| + """Class for scanning LaTeX files for included files.
|
| +
|
| + Unlike most scanners, which use regular expressions that just
|
| + return the included file name, this returns a tuple consisting
|
| + of the keyword for the inclusion ("include", "includegraphics",
|
| + "input", or "bibliography"), and then the file name itself.
|
| + Based on a quick look at LaTeX documentation, it seems that we
|
| + should append .tex suffix for the "include" keywords, append .tex if
|
| + there is no extension for the "input" keyword, and need to add .bib
|
| + for the "bibliography" keyword that does not accept extensions by itself.
|
| +
|
| + Finally, if there is no extension for an "includegraphics" keyword
|
| + latex will append .ps or .eps to find the file, while pdftex may use .pdf,
|
| + .jpg, .tif, .mps, or .png.
|
| +
|
| + The actual subset and search order may be altered by
|
| + DeclareGraphicsExtensions command. This complication is ignored.
|
| + The default order corresponds to experimentation with teTeX
|
| + $ latex --version
|
| + pdfeTeX 3.141592-1.21a-2.2 (Web2C 7.5.4)
|
| + kpathsea version 3.5.4
|
| + The order is:
|
| + ['.eps', '.ps'] for latex
|
| + ['.png', '.pdf', '.jpg', '.tif'].
|
| +
|
| + Another difference is that the search path is determined by the type
|
| + of the file being searched:
|
| + env['TEXINPUTS'] for "input" and "include" keywords
|
| + env['TEXINPUTS'] for "includegraphics" keyword
|
| + env['TEXINPUTS'] for "lstinputlisting" keyword
|
| + env['BIBINPUTS'] for "bibliography" keyword
|
| + env['BSTINPUTS'] for "bibliographystyle" keyword
|
| +
|
| + FIXME: also look for the class or style in document[class|style]{}
|
| + FIXME: also look for the argument of bibliographystyle{}
|
| + """
|
| + keyword_paths = {'include': 'TEXINPUTS',
|
| + 'input': 'TEXINPUTS',
|
| + 'includegraphics': 'TEXINPUTS',
|
| + 'bibliography': 'BIBINPUTS',
|
| + 'bibliographystyle': 'BSTINPUTS',
|
| + 'usepackage': 'TEXINPUTS',
|
| + 'lstinputlisting': 'TEXINPUTS'}
|
| + env_variables = SCons.Util.unique(list(keyword_paths.values()))
|
| +
|
| + def __init__(self, name, suffixes, graphics_extensions, *args, **kw):
|
| +
|
| + # We have to include \n with the % we exclude from the first part
|
| + # part of the regex because the expression is compiled with re.M.
|
| + # Without the \n, the ^ could match the beginning of a *previous*
|
| + # line followed by one or more newline characters (i.e. blank
|
| + # lines), interfering with a match on the next line.
|
| + # add option for whitespace before the '[options]' or the '{filename}'
|
| + regex = r'^[^%\n]*\\(include|includegraphics(?:\s*\[[^\]]+\])?|lstinputlisting(?:\[[^\]]+\])?|input|bibliography|usepackage)\s*{([^}]*)}'
|
| + self.cre = re.compile(regex, re.M)
|
| + self.comment_re = re.compile(r'^((?:(?:\\%)|[^%\n])*)(.*)$', re.M)
|
| +
|
| + self.graphics_extensions = graphics_extensions
|
| +
|
| + def _scan(node, env, path=(), self=self):
|
| + node = node.rfile()
|
| + if not node.exists():
|
| + return []
|
| + return self.scan_recurse(node, path)
|
| +
|
| + class FindMultiPathDirs(object):
|
| + """The stock FindPathDirs function has the wrong granularity:
|
| + it is called once per target, while we need the path that depends
|
| + on what kind of included files is being searched. This wrapper
|
| + hides multiple instances of FindPathDirs, one per the LaTeX path
|
| + variable in the environment. When invoked, the function calculates
|
| + and returns all the required paths as a dictionary (converted into
|
| + a tuple to become hashable). Then the scan function converts it
|
| + back and uses a dictionary of tuples rather than a single tuple
|
| + of paths.
|
| + """
|
| + def __init__(self, dictionary):
|
| + self.dictionary = {}
|
| + for k,n in dictionary.items():
|
| + self.dictionary[k] = ( SCons.Scanner.FindPathDirs(n),
|
| + FindENVPathDirs(n) )
|
| +
|
| + def __call__(self, env, dir=None, target=None, source=None,
|
| + argument=None):
|
| + di = {}
|
| + for k,(c,cENV) in self.dictionary.items():
|
| + di[k] = ( c(env, dir=None, target=None, source=None,
|
| + argument=None) ,
|
| + cENV(env, dir=None, target=None, source=None,
|
| + argument=None) )
|
| + # To prevent "dict is not hashable error"
|
| + return tuple(di.items())
|
| +
|
| + class LaTeXScanCheck(object):
|
| + """Skip all but LaTeX source files, i.e., do not scan *.eps,
|
| + *.pdf, *.jpg, etc.
|
| + """
|
| + def __init__(self, suffixes):
|
| + self.suffixes = suffixes
|
| + def __call__(self, node, env):
|
| + current = not node.has_builder() or node.is_up_to_date()
|
| + scannable = node.get_suffix() in env.subst_list(self.suffixes)[0]
|
| + # Returning false means that the file is not scanned.
|
| + return scannable and current
|
| +
|
| + kw['function'] = _scan
|
| + kw['path_function'] = FindMultiPathDirs(LaTeX.keyword_paths)
|
| + kw['recursive'] = 0
|
| + kw['skeys'] = suffixes
|
| + kw['scan_check'] = LaTeXScanCheck(suffixes)
|
| + kw['name'] = name
|
| +
|
| + SCons.Scanner.Base.__init__(self, *args, **kw)
|
| +
|
| + def _latex_names(self, include):
|
| + filename = include[1]
|
| + if include[0] == 'input':
|
| + base, ext = os.path.splitext( filename )
|
| + if ext == "":
|
| + return [filename + '.tex']
|
| + if (include[0] == 'include'):
|
| + return [filename + '.tex']
|
| + if include[0] == 'bibliography':
|
| + base, ext = os.path.splitext( filename )
|
| + if ext == "":
|
| + return [filename + '.bib']
|
| + if include[0] == 'usepackage':
|
| + base, ext = os.path.splitext( filename )
|
| + if ext == "":
|
| + return [filename + '.sty']
|
| + if include[0] == 'includegraphics':
|
| + base, ext = os.path.splitext( filename )
|
| + if ext == "":
|
| + #return [filename+e for e in self.graphics_extensions + TexGraphics]
|
| + # use the line above to find dependencies for the PDF builder
|
| + # when only an .eps figure is present. Since it will be found
|
| + # if the user tells scons how to make the pdf figure, leave
|
| + # it out for now.
|
| + return [filename+e for e in self.graphics_extensions]
|
| + return [filename]
|
| +
|
| + def sort_key(self, include):
|
| + return SCons.Node.FS._my_normcase(str(include))
|
| +
|
| + def find_include(self, include, source_dir, path):
|
| + try:
|
| + sub_path = path[include[0]]
|
| + except (IndexError, KeyError):
|
| + sub_path = ()
|
| + try_names = self._latex_names(include)
|
| + for n in try_names:
|
| + # see if we find it using the path in env[var]
|
| + i = SCons.Node.FS.find_file(n, (source_dir,) + sub_path[0])
|
| + if i:
|
| + return i, include
|
| + # see if we find it using the path in env['ENV'][var]
|
| + i = SCons.Node.FS.find_file(n, (source_dir,) + sub_path[1])
|
| + if i:
|
| + return i, include
|
| + return i, include
|
| +
|
| + def canonical_text(self, text):
|
| + """Standardize an input TeX-file contents.
|
| +
|
| + Currently:
|
| + * removes comments, unwrapping comment-wrapped lines.
|
| + """
|
| + out = []
|
| + line_continues_a_comment = False
|
| + for line in text.splitlines():
|
| + line,comment = self.comment_re.findall(line)[0]
|
| + if line_continues_a_comment == True:
|
| + out[-1] = out[-1] + line.lstrip()
|
| + else:
|
| + out.append(line)
|
| + line_continues_a_comment = len(comment) > 0
|
| + return '\n'.join(out).rstrip()+'\n'
|
| +
|
| + def scan(self, node):
|
| + # Modify the default scan function to allow for the regular
|
| + # expression to return a comma separated list of file names
|
| + # as can be the case with the bibliography keyword.
|
| +
|
| + # Cache the includes list in node so we only scan it once:
|
| + # path_dict = dict(list(path))
|
| + # add option for whitespace (\s) before the '['
|
| + noopt_cre = re.compile('\s*\[.*$')
|
| + if node.includes != None:
|
| + includes = node.includes
|
| + else:
|
| + text = self.canonical_text(node.get_text_contents())
|
| + includes = self.cre.findall(text)
|
| + # 1. Split comma-separated lines, e.g.
|
| + # ('bibliography', 'phys,comp')
|
| + # should become two entries
|
| + # ('bibliography', 'phys')
|
| + # ('bibliography', 'comp')
|
| + # 2. Remove the options, e.g., such as
|
| + # ('includegraphics[clip,width=0.7\\linewidth]', 'picture.eps')
|
| + # should become
|
| + # ('includegraphics', 'picture.eps')
|
| + split_includes = []
|
| + for include in includes:
|
| + inc_type = noopt_cre.sub('', include[0])
|
| + inc_list = include[1].split(',')
|
| + for j in range(len(inc_list)):
|
| + split_includes.append( (inc_type, inc_list[j]) )
|
| + #
|
| + includes = split_includes
|
| + node.includes = includes
|
| +
|
| + return includes
|
| +
|
| + def scan_recurse(self, node, path=()):
|
| + """ do a recursive scan of the top level target file
|
| + This lets us search for included files based on the
|
| + directory of the main file just as latex does"""
|
| +
|
| + path_dict = dict(list(path))
|
| +
|
| + queue = []
|
| + queue.extend( self.scan(node) )
|
| + seen = {}
|
| +
|
| + # This is a hand-coded DSU (decorate-sort-undecorate, or
|
| + # Schwartzian transform) pattern. The sort key is the raw name
|
| + # of the file as specifed on the \include, \input, etc. line.
|
| + # TODO: what about the comment in the original Classic scanner:
|
| + # """which lets
|
| + # us keep the sort order constant regardless of whether the file
|
| + # is actually found in a Repository or locally."""
|
| + nodes = []
|
| + source_dir = node.get_dir()
|
| + #for include in includes:
|
| + while queue:
|
| +
|
| + include = queue.pop()
|
| + try:
|
| + if seen[include[1]] == 1:
|
| + continue
|
| + except KeyError:
|
| + seen[include[1]] = 1
|
| +
|
| + #
|
| + # Handle multiple filenames in include[1]
|
| + #
|
| + n, i = self.find_include(include, source_dir, path_dict)
|
| + if n is None:
|
| + # Do not bother with 'usepackage' warnings, as they most
|
| + # likely refer to system-level files
|
| + if include[0] != 'usepackage':
|
| + SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
|
| + "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node))
|
| + else:
|
| + sortkey = self.sort_key(n)
|
| + nodes.append((sortkey, n))
|
| + # recurse down
|
| + queue.extend( self.scan(n) )
|
| +
|
| + return [pair[1] for pair in sorted(nodes)]
|
| +
|
| +# Local Variables:
|
| +# tab-width:4
|
| +# indent-tabs-mode:nil
|
| +# End:
|
| +# vim: set expandtab tabstop=4 shiftwidth=4:
|
|
|
| Property changes on: scons-2.0.1/engine/SCons/Scanner/LaTeX.py
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|