preprocessor.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. # This Source Code Form is subject to the terms of the Mozilla Public
  2. # License, v. 2.0. If a copy of the MPL was not distributed with this
  3. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  4. """
  5. This is a very primitive line based preprocessor, for times when using
  6. a C preprocessor isn't an option.
  7. It currently supports the following grammar for expressions, whitespace is
  8. ignored:
  9. expression :
  10. and_cond ( '||' expression ) ? ;
  11. and_cond:
  12. test ( '&&' and_cond ) ? ;
  13. test:
  14. unary ( ( '==' | '!=' ) unary ) ? ;
  15. unary :
  16. '!'? value ;
  17. value :
  18. [0-9]+ # integer
  19. | 'defined(' \w+ ')'
  20. | \w+ # string identifier or value;
  21. """
  22. import sys
  23. import os
  24. import platform
  25. import re
  26. from optparse import OptionParser
  27. import errno
  28. # hack around win32 mangling our line endings
  29. # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65443
  30. if sys.platform == "win32":
  31. import msvcrt
  32. msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
  33. os.linesep = '\n'
  34. __all__ = [
  35. 'Context',
  36. 'Expression',
  37. 'Preprocessor',
  38. ]
  39. class Expression:
  40. def __init__(self, expression_string):
  41. """
  42. Create a new expression with this string.
  43. The expression will already be parsed into an Abstract Syntax Tree.
  44. """
  45. self.content = expression_string
  46. self.offset = 0
  47. self.__ignore_whitespace()
  48. self.e = self.__get_logical_or()
  49. if self.content:
  50. raise Expression.ParseError, self
  51. def __get_logical_or(self):
  52. """
  53. Production: and_cond ( '||' expression ) ?
  54. """
  55. if not len(self.content):
  56. return None
  57. rv = Expression.__AST("logical_op")
  58. # test
  59. rv.append(self.__get_logical_and())
  60. self.__ignore_whitespace()
  61. if self.content[:2] != '||':
  62. # no logical op needed, short cut to our prime element
  63. return rv[0]
  64. # append operator
  65. rv.append(Expression.__ASTLeaf('op', self.content[:2]))
  66. self.__strip(2)
  67. self.__ignore_whitespace()
  68. rv.append(self.__get_logical_or())
  69. self.__ignore_whitespace()
  70. return rv
  71. def __get_logical_and(self):
  72. """
  73. Production: test ( '&&' and_cond ) ?
  74. """
  75. if not len(self.content):
  76. return None
  77. rv = Expression.__AST("logical_op")
  78. # test
  79. rv.append(self.__get_equality())
  80. self.__ignore_whitespace()
  81. if self.content[:2] != '&&':
  82. # no logical op needed, short cut to our prime element
  83. return rv[0]
  84. # append operator
  85. rv.append(Expression.__ASTLeaf('op', self.content[:2]))
  86. self.__strip(2)
  87. self.__ignore_whitespace()
  88. rv.append(self.__get_logical_and())
  89. self.__ignore_whitespace()
  90. return rv
  91. def __get_equality(self):
  92. """
  93. Production: unary ( ( '==' | '!=' ) unary ) ?
  94. """
  95. if not len(self.content):
  96. return None
  97. rv = Expression.__AST("equality")
  98. # unary
  99. rv.append(self.__get_unary())
  100. self.__ignore_whitespace()
  101. if not re.match('[=!]=', self.content):
  102. # no equality needed, short cut to our prime unary
  103. return rv[0]
  104. # append operator
  105. rv.append(Expression.__ASTLeaf('op', self.content[:2]))
  106. self.__strip(2)
  107. self.__ignore_whitespace()
  108. rv.append(self.__get_unary())
  109. self.__ignore_whitespace()
  110. return rv
  111. def __get_unary(self):
  112. """
  113. Production: '!'? value
  114. """
  115. # eat whitespace right away, too
  116. not_ws = re.match('!\s*', self.content)
  117. if not not_ws:
  118. return self.__get_value()
  119. rv = Expression.__AST('not')
  120. self.__strip(not_ws.end())
  121. rv.append(self.__get_value())
  122. self.__ignore_whitespace()
  123. return rv
  124. def __get_value(self):
  125. """
  126. Production: ( [0-9]+ | 'defined(' \w+ ')' | \w+ )
  127. Note that the order is important, and the expression is kind-of
  128. ambiguous as \w includes 0-9. One could make it unambiguous by
  129. removing 0-9 from the first char of a string literal.
  130. """
  131. rv = None
  132. m = re.match('defined\s*\(\s*(\w+)\s*\)', self.content)
  133. if m:
  134. word_len = m.end()
  135. rv = Expression.__ASTLeaf('defined', m.group(1))
  136. else:
  137. word_len = re.match('[0-9]*', self.content).end()
  138. if word_len:
  139. value = int(self.content[:word_len])
  140. rv = Expression.__ASTLeaf('int', value)
  141. else:
  142. word_len = re.match('\w*', self.content).end()
  143. if word_len:
  144. rv = Expression.__ASTLeaf('string', self.content[:word_len])
  145. else:
  146. raise Expression.ParseError, self
  147. self.__strip(word_len)
  148. self.__ignore_whitespace()
  149. return rv
  150. def __ignore_whitespace(self):
  151. ws_len = re.match('\s*', self.content).end()
  152. self.__strip(ws_len)
  153. return
  154. def __strip(self, length):
  155. """
  156. Remove a given amount of chars from the input and update
  157. the offset.
  158. """
  159. self.content = self.content[length:]
  160. self.offset += length
  161. def evaluate(self, context):
  162. """
  163. Evaluate the expression with the given context
  164. """
  165. # Helper function to evaluate __get_equality results
  166. def eval_equality(tok):
  167. left = opmap[tok[0].type](tok[0])
  168. right = opmap[tok[2].type](tok[2])
  169. rv = left == right
  170. if tok[1].value == '!=':
  171. rv = not rv
  172. return rv
  173. # Helper function to evaluate __get_logical_and and __get_logical_or results
  174. def eval_logical_op(tok):
  175. left = opmap[tok[0].type](tok[0])
  176. right = opmap[tok[2].type](tok[2])
  177. if tok[1].value == '&&':
  178. return left and right
  179. elif tok[1].value == '||':
  180. return left or right
  181. raise Expression.ParseError, self
  182. # Mapping from token types to evaluator functions
  183. # Apart from (non-)equality, all these can be simple lambda forms.
  184. opmap = {
  185. 'logical_op': eval_logical_op,
  186. 'equality': eval_equality,
  187. 'not': lambda tok: not opmap[tok[0].type](tok[0]),
  188. 'string': lambda tok: context[tok.value],
  189. 'defined': lambda tok: tok.value in context,
  190. 'int': lambda tok: tok.value}
  191. return opmap[self.e.type](self.e);
  192. class __AST(list):
  193. """
  194. Internal class implementing Abstract Syntax Tree nodes
  195. """
  196. def __init__(self, type):
  197. self.type = type
  198. super(self.__class__, self).__init__(self)
  199. class __ASTLeaf:
  200. """
  201. Internal class implementing Abstract Syntax Tree leafs
  202. """
  203. def __init__(self, type, value):
  204. self.value = value
  205. self.type = type
  206. def __str__(self):
  207. return self.value.__str__()
  208. def __repr__(self):
  209. return self.value.__repr__()
  210. class ParseError(StandardError):
  211. """
  212. Error raised when parsing fails.
  213. It has two members, offset and content, which give the offset of the
  214. error and the offending content.
  215. """
  216. def __init__(self, expression):
  217. self.offset = expression.offset
  218. self.content = expression.content[:3]
  219. def __str__(self):
  220. return 'Unexpected content at offset {0}, "{1}"'.format(self.offset,
  221. self.content)
  222. class Context(dict):
  223. """
  224. This class holds variable values by subclassing dict, and while it
  225. truthfully reports True and False on
  226. name in context
  227. it returns the variable name itself on
  228. context["name"]
  229. to reflect the ambiguity between string literals and preprocessor
  230. variables.
  231. """
  232. def __getitem__(self, key):
  233. if key in self:
  234. return super(self.__class__, self).__getitem__(key)
  235. return key
  236. class Preprocessor:
  237. """
  238. Class for preprocessing text files.
  239. """
  240. class Error(RuntimeError):
  241. def __init__(self, cpp, MSG, context):
  242. self.file = cpp.context['FILE']
  243. self.line = cpp.context['LINE']
  244. self.key = MSG
  245. RuntimeError.__init__(self, (self.file, self.line, self.key, context))
  246. def __init__(self, defines=None, marker='#'):
  247. self.context = Context()
  248. for k,v in {'FILE': '',
  249. 'LINE': 0,
  250. 'DIRECTORY': os.path.abspath('.')}.iteritems():
  251. self.context[k] = v
  252. self.actionLevel = 0
  253. self.disableLevel = 0
  254. # ifStates can be
  255. # 0: hadTrue
  256. # 1: wantsTrue
  257. # 2: #else found
  258. self.ifStates = []
  259. self.checkLineNumbers = False
  260. self.filters = []
  261. self.cmds = {}
  262. for cmd, level in {'define': 0,
  263. 'undef': 0,
  264. 'if': sys.maxint,
  265. 'ifdef': sys.maxint,
  266. 'ifndef': sys.maxint,
  267. 'else': 1,
  268. 'elif': 1,
  269. 'elifdef': 1,
  270. 'elifndef': 1,
  271. 'endif': sys.maxint,
  272. 'expand': 0,
  273. 'literal': 0,
  274. 'filter': 0,
  275. 'unfilter': 0,
  276. 'include': 0,
  277. 'includesubst': 0,
  278. 'error': 0}.iteritems():
  279. self.cmds[cmd] = (level, getattr(self, 'do_' + cmd))
  280. self.out = sys.stdout
  281. self.setMarker(marker)
  282. self.varsubst = re.compile('@(?P<VAR>\w+)@', re.U)
  283. self.includes = set()
  284. self.silenceMissingDirectiveWarnings = False
  285. if defines:
  286. self.context.update(defines)
  287. def warnUnused(self, file):
  288. msg = None
  289. if self.actionLevel == 0 and not self.silenceMissingDirectiveWarnings:
  290. sys.stderr.write('{0}: WARNING: no preprocessor directives found\n'.format(file))
  291. elif self.actionLevel == 1:
  292. sys.stderr.write('{0}: WARNING: no useful preprocessor directives found\n'.format(file))
  293. pass
  294. def setMarker(self, aMarker):
  295. """
  296. Set the marker to be used for processing directives.
  297. Used for handling CSS files, with pp.setMarker('%'), for example.
  298. The given marker may be None, in which case no markers are processed.
  299. """
  300. self.marker = aMarker
  301. if aMarker:
  302. self.instruction = re.compile('{0}(?P<cmd>[a-z]+)(?:\s(?P<args>.*))?$'
  303. .format(aMarker),
  304. re.U)
  305. self.comment = re.compile(aMarker, re.U)
  306. else:
  307. class NoMatch(object):
  308. def match(self, *args):
  309. return False
  310. self.instruction = self.comment = NoMatch()
  311. def setSilenceDirectiveWarnings(self, value):
  312. """
  313. Sets whether missing directive warnings are silenced, according to
  314. ``value``. The default behavior of the preprocessor is to emit
  315. such warnings.
  316. """
  317. self.silenceMissingDirectiveWarnings = value
  318. def addDefines(self, defines):
  319. """
  320. Adds the specified defines to the preprocessor.
  321. ``defines`` may be a dictionary object or an iterable of key/value pairs
  322. (as tuples or other iterables of length two)
  323. """
  324. self.context.update(defines)
  325. def clearDefines(self):
  326. self.context.clear
  327. def clone(self):
  328. """
  329. Create a clone of the current processor, including line ending
  330. settings, marker, variable definitions, output stream.
  331. """
  332. rv = Preprocessor()
  333. rv.context.update(self.context)
  334. rv.setMarker(self.marker)
  335. rv.out = self.out
  336. return rv
  337. def processFile(self, input, output):
  338. """
  339. Preprocesses the contents of the ``input`` stream and writes the result
  340. to the ``output`` stream.
  341. """
  342. self.out = output
  343. self.do_include(input, False)
  344. self.warnUnused(input.name)
  345. def applyFilters(self, aLine):
  346. for f in self.filters:
  347. aLine = f[1](aLine)
  348. return aLine
  349. def noteLineInfo(self):
  350. # Record the current line and file. Called once before transitioning
  351. # into or out of an included file and after writing each line.
  352. self.line_info = self.context['FILE'], self.context['LINE']
  353. def write(self, aLine):
  354. """
  355. Internal method for handling output.
  356. """
  357. if not self.out:
  358. return
  359. next_line, next_file = self.context['LINE'], self.context['FILE']
  360. if self.checkLineNumbers:
  361. expected_file, expected_line = self.line_info
  362. expected_line += 1
  363. if (expected_line != next_line or
  364. expected_file and expected_file != next_file):
  365. self.out.write('//@line {line} "{file}"\n'.format(line=next_line,
  366. file=next_file))
  367. self.noteLineInfo()
  368. filteredLine = self.applyFilters(aLine)
  369. if filteredLine != aLine:
  370. self.actionLevel = 2
  371. self.out.write(filteredLine)
  372. def handleCommandLine(self, args, defaultToStdin = False):
  373. """
  374. Parse a commandline into this parser.
  375. Uses OptionParser internally, no args mean sys.argv[1:].
  376. """
  377. def get_output_file(path):
  378. dir = os.path.dirname(path)
  379. if dir:
  380. try:
  381. os.makedirs(dir)
  382. except OSError as error:
  383. if error.errno != errno.EEXIST:
  384. raise
  385. return open(path, 'wb')
  386. p = self.getCommandLineParser()
  387. options, args = p.parse_args(args=args)
  388. out = self.out
  389. if options.output:
  390. out = get_output_file(options.output)
  391. if defaultToStdin and len(args) == 0:
  392. args = [sys.stdin]
  393. if args:
  394. for f in args:
  395. with open(f, 'rU') as input:
  396. self.processFile(input=input, output=out)
  397. if options.output:
  398. out.close()
  399. def getCommandLineParser(self, unescapeDefines = False):
  400. escapedValue = re.compile('".*"$')
  401. numberValue = re.compile('\d+$')
  402. def handleD(option, opt, value, parser):
  403. vals = value.split('=', 1)
  404. if len(vals) == 1:
  405. vals.append(1)
  406. elif unescapeDefines and escapedValue.match(vals[1]):
  407. # strip escaped string values
  408. vals[1] = vals[1][1:-1]
  409. elif numberValue.match(vals[1]):
  410. vals[1] = int(vals[1])
  411. self.context[vals[0]] = vals[1]
  412. def handleU(option, opt, value, parser):
  413. del self.context[value]
  414. def handleF(option, opt, value, parser):
  415. self.do_filter(value)
  416. def handleMarker(option, opt, value, parser):
  417. self.setMarker(value)
  418. def handleSilenceDirectiveWarnings(option, opt, value, parse):
  419. self.setSilenceDirectiveWarnings(True)
  420. p = OptionParser()
  421. p.add_option('-D', action='callback', callback=handleD, type="string",
  422. metavar="VAR[=VAL]", help='Define a variable')
  423. p.add_option('-U', action='callback', callback=handleU, type="string",
  424. metavar="VAR", help='Undefine a variable')
  425. p.add_option('-F', action='callback', callback=handleF, type="string",
  426. metavar="FILTER", help='Enable the specified filter')
  427. p.add_option('-o', '--output', type="string", default=None,
  428. metavar="FILENAME", help='Output to the specified file '+
  429. 'instead of stdout')
  430. p.add_option('--marker', action='callback', callback=handleMarker,
  431. type="string",
  432. help='Use the specified marker instead of #')
  433. p.add_option('--silence-missing-directive-warnings', action='callback',
  434. callback=handleSilenceDirectiveWarnings,
  435. help='Don\'t emit warnings about missing directives')
  436. return p
  437. def handleLine(self, aLine):
  438. """
  439. Handle a single line of input (internal).
  440. """
  441. if self.actionLevel == 0 and self.comment.match(aLine):
  442. self.actionLevel = 1
  443. m = self.instruction.match(aLine)
  444. if m:
  445. args = None
  446. cmd = m.group('cmd')
  447. try:
  448. args = m.group('args')
  449. except IndexError:
  450. pass
  451. if cmd not in self.cmds:
  452. raise Preprocessor.Error(self, 'INVALID_CMD', aLine)
  453. level, cmd = self.cmds[cmd]
  454. if (level >= self.disableLevel):
  455. cmd(args)
  456. if cmd != 'literal':
  457. self.actionLevel = 2
  458. elif self.disableLevel == 0 and not self.comment.match(aLine):
  459. self.write(aLine)
  460. # Instruction handlers
  461. # These are named do_'instruction name' and take one argument
  462. # Variables
  463. def do_define(self, args):
  464. m = re.match('(?P<name>\w+)(?:\s(?P<value>.*))?', args, re.U)
  465. if not m:
  466. raise Preprocessor.Error(self, 'SYNTAX_DEF', args)
  467. val = ''
  468. if m.group('value'):
  469. val = self.applyFilters(m.group('value'))
  470. try:
  471. val = int(val)
  472. except:
  473. pass
  474. self.context[m.group('name')] = val
  475. def do_undef(self, args):
  476. m = re.match('(?P<name>\w+)$', args, re.U)
  477. if not m:
  478. raise Preprocessor.Error(self, 'SYNTAX_DEF', args)
  479. if args in self.context:
  480. del self.context[args]
  481. # Logic
  482. def ensure_not_else(self):
  483. if len(self.ifStates) == 0 or self.ifStates[-1] == 2:
  484. sys.stderr.write('WARNING: bad nesting of #else\n')
  485. def do_if(self, args, replace=False):
  486. if self.disableLevel and not replace:
  487. self.disableLevel += 1
  488. return
  489. val = None
  490. try:
  491. e = Expression(args)
  492. val = e.evaluate(self.context)
  493. except Exception:
  494. # XXX do real error reporting
  495. raise Preprocessor.Error(self, 'SYNTAX_ERR', args)
  496. if type(val) == str:
  497. # we're looking for a number value, strings are false
  498. val = False
  499. if not val:
  500. self.disableLevel = 1
  501. if replace:
  502. if val:
  503. self.disableLevel = 0
  504. self.ifStates[-1] = self.disableLevel
  505. else:
  506. self.ifStates.append(self.disableLevel)
  507. pass
  508. def do_ifdef(self, args, replace=False):
  509. if self.disableLevel and not replace:
  510. self.disableLevel += 1
  511. return
  512. if re.match('\W', args, re.U):
  513. raise Preprocessor.Error(self, 'INVALID_VAR', args)
  514. if args not in self.context:
  515. self.disableLevel = 1
  516. if replace:
  517. if args in self.context:
  518. self.disableLevel = 0
  519. self.ifStates[-1] = self.disableLevel
  520. else:
  521. self.ifStates.append(self.disableLevel)
  522. pass
  523. def do_ifndef(self, args, replace=False):
  524. if self.disableLevel and not replace:
  525. self.disableLevel += 1
  526. return
  527. if re.match('\W', args, re.U):
  528. raise Preprocessor.Error(self, 'INVALID_VAR', args)
  529. if args in self.context:
  530. self.disableLevel = 1
  531. if replace:
  532. if args not in self.context:
  533. self.disableLevel = 0
  534. self.ifStates[-1] = self.disableLevel
  535. else:
  536. self.ifStates.append(self.disableLevel)
  537. pass
  538. def do_else(self, args, ifState = 2):
  539. self.ensure_not_else()
  540. hadTrue = self.ifStates[-1] == 0
  541. self.ifStates[-1] = ifState # in-else
  542. if hadTrue:
  543. self.disableLevel = 1
  544. return
  545. self.disableLevel = 0
  546. def do_elif(self, args):
  547. if self.disableLevel == 1:
  548. if self.ifStates[-1] == 1:
  549. self.do_if(args, replace=True)
  550. else:
  551. self.do_else(None, self.ifStates[-1])
  552. def do_elifdef(self, args):
  553. if self.disableLevel == 1:
  554. if self.ifStates[-1] == 1:
  555. self.do_ifdef(args, replace=True)
  556. else:
  557. self.do_else(None, self.ifStates[-1])
  558. def do_elifndef(self, args):
  559. if self.disableLevel == 1:
  560. if self.ifStates[-1] == 1:
  561. self.do_ifndef(args, replace=True)
  562. else:
  563. self.do_else(None, self.ifStates[-1])
  564. def do_endif(self, args):
  565. if self.disableLevel > 0:
  566. self.disableLevel -= 1
  567. if self.disableLevel == 0:
  568. self.ifStates.pop()
  569. # output processing
  570. def do_expand(self, args):
  571. lst = re.split('__(\w+)__', args, re.U)
  572. do_replace = False
  573. def vsubst(v):
  574. if v in self.context:
  575. return str(self.context[v])
  576. return ''
  577. for i in range(1, len(lst), 2):
  578. lst[i] = vsubst(lst[i])
  579. lst.append('\n') # add back the newline
  580. self.write(reduce(lambda x, y: x+y, lst, ''))
  581. def do_literal(self, args):
  582. self.write(args + '\n')
  583. def do_filter(self, args):
  584. filters = [f for f in args.split(' ') if hasattr(self, 'filter_' + f)]
  585. if len(filters) == 0:
  586. return
  587. current = dict(self.filters)
  588. for f in filters:
  589. current[f] = getattr(self, 'filter_' + f)
  590. filterNames = current.keys()
  591. filterNames.sort()
  592. self.filters = [(fn, current[fn]) for fn in filterNames]
  593. return
  594. def do_unfilter(self, args):
  595. filters = args.split(' ')
  596. current = dict(self.filters)
  597. for f in filters:
  598. if f in current:
  599. del current[f]
  600. filterNames = current.keys()
  601. filterNames.sort()
  602. self.filters = [(fn, current[fn]) for fn in filterNames]
  603. return
  604. # Filters
  605. #
  606. # emptyLines
  607. # Strips blank lines from the output.
  608. def filter_emptyLines(self, aLine):
  609. if aLine == '\n':
  610. return ''
  611. return aLine
  612. # slashslash
  613. # Strips everything after //
  614. def filter_slashslash(self, aLine):
  615. if (aLine.find('//') == -1):
  616. return aLine
  617. [aLine, rest] = aLine.split('//', 1)
  618. if rest:
  619. aLine += '\n'
  620. return aLine
  621. # spaces
  622. # Collapses sequences of spaces into a single space
  623. def filter_spaces(self, aLine):
  624. return re.sub(' +', ' ', aLine).strip(' ')
  625. # substition
  626. # helper to be used by both substition and attemptSubstitution
  627. def filter_substitution(self, aLine, fatal=True):
  628. def repl(matchobj):
  629. varname = matchobj.group('VAR')
  630. if varname in self.context:
  631. return str(self.context[varname])
  632. if fatal:
  633. raise Preprocessor.Error(self, 'UNDEFINED_VAR', varname)
  634. return matchobj.group(0)
  635. return self.varsubst.sub(repl, aLine)
  636. def filter_attemptSubstitution(self, aLine):
  637. return self.filter_substitution(aLine, fatal=False)
  638. # File ops
  639. def do_include(self, args, filters=True):
  640. """
  641. Preprocess a given file.
  642. args can either be a file name, or a file-like object.
  643. Files should be opened, and will be closed after processing.
  644. """
  645. isName = type(args) == str or type(args) == unicode
  646. oldCheckLineNumbers = self.checkLineNumbers
  647. self.checkLineNumbers = False
  648. if isName:
  649. try:
  650. args = str(args)
  651. if filters:
  652. args = self.applyFilters(args)
  653. if not os.path.isabs(args):
  654. args = os.path.join(self.context['DIRECTORY'], args)
  655. args = open(args, 'rU')
  656. except Preprocessor.Error:
  657. raise
  658. except:
  659. raise Preprocessor.Error(self, 'FILE_NOT_FOUND', str(args))
  660. self.checkLineNumbers = bool(re.search('\.(js|jsm|java)(?:\.in)?$', args.name))
  661. oldFile = self.context['FILE']
  662. oldLine = self.context['LINE']
  663. oldDir = self.context['DIRECTORY']
  664. self.noteLineInfo()
  665. if args.isatty():
  666. # we're stdin, use '-' and '' for file and dir
  667. self.context['FILE'] = '-'
  668. self.context['DIRECTORY'] = ''
  669. else:
  670. abspath = os.path.abspath(args.name)
  671. self.includes.add(abspath)
  672. self.context['FILE'] = abspath
  673. self.context['DIRECTORY'] = os.path.dirname(abspath)
  674. self.context['LINE'] = 0
  675. for l in args:
  676. self.context['LINE'] += 1
  677. self.handleLine(l)
  678. if isName:
  679. args.close()
  680. self.context['FILE'] = oldFile
  681. self.checkLineNumbers = oldCheckLineNumbers
  682. self.context['LINE'] = oldLine
  683. self.context['DIRECTORY'] = oldDir
  684. def do_includesubst(self, args):
  685. args = self.filter_substitution(args)
  686. self.do_include(args)
  687. def do_error(self, args):
  688. raise Preprocessor.Error(self, 'Error: ', str(args))
  689. # Keep this module independently executable.
  690. if __name__ == "__main__":
  691. pp = Preprocessor()
  692. pp.handleCommandLine(None, True)