Package SCons :: Package Scanner :: Module LaTeX
[hide private]
[frames] | no frames]

Source Code for Module SCons.Scanner.LaTeX

  1  """SCons.Scanner.LaTeX 
  2   
  3  This module implements the dependency scanner for LaTeX code. 
  4   
  5  """ 
  6   
  7  # 
  8  # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation 
  9  # 
 10  # Permission is hereby granted, free of charge, to any person obtaining 
 11  # a copy of this software and associated documentation files (the 
 12  # "Software"), to deal in the Software without restriction, including 
 13  # without limitation the rights to use, copy, modify, merge, publish, 
 14  # distribute, sublicense, and/or sell copies of the Software, and to 
 15  # permit persons to whom the Software is furnished to do so, subject to 
 16  # the following conditions: 
 17  # 
 18  # The above copyright notice and this permission notice shall be included 
 19  # in all copies or substantial portions of the Software. 
 20  # 
 21  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 
 22  # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
 23  # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 24  # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
 25  # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
 26  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
 27  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
 28  # 
 29   
 30  __revision__ = "src/engine/SCons/Scanner/LaTeX.py 3603 2008/10/10 05:46:45 scons" 
 31   
 32  import os.path 
 33  import string 
 34  import re 
 35   
 36  import SCons.Scanner 
 37   
38 -def LaTeXScanner():
39 """Return a prototype Scanner instance for scanning LaTeX source files 40 when built with latex. 41 """ 42 ds = LaTeX(name = "LaTeXScanner", 43 suffixes = '$LATEXSUFFIXES', 44 # in the search order, see below in LaTeX class docstring 45 graphics_extensions = ['.eps', '.ps'], 46 recursive = 0) 47 return ds
48
49 -def PDFLaTeXScanner():
50 """Return a prototype Scanner instance for scanning LaTeX source files 51 when built with pdflatex. 52 """ 53 ds = LaTeX(name = "PDFLaTeXScanner", 54 suffixes = '$LATEXSUFFIXES', 55 # in the search order, see below in LaTeX class docstring 56 graphics_extensions = ['.pdf', '.png', '.jpg', '.gif', '.tif'], 57 recursive = 0) 58 return ds
59
60 -class LaTeX(SCons.Scanner.Base):
61 """Class for scanning LaTeX files for included files. 62 63 Unlike most scanners, which use regular expressions that just 64 return the included file name, this returns a tuple consisting 65 of the keyword for the inclusion ("include", "includegraphics", 66 "input", or "bibliography"), and then the file name itself. 67 Based on a quick look at LaTeX documentation, it seems that we 68 should append .tex suffix for the "include" keywords, append .tex if 69 there is no extension for the "input" keyword, and need to add .bib 70 for the "bibliography" keyword that does not accept extensions by itself. 71 72 Finally, if there is no extension for an "includegraphics" keyword 73 latex will append .ps or .eps to find the file, while pdftex may use .pdf, 74 .jpg, .tif, .mps, or .png. 75 76 The actual subset and search order may be altered by 77 DeclareGraphicsExtensions command. This complication is ignored. 78 The default order corresponds to experimentation with teTeX 79 $ latex --version 80 pdfeTeX 3.141592-1.21a-2.2 (Web2C 7.5.4) 81 kpathsea version 3.5.4 82 The order is: 83 ['.eps', '.ps'] for latex 84 ['.png', '.pdf', '.jpg', '.tif']. 85 86 Another difference is that the search path is determined by the type 87 of the file being searched: 88 env['TEXINPUTS'] for "input" and "include" keywords 89 env['TEXINPUTS'] for "includegraphics" keyword 90 env['BIBINPUTS'] for "bibliography" keyword 91 env['BSTINPUTS'] for "bibliographystyle" keyword 92 93 FIXME: also look for the class or style in document[class|style]{} 94 FIXME: also look for the argument of bibliographystyle{} 95 """ 96 keyword_paths = {'include': 'TEXINPUTS', 97 'input': 'TEXINPUTS', 98 'includegraphics': 'TEXINPUTS', 99 'bibliography': 'BIBINPUTS', 100 'bibliographystyle': 'BSTINPUTS', 101 'usepackage': 'TEXINPUTS'} 102 env_variables = SCons.Util.unique(keyword_paths.values()) 103
104 - def __init__(self, name, suffixes, graphics_extensions, *args, **kw):
105 106 # We have to include \n with the % we exclude from the first part 107 # part of the regex because the expression is compiled with re.M. 108 # Without the \n, the ^ could match the beginning of a *previous* 109 # line followed by one or more newline characters (i.e. blank 110 # lines), interfering with a match on the next line. 111 regex = r'^[^%\n]*\\(include|includegraphics(?:\[[^\]]+\])?|input|bibliography|usepackage){([^}]*)}' 112 self.cre = re.compile(regex, re.M) 113 self.graphics_extensions = graphics_extensions 114 115 def _scan(node, env, path=(), self=self): 116 node = node.rfile() 117 if not node.exists(): 118 return [] 119 return self.scan(node, path)
120 121 class FindMultiPathDirs: 122 """The stock FindPathDirs function has the wrong granularity: 123 it is called once per target, while we need the path that depends 124 on what kind of included files is being searched. This wrapper 125 hides multiple instances of FindPathDirs, one per the LaTeX path 126 variable in the environment. When invoked, the function calculates 127 and returns all the required paths as a dictionary (converted into 128 a tuple to become hashable). Then the scan function converts it 129 back and uses a dictionary of tuples rather than a single tuple 130 of paths. 131 """ 132 def __init__(self, dictionary): 133 self.dictionary = {} 134 for k,n in dictionary.items(): 135 self.dictionary[k] = SCons.Scanner.FindPathDirs(n)
136 137 def __call__(self, env, dir=None, target=None, source=None, 138 argument=None): 139 di = {} 140 for k,c in self.dictionary.items(): 141 di[k] = c(env, dir=None, target=None, source=None, 142 argument=None) 143 # To prevent "dict is not hashable error" 144 return tuple(di.items()) 145 146 class LaTeXScanCheck: 147 """Skip all but LaTeX source files, i.e., do not scan *.eps, 148 *.pdf, *.jpg, etc. 149 """ 150 def __init__(self, suffixes): 151 self.suffixes = suffixes 152 def __call__(self, node, env): 153 current = not node.has_builder() or node.is_up_to_date() 154 scannable = node.get_suffix() in env.subst_list(self.suffixes)[0] 155 # Returning false means that the file is not scanned. 156 return scannable and current 157 158 kw['function'] = _scan 159 kw['path_function'] = FindMultiPathDirs(LaTeX.keyword_paths) 160 kw['recursive'] = 1 161 kw['skeys'] = suffixes 162 kw['scan_check'] = LaTeXScanCheck(suffixes) 163 kw['name'] = name 164 165 apply(SCons.Scanner.Base.__init__, (self,) + args, kw) 166
167 - def _latex_names(self, include):
168 filename = include[1] 169 if include[0] == 'input': 170 base, ext = os.path.splitext( filename ) 171 if ext == "": 172 return [filename + '.tex'] 173 if (include[0] == 'include'): 174 return [filename + '.tex'] 175 if include[0] == 'bibliography': 176 base, ext = os.path.splitext( filename ) 177 if ext == "": 178 return [filename + '.bib'] 179 if include[0] == 'usepackage': 180 base, ext = os.path.splitext( filename ) 181 if ext == "": 182 return [filename + '.sty'] 183 if include[0] == 'includegraphics': 184 base, ext = os.path.splitext( filename ) 185 if ext == "": 186 #TODO(1.5) return [filename + e for e in self.graphics_extensions] 187 return map(lambda e, f=filename: f+e, self.graphics_extensions) 188 return [filename]
189
190 - def sort_key(self, include):
191 return SCons.Node.FS._my_normcase(str(include))
192
193 - def find_include(self, include, source_dir, path):
194 try: 195 sub_path = path[include[0]] 196 except (IndexError, KeyError): 197 sub_path = () 198 try_names = self._latex_names(include) 199 for n in try_names: 200 i = SCons.Node.FS.find_file(n, (source_dir,) + sub_path) 201 if i: 202 return i, include 203 return i, include
204
205 - def scan(self, node, path=()):
206 # Modify the default scan function to allow for the regular 207 # expression to return a comma separated list of file names 208 # as can be the case with the bibliography keyword. 209 210 # Cache the includes list in node so we only scan it once: 211 path_dict = dict(list(path)) 212 noopt_cre = re.compile('\[.*$') 213 if node.includes != None: 214 includes = node.includes 215 else: 216 includes = self.cre.findall(node.get_contents()) 217 # 1. Split comma-separated lines, e.g. 218 # ('bibliography', 'phys,comp') 219 # should become two entries 220 # ('bibliography', 'phys') 221 # ('bibliography', 'comp') 222 # 2. Remove the options, e.g., such as 223 # ('includegraphics[clip,width=0.7\\linewidth]', 'picture.eps') 224 # should become 225 # ('includegraphics', 'picture.eps') 226 split_includes = [] 227 for include in includes: 228 inc_type = noopt_cre.sub('', include[0]) 229 inc_list = string.split(include[1],',') 230 for j in range(len(inc_list)): 231 split_includes.append( (inc_type, inc_list[j]) ) 232 # 233 includes = split_includes 234 node.includes = includes 235 236 # This is a hand-coded DSU (decorate-sort-undecorate, or 237 # Schwartzian transform) pattern. The sort key is the raw name 238 # of the file as specifed on the \include, \input, etc. line. 239 # TODO: what about the comment in the original Classic scanner: 240 # """which lets 241 # us keep the sort order constant regardless of whether the file 242 # is actually found in a Repository or locally.""" 243 nodes = [] 244 source_dir = node.get_dir() 245 for include in includes: 246 # 247 # Handle multiple filenames in include[1] 248 # 249 n, i = self.find_include(include, source_dir, path_dict) 250 if n is None: 251 # Do not bother with 'usepackage' warnings, as they most 252 # likely refer to system-level files 253 if include[0] != 'usepackage': 254 SCons.Warnings.warn(SCons.Warnings.DependencyWarning, 255 "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node)) 256 else: 257 sortkey = self.sort_key(n) 258 nodes.append((sortkey, n)) 259 # 260 nodes.sort() 261 nodes = map(lambda pair: pair[1], nodes) 262 return nodes
263