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