Coverage for cogapp/cogapp.py: 49.50%
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# coding: utf8
2""" Cog content generation tool.
3 http://nedbatchelder.com/code/cog
5 Copyright 2004-2019, Ned Batchelder.
6"""
8from __future__ import absolute_import, print_function
10import copy
11import getopt
12import glob
13import hashlib
14import linecache
15import os
16import re
17import shlex
18import sys
19import traceback
21from .backward import PY3, StringIO, string_types, to_bytes
23__all__ = ['Cog', 'CogUsageError', 'main']
25__version__ = '3.2.0'
27usage = """\
28cog - generate content with inlined Python code.
30cog [OPTIONS] [INFILE | @FILELIST] ...
32INFILE is the name of an input file, '-' will read from stdin.
33FILELIST is the name of a text file containing file names or
34 other @FILELISTs.
36OPTIONS:
37 -c Checksum the output to protect it against accidental change.
38 -d Delete the generator code from the output file.
39 -D name=val Define a global string available to your generator code.
40 -e Warn if a file has no cog code in it.
41 -I PATH Add PATH to the list of directories for data files and modules.
42 -n ENCODING Use ENCODING when reading and writing files.
43 -o OUTNAME Write the output to OUTNAME.
44 -p PROLOGUE Prepend the generator source with PROLOGUE. Useful to insert an
45 import line. Example: -p "import math"
46 -P Use print() instead of outl for code output.
47 -r Replace the input file with the output.
48 -s STRING Suffix all generated output lines with STRING.
49 -U Write the output with Unix newlines (only LF line-endings).
50 -w CMD Use CMD if the output file needs to be made writable.
51 A %s in the CMD will be filled with the filename.
52 -x Excise all the generated output without running the generators.
53 -z The end-output marker can be omitted, and is assumed at eof.
54 -v Print the version of cog and exit.
55 --verbosity=VERBOSITY
56 Control the amount of output. 2 (the default) lists all files,
57 1 lists only changed files, 0 lists no files.
58 --markers='START END END-OUTPUT'
59 The patterns surrounding cog inline instructions. Should
60 include three values separated by spaces, the start, end,
61 and end-output markers. Defaults to '[[[cog ]]] [[[end]]]'.
62 -h Print this help.
63"""
65# Other package modules
66from .whiteutils import *
68class CogError(Exception):
69 """ Any exception raised by Cog.
70 """
71 def __init__(self, msg, file='', line=0):
72 if file:
73 Exception.__init__(self, "%s(%d): %s" % (file, line, msg))
74 else:
75 Exception.__init__(self, msg)
77class CogUsageError(CogError):
78 """ An error in usage of command-line arguments in cog.
79 """
80 pass
82class CogInternalError(CogError):
83 """ An error in the coding of Cog. Should never happen.
84 """
85 pass
87class CogGeneratedError(CogError):
88 """ An error raised by a user's cog generator.
89 """
90 pass
92class CogUserException(CogError):
93 """ An exception caught when running a user's cog generator.
94 The argument is the traceback message to print.
95 """
96 pass
98class Redirectable:
99 """ An object with its own stdout and stderr files.
100 """
101 def __init__(self):
102 self.stdout = sys.stdout
103 self.stderr = sys.stderr
105 def setOutput(self, stdout=None, stderr=None):
106 """ Assign new files for standard out and/or standard error.
107 """
108 if stdout: 108 ↛ 110line 108 didn't jump to line 110, because the condition on line 108 was never false
109 self.stdout = stdout
110 if stderr: 110 ↛ 111line 110 didn't jump to line 111, because the condition on line 110 was never true
111 self.stderr = stderr
113 def prout(self, s, end="\n"):
114 print(s, file=self.stdout, end=end)
116 def prerr(self, s, end="\n"):
117 print(s, file=self.stderr, end=end)
120class CogGenerator(Redirectable):
121 """ A generator pulled from a source file.
122 """
123 def __init__(self, options=None):
124 Redirectable.__init__(self)
125 self.markers = []
126 self.lines = []
127 self.options = options or CogOptions()
129 def parseMarker(self, l):
130 self.markers.append(l)
132 def parseLine(self, l):
133 self.lines.append(l.strip('\n'))
135 def getCode(self):
136 """ Extract the executable Python code from the generator.
137 """
138 # If the markers and lines all have the same prefix
139 # (end-of-line comment chars, for example),
140 # then remove it from all the lines.
141 prefIn = commonPrefix(self.markers + self.lines)
142 if prefIn:
143 self.markers = [ l.replace(prefIn, '', 1) for l in self.markers ]
144 self.lines = [ l.replace(prefIn, '', 1) for l in self.lines ]
146 return reindentBlock(self.lines, '')
148 def evaluate(self, cog, globals, fname):
149 # figure out the right whitespace prefix for the output
150 prefOut = whitePrefix(self.markers)
152 intext = self.getCode()
153 if not intext:
154 return ''
156 prologue = "import " + cog.cogmodulename + " as cog\n"
157 if self.options.sPrologue: 157 ↛ 158line 157 didn't jump to line 158, because the condition on line 157 was never true
158 prologue += self.options.sPrologue + '\n'
159 code = compile(prologue + intext, str(fname), 'exec')
161 # Make sure the "cog" module has our state.
162 cog.cogmodule.msg = self.msg
163 cog.cogmodule.out = self.out
164 cog.cogmodule.outl = self.outl
165 cog.cogmodule.error = self.error
167 real_stdout = sys.stdout
168 if self.options.bPrintOutput: 168 ↛ 169line 168 didn't jump to line 169, because the condition on line 168 was never true
169 sys.stdout = captured_stdout = StringIO()
171 self.outstring = ''
172 try:
173 eval(code, globals)
174 except CogError: 174 ↛ 175line 174 didn't jump to line 175, because the exception caught by line 174 didn't happen
175 raise
176 except:
177 typ, err, tb = sys.exc_info()
178 frames = (tuple(fr) for fr in traceback.extract_tb(tb.tb_next))
179 frames = find_cog_source(frames, prologue)
180 msg = "".join(traceback.format_list(frames))
181 msg += "{}: {}".format(typ.__name__, err)
182 raise CogUserException(msg)
183 finally:
184 sys.stdout = real_stdout
186 if self.options.bPrintOutput: 186 ↛ 187line 186 didn't jump to line 187, because the condition on line 186 was never true
187 self.outstring = captured_stdout.getvalue()
189 # We need to make sure that the last line in the output
190 # ends with a newline, or it will be joined to the
191 # end-output line, ruining cog's idempotency.
192 if self.outstring and self.outstring[-1] != '\n':
193 self.outstring += '\n'
195 return reindentBlock(self.outstring, prefOut)
197 def msg(self, s):
198 self.prout("Message: "+s)
200 def out(self, sOut='', dedent=False, trimblanklines=False):
201 """ The cog.out function.
202 """
203 if trimblanklines and ('\n' in sOut):
204 lines = sOut.split('\n')
205 if lines[0].strip() == '':
206 del lines[0]
207 if lines and lines[-1].strip() == '':
208 del lines[-1]
209 sOut = '\n'.join(lines)+'\n'
210 if dedent:
211 sOut = reindentBlock(sOut)
212 self.outstring += sOut
214 def outl(self, sOut='', **kw):
215 """ The cog.outl function.
216 """
217 self.out(sOut, **kw)
218 self.out('\n')
220 def error(self, msg='Error raised by cog generator.'):
221 """ The cog.error function.
222 Instead of raising standard python errors, cog generators can use
223 this function. It will display the error without a scary Python
224 traceback.
225 """
226 raise CogGeneratedError(msg)
229class NumberedFileReader:
230 """ A decorator for files that counts the readline()'s called.
231 """
232 def __init__(self, f):
233 self.f = f
234 self.n = 0
236 def readline(self):
237 l = self.f.readline()
238 if l:
239 self.n += 1
240 return l
242 def linenumber(self):
243 return self.n
246class CogOptions:
247 """ Options for a run of cog.
248 """
249 def __init__(self):
250 # Defaults for argument values.
251 self.args = []
252 self.includePath = []
253 self.defines = {}
254 self.bShowVersion = False
255 self.sMakeWritableCmd = None
256 self.bReplace = False
257 self.bNoGenerate = False
258 self.sOutputName = None
259 self.bWarnEmpty = False
260 self.bHashOutput = False
261 self.bDeleteCode = False
262 self.bEofCanBeEnd = False
263 self.sSuffix = None
264 self.bNewlines = False
265 self.sBeginSpec = '[[[cog'
266 self.sEndSpec = ']]]'
267 self.sEndOutput = '[[[end]]]'
268 self.sEncoding = "utf-8"
269 self.verbosity = 2
270 self.sPrologue = ''
271 self.bPrintOutput = False
273 def __eq__(self, other):
274 """ Comparison operator for tests to use.
275 """
276 return self.__dict__ == other.__dict__
278 def clone(self):
279 """ Make a clone of these options, for further refinement.
280 """
281 return copy.deepcopy(self)
283 def addToIncludePath(self, dirs):
284 """ Add directories to the include path.
285 """
286 dirs = dirs.split(os.pathsep)
287 self.includePath.extend(dirs)
289 def parseArgs(self, argv):
290 # Parse the command line arguments.
291 try:
292 opts, self.args = getopt.getopt(
293 argv,
294 'cdD:eI:n:o:rs:p:PUvw:xz',
295 [
296 'markers=',
297 'verbosity=',
298 ]
299 )
300 except getopt.error as msg:
301 raise CogUsageError(msg)
303 # Handle the command line arguments.
304 for o, a in opts:
305 if o == '-c':
306 self.bHashOutput = True
307 elif o == '-d':
308 self.bDeleteCode = True
309 elif o == '-D':
310 if a.count('=') < 1:
311 raise CogUsageError("-D takes a name=value argument")
312 name, value = a.split('=', 1)
313 self.defines[name] = value
314 elif o == '-e':
315 self.bWarnEmpty = True
316 elif o == '-I':
317 self.addToIncludePath(os.path.abspath(a))
318 elif o == '-n':
319 self.sEncoding = a
320 elif o == '-o':
321 self.sOutputName = a
322 elif o == '-r':
323 self.bReplace = True
324 elif o == '-s':
325 self.sSuffix = a
326 elif o == '-p':
327 self.sPrologue = a
328 elif o == '-P':
329 self.bPrintOutput = True
330 elif o == '-U':
331 self.bNewlines = True
332 elif o == '-v':
333 self.bShowVersion = True
334 elif o == '-w':
335 self.sMakeWritableCmd = a
336 elif o == '-x':
337 self.bNoGenerate = True
338 elif o == '-z':
339 self.bEofCanBeEnd = True
340 elif o == '--markers':
341 self._parse_markers(a)
342 elif o == '--verbosity':
343 self.verbosity = int(a)
344 else:
345 # Since getopt.getopt is given a list of possible flags,
346 # this is an internal error.
347 raise CogInternalError("Don't understand argument %s" % o)
349 def _parse_markers(self, val):
350 try:
351 self.sBeginSpec, self.sEndSpec, self.sEndOutput = val.split(' ')
352 except ValueError:
353 raise CogUsageError(
354 '--markers requires 3 values separated by spaces, could not parse %r' % val
355 )
357 def validate(self):
358 """ Does nothing if everything is OK, raises CogError's if it's not.
359 """
360 if self.bReplace and self.bDeleteCode:
361 raise CogUsageError("Can't use -d with -r (or you would delete all your source!)")
363 if self.bReplace and self.sOutputName:
364 raise CogUsageError("Can't use -o with -r (they are opposites)")
367class Cog(Redirectable):
368 """ The Cog engine.
369 """
370 def __init__(self):
371 Redirectable.__init__(self)
372 self.options = CogOptions()
373 self._fixEndOutputPatterns()
374 self.cogmodulename = "cog"
375 self.createCogModule()
377 def _fixEndOutputPatterns(self):
378 end_output = re.escape(self.options.sEndOutput)
379 self.reEndOutput = re.compile(end_output + r'(?P<hashsect> *\(checksum: (?P<hash>[a-f0-9]+)\))')
380 self.sEndFormat = self.options.sEndOutput + ' (checksum: %s)'
382 def showWarning(self, msg):
383 self.prout("Warning: "+msg)
385 def isBeginSpecLine(self, s):
386 return self.options.sBeginSpec in s
388 def isEndSpecLine(self, s):
389 return self.options.sEndSpec in s and not self.isEndOutputLine(s)
391 def isEndOutputLine(self, s):
392 return self.options.sEndOutput in s
394 def createCogModule(self):
395 """ Make a cog "module" object so that imported Python modules
396 can say "import cog" and get our state.
397 """
398 class DummyModule(object):
399 """Modules don't have to be anything special, just an object will do."""
400 pass
401 self.cogmodule = DummyModule()
402 self.cogmodule.path = []
404 def openOutputFile(self, fname):
405 """ Open an output file, taking all the details into account.
406 """
407 opts = {}
408 mode = "w"
409 if PY3:
410 opts['encoding'] = self.options.sEncoding
411 if self.options.bNewlines:
412 if PY3:
413 opts['newline'] = "\n"
414 else:
415 mode = "wb"
416 fdir = os.path.dirname(fname)
417 if os.path.dirname(fdir) and not os.path.exists(fdir):
418 os.makedirs(fdir)
419 return open(fname, mode, **opts)
421 def openInputFile(self, fname):
422 """ Open an input file. """
423 if fname == "-":
424 return sys.stdin
425 else:
426 opts = {}
427 if PY3:
428 opts['encoding'] = self.options.sEncoding
429 return open(fname, "r", **opts)
431 def processFile(self, fIn, fOut, fname=None, globals=None):
432 """ Process an input file object to an output file object.
433 fIn and fOut can be file objects, or file names.
434 """
436 sFileIn = fname or ''
437 sFileOut = fname or ''
438 fInToClose = fOutToClose = None
439 # Convert filenames to files.
440 if isinstance(fIn, string_types): 440 ↛ 442line 440 didn't jump to line 442, because the condition on line 440 was never true
441 # Open the input file.
442 sFileIn = fIn
443 fIn = fInToClose = self.openInputFile(fIn)
444 if isinstance(fOut, string_types): 444 ↛ 446line 444 didn't jump to line 446, because the condition on line 444 was never true
445 # Open the output file.
446 sFileOut = fOut
447 fOut = fOutToClose = self.openOutputFile(fOut)
449 try:
450 fIn = NumberedFileReader(fIn)
452 bSawCog = False
454 self.cogmodule.inFile = sFileIn
455 self.cogmodule.outFile = sFileOut
456 self.cogmodulename = 'cog_' + hashlib.md5(sFileOut.encode()).hexdigest()
457 sys.modules[self.cogmodulename] = self.cogmodule
458 # if "import cog" explicitly done in code by user, note threading will cause clashes.
459 sys.modules['cog'] = self.cogmodule
461 # The globals dict we'll use for this file.
462 if globals is None: 462 ↛ 466line 462 didn't jump to line 466, because the condition on line 462 was never false
463 globals = {}
465 # If there are any global defines, put them in the globals.
466 globals.update(self.options.defines)
468 # loop over generator chunks
469 l = fIn.readline()
470 while l:
471 # Find the next spec begin
472 while l and not self.isBeginSpecLine(l):
473 if self.isEndSpecLine(l): 473 ↛ 474line 473 didn't jump to line 474, because the condition on line 473 was never true
474 raise CogError("Unexpected '%s'" % self.options.sEndSpec,
475 file=sFileIn, line=fIn.linenumber())
476 if self.isEndOutputLine(l): 476 ↛ 477line 476 didn't jump to line 477, because the condition on line 476 was never true
477 raise CogError("Unexpected '%s'" % self.options.sEndOutput,
478 file=sFileIn, line=fIn.linenumber())
479 fOut.write(l)
480 l = fIn.readline()
481 if not l:
482 break
483 if not self.options.bDeleteCode: 483 ↛ 487line 483 didn't jump to line 487, because the condition on line 483 was never false
484 fOut.write(l)
486 # l is the begin spec
487 gen = CogGenerator(options=self.options)
488 gen.setOutput(stdout=self.stdout)
489 gen.parseMarker(l)
490 firstLineNum = fIn.linenumber()
491 self.cogmodule.firstLineNum = firstLineNum
493 # If the spec begin is also a spec end, then process the single
494 # line of code inside.
495 if self.isEndSpecLine(l):
496 beg = l.find(self.options.sBeginSpec)
497 end = l.find(self.options.sEndSpec)
498 if beg > end:
499 raise CogError("Cog code markers inverted",
500 file=sFileIn, line=firstLineNum)
501 else:
502 sCode = l[beg+len(self.options.sBeginSpec):end].strip()
503 gen.parseLine(sCode)
504 else:
505 # Deal with an ordinary code block.
506 l = fIn.readline()
508 # Get all the lines in the spec
509 while l and not self.isEndSpecLine(l):
510 if self.isBeginSpecLine(l): 510 ↛ 511line 510 didn't jump to line 511, because the condition on line 510 was never true
511 raise CogError("Unexpected '%s'" % self.options.sBeginSpec,
512 file=sFileIn, line=fIn.linenumber())
513 if self.isEndOutputLine(l): 513 ↛ 514line 513 didn't jump to line 514, because the condition on line 513 was never true
514 raise CogError("Unexpected '%s'" % self.options.sEndOutput,
515 file=sFileIn, line=fIn.linenumber())
516 if not self.options.bDeleteCode: 516 ↛ 518line 516 didn't jump to line 518, because the condition on line 516 was never false
517 fOut.write(l)
518 gen.parseLine(l)
519 l = fIn.readline()
520 if not l: 520 ↛ 521line 520 didn't jump to line 521, because the condition on line 520 was never true
521 raise CogError(
522 "Cog block begun but never ended.",
523 file=sFileIn, line=firstLineNum)
525 if not self.options.bDeleteCode: 525 ↛ 527line 525 didn't jump to line 527, because the condition on line 525 was never false
526 fOut.write(l)
527 gen.parseMarker(l)
529 l = fIn.readline()
531 # Eat all the lines in the output section. While reading past
532 # them, compute the md5 hash of the old output.
533 previous = ""
534 hasher = hashlib.md5()
535 while l and not self.isEndOutputLine(l):
536 if self.isBeginSpecLine(l): 536 ↛ 537line 536 didn't jump to line 537, because the condition on line 536 was never true
537 raise CogError("Unexpected '%s'" % self.options.sBeginSpec,
538 file=sFileIn, line=fIn.linenumber())
539 if self.isEndSpecLine(l): 539 ↛ 540line 539 didn't jump to line 540, because the condition on line 539 was never true
540 raise CogError("Unexpected '%s'" % self.options.sEndSpec,
541 file=sFileIn, line=fIn.linenumber())
542 previous += l
543 hasher.update(to_bytes(l))
544 l = fIn.readline()
545 curHash = hasher.hexdigest()
547 if not l and not self.options.bEofCanBeEnd: 547 ↛ 549line 547 didn't jump to line 549, because the condition on line 547 was never true
548 # We reached end of file before we found the end output line.
549 raise CogError("Missing '%s' before end of file." % self.options.sEndOutput,
550 file=sFileIn, line=fIn.linenumber())
552 # Make the previous output available to the current code
553 self.cogmodule.previous = previous
555 # Write the output of the spec to be the new output if we're
556 # supposed to generate code.
557 hasher = hashlib.md5()
558 if not self.options.bNoGenerate: 558 ↛ 564line 558 didn't jump to line 564, because the condition on line 558 was never false
559 sFile = "<cog %s:%d>" % (sFileIn, firstLineNum)
560 sGen = gen.evaluate(cog=self, globals=globals, fname=sFile)
561 sGen = self.suffixLines(sGen)
562 hasher.update(to_bytes(sGen))
563 fOut.write(sGen)
564 newHash = hasher.hexdigest()
566 bSawCog = True
568 # Write the ending output line
569 hashMatch = self.reEndOutput.search(l)
570 if self.options.bHashOutput: 570 ↛ 571line 570 didn't jump to line 571, because the condition on line 570 was never true
571 if hashMatch:
572 oldHash = hashMatch.groupdict()['hash']
573 if oldHash != curHash:
574 raise CogError("Output has been edited! Delete old checksum to unprotect.",
575 file=sFileIn, line=fIn.linenumber())
576 # Create a new end line with the correct hash.
577 endpieces = l.split(hashMatch.group(0), 1)
578 else:
579 # There was no old hash, but we want a new hash.
580 endpieces = l.split(self.options.sEndOutput, 1)
581 l = (self.sEndFormat % newHash).join(endpieces)
582 else:
583 # We don't want hashes output, so if there was one, get rid of
584 # it.
585 if hashMatch: 585 ↛ 586line 585 didn't jump to line 586, because the condition on line 585 was never true
586 l = l.replace(hashMatch.groupdict()['hashsect'], '', 1)
588 if not self.options.bDeleteCode: 588 ↛ 590line 588 didn't jump to line 590, because the condition on line 588 was never false
589 fOut.write(l)
590 l = fIn.readline()
592 if not bSawCog and self.options.bWarnEmpty: 592 ↛ 593line 592 didn't jump to line 593, because the condition on line 592 was never true
593 self.showWarning("no cog code found in %s" % sFileIn)
594 finally:
595 if fInToClose: 595 ↛ 596line 595 didn't jump to line 596, because the condition on line 595 was never true
596 fInToClose.close()
597 if fOutToClose: 597 ↛ 598line 597 didn't jump to line 598, because the condition on line 597 was never true
598 fOutToClose.close()
601 # A regex for non-empty lines, used by suffixLines.
602 reNonEmptyLines = re.compile(r"^\s*\S+.*$", re.MULTILINE)
604 def suffixLines(self, text):
605 """ Add suffixes to the lines in text, if our options desire it.
606 text is many lines, as a single string.
607 """
608 if self.options.sSuffix: 608 ↛ 610line 608 didn't jump to line 610, because the condition on line 608 was never true
609 # Find all non-blank lines, and add the suffix to the end.
610 repl = r"\g<0>" + self.options.sSuffix.replace('\\', '\\\\')
611 text = self.reNonEmptyLines.sub(repl, text)
612 return text
614 def processString(self, sInput, fname=None):
615 """ Process sInput as the text to cog.
616 Return the cogged output as a string.
617 """
618 fOld = StringIO(sInput)
619 fNew = StringIO()
620 self.processFile(fOld, fNew, fname=fname)
621 return fNew.getvalue()
623 def replaceFile(self, sOldPath, sNewText):
624 """ Replace file sOldPath with the contents sNewText
625 """
626 if not os.access(sOldPath, os.W_OK):
627 # Need to ensure we can write.
628 if self.options.sMakeWritableCmd:
629 # Use an external command to make the file writable.
630 cmd = self.options.sMakeWritableCmd.replace('%s', sOldPath)
631 self.stdout.write(os.popen(cmd).read())
632 if not os.access(sOldPath, os.W_OK):
633 raise CogError("Couldn't make %s writable" % sOldPath)
634 else:
635 # Can't write!
636 raise CogError("Can't overwrite %s" % sOldPath)
637 f = self.openOutputFile(sOldPath)
638 f.write(sNewText)
639 f.close()
641 def saveIncludePath(self):
642 self.savedInclude = self.options.includePath[:]
643 self.savedSysPath = sys.path[:]
645 def restoreIncludePath(self):
646 self.options.includePath = self.savedInclude
647 self.cogmodule.path = self.options.includePath
648 sys.path = self.savedSysPath
650 def addToIncludePath(self, includePath):
651 self.cogmodule.path.extend(includePath)
652 sys.path.extend(includePath)
654 def processOneFile(self, sFile):
655 """ Process one filename through cog.
656 """
658 self.saveIncludePath()
659 bNeedNewline = False
661 try:
662 self.addToIncludePath(self.options.includePath)
663 # Since we know where the input file came from,
664 # push its directory onto the include path.
665 self.addToIncludePath([os.path.dirname(sFile)])
667 # How we process the file depends on where the output is going.
668 if self.options.sOutputName:
669 self.processFile(sFile, self.options.sOutputName, sFile)
670 elif self.options.bReplace:
671 # We want to replace the cog file with the output,
672 # but only if they differ.
673 if self.options.verbosity >= 2:
674 self.prout("Cogging %s" % sFile, end="")
675 bNeedNewline = True
677 try:
678 fOldFile = self.openInputFile(sFile)
679 sOldText = fOldFile.read()
680 fOldFile.close()
681 sNewText = self.processString(sOldText, fname=sFile)
682 if sOldText != sNewText:
683 if self.options.verbosity >= 1:
684 if self.options.verbosity < 2:
685 self.prout("Cogging %s" % sFile, end="")
686 self.prout(" (changed)")
687 bNeedNewline = False
688 self.replaceFile(sFile, sNewText)
689 finally:
690 # The try-finally block is so we can print a partial line
691 # with the name of the file, and print (changed) on the
692 # same line, but also make sure to break the line before
693 # any traceback.
694 if bNeedNewline:
695 self.prout("")
696 else:
697 self.processFile(sFile, self.stdout, sFile)
698 finally:
699 self.restoreIncludePath()
701 def processWildcards(self, sFile):
702 files = glob.glob(sFile)
703 if files:
704 for sMatchingFile in files:
705 self.processOneFile(sMatchingFile)
706 else:
707 self.processOneFile(sFile)
709 def processFileList(self, sFileList):
710 """ Process the files in a file list.
711 """
712 flist = self.openInputFile(sFileList)
713 lines = flist.readlines()
714 flist.close()
715 for l in lines:
716 # Use shlex to parse the line like a shell.
717 lex = shlex.shlex(l, posix=True)
718 lex.whitespace_split = True
719 lex.commenters = '#'
720 # No escapes, so that backslash can be part of the path
721 lex.escape = ''
722 args = list(lex)
723 if args:
724 self.processArguments(args)
726 def processArguments(self, args):
727 """ Process one command-line.
728 """
729 saved_options = self.options
730 self.options = self.options.clone()
732 self.options.parseArgs(args[1:])
733 self.options.validate()
735 if args[0][0] == '@':
736 if self.options.sOutputName:
737 raise CogUsageError("Can't use -o with @file")
738 self.processFileList(args[0][1:])
739 else:
740 self.processWildcards(args[0])
742 self.options = saved_options
744 def callableMain(self, argv):
745 """ All of command-line cog, but in a callable form.
746 This is used by main.
747 argv is the equivalent of sys.argv.
748 """
749 argv = argv[1:]
751 # Provide help if asked for anywhere in the command line.
752 if '-?' in argv or '-h' in argv:
753 self.prerr(usage, end="")
754 return
756 self.options.parseArgs(argv)
757 self.options.validate()
758 self._fixEndOutputPatterns()
760 if self.options.bShowVersion:
761 self.prout("Cog version %s" % __version__)
762 return
764 if self.options.args:
765 for a in self.options.args:
766 self.processArguments([a])
767 else:
768 raise CogUsageError("No files to process")
770 def main(self, argv):
771 """ Handle the command-line execution for cog.
772 """
774 try:
775 self.callableMain(argv)
776 return 0
777 except CogUsageError as err:
778 self.prerr(err)
779 self.prerr("(for help use -?)")
780 return 2
781 except CogGeneratedError as err:
782 self.prerr("Error: %s" % err)
783 return 3
784 except CogUserException as err:
785 self.prerr("Traceback (most recent call last):")
786 self.prerr(err.args[0])
787 return 4
788 except CogError as err:
789 self.prerr(err)
790 return 1
793def find_cog_source(frame_summary, prologue):
794 """Find cog source lines in a frame summary list, for printing tracebacks.
796 Arguments:
797 frame_summary: a list of 4-item tuples, as returned by traceback.extract_tb.
798 prologue: the text of the code prologue.
800 Returns
801 A list of 4-item tuples, updated to correct the cog entries.
803 """
804 prolines = prologue.splitlines()
805 for filename, lineno, funcname, source in frame_summary:
806 if not source: 806 ↛ 818line 806 didn't jump to line 818, because the condition on line 806 was never false
807 m = re.search(r"^<cog ([^:]+):(\d+)>$", filename)
808 if m: 808 ↛ 809line 808 didn't jump to line 809, because the condition on line 808 was never true
809 if lineno <= len(prolines):
810 filename = '<prologue>'
811 source = prolines[lineno-1]
812 lineno -= 1 # Because "import cog" is the first line in the prologue
813 else:
814 filename, coglineno = m.groups()
815 coglineno = int(coglineno)
816 lineno += coglineno - len(prolines)
817 source = linecache.getline(filename, lineno).strip()
818 yield filename, lineno, funcname, source
821def main():
822 """Main function for entry_points to use."""
823 return Cog().main(sys.argv)