Coverage for cogapp/cogapp.py : 49.34%

Hot-keys 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 imp
15import linecache
16import os
17import re
18import shlex
19import sys
20import traceback
22from .backward import PY3, StringIO, string_types, to_bytes
24__all__ = ['Cog', 'CogUsageError', 'main']
26__version__ = '3.0.0'
28usage = """\
29cog - generate content with inlined Python code.
31cog [OPTIONS] [INFILE | @FILELIST] ...
33INFILE is the name of an input file, '-' will read from stdin.
34FILELIST is the name of a text file containing file names or
35 other @FILELISTs.
37OPTIONS:
38 -c Checksum the output to protect it against accidental change.
39 -d Delete the generator code from the output file.
40 -D name=val Define a global string available to your generator code.
41 -e Warn if a file has no cog code in it.
42 -I PATH Add PATH to the list of directories for data files and modules.
43 -n ENCODING Use ENCODING when reading and writing files.
44 -o OUTNAME Write the output to OUTNAME.
45 -p PROLOGUE Prepend the generator source with PROLOGUE. Useful to insert an
46 import line. Example: -p "import math"
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 self.outstring = ''
168 try:
169 eval(code, globals)
170 except CogError: 170 ↛ 171line 170 didn't jump to line 171, because the exception caught by line 170 didn't happen
171 raise
172 except:
173 typ, err, tb = sys.exc_info()
174 frames = (tuple(fr) for fr in traceback.extract_tb(tb.tb_next))
175 frames = find_cog_source(frames, prologue)
176 msg = "".join(traceback.format_list(frames))
177 msg += "{}: {}".format(typ.__name__, err)
178 raise CogUserException(msg)
180 # We need to make sure that the last line in the output
181 # ends with a newline, or it will be joined to the
182 # end-output line, ruining cog's idempotency.
183 if self.outstring and self.outstring[-1] != '\n':
184 self.outstring += '\n'
186 return reindentBlock(self.outstring, prefOut)
188 def msg(self, s):
189 self.prout("Message: "+s)
191 def out(self, sOut='', dedent=False, trimblanklines=False):
192 """ The cog.out function.
193 """
194 if trimblanklines and ('\n' in sOut):
195 lines = sOut.split('\n')
196 if lines[0].strip() == '':
197 del lines[0]
198 if lines and lines[-1].strip() == '':
199 del lines[-1]
200 sOut = '\n'.join(lines)+'\n'
201 if dedent:
202 sOut = reindentBlock(sOut)
203 self.outstring += sOut
205 def outl(self, sOut='', **kw):
206 """ The cog.outl function.
207 """
208 self.out(sOut, **kw)
209 self.out('\n')
211 def error(self, msg='Error raised by cog generator.'):
212 """ The cog.error function.
213 Instead of raising standard python errors, cog generators can use
214 this function. It will display the error without a scary Python
215 traceback.
216 """
217 raise CogGeneratedError(msg)
220class NumberedFileReader:
221 """ A decorator for files that counts the readline()'s called.
222 """
223 def __init__(self, f):
224 self.f = f
225 self.n = 0
227 def readline(self):
228 l = self.f.readline()
229 if l:
230 self.n += 1
231 return l
233 def linenumber(self):
234 return self.n
237class CogOptions:
238 """ Options for a run of cog.
239 """
240 def __init__(self):
241 # Defaults for argument values.
242 self.args = []
243 self.includePath = []
244 self.defines = {}
245 self.bShowVersion = False
246 self.sMakeWritableCmd = None
247 self.bReplace = False
248 self.bNoGenerate = False
249 self.sOutputName = None
250 self.bWarnEmpty = False
251 self.bHashOutput = False
252 self.bDeleteCode = False
253 self.bEofCanBeEnd = False
254 self.sSuffix = None
255 self.bNewlines = False
256 self.sBeginSpec = '[[[cog'
257 self.sEndSpec = ']]]'
258 self.sEndOutput = '[[[end]]]'
259 self.sEncoding = "utf-8"
260 self.verbosity = 2
261 self.sPrologue = ''
263 def __eq__(self, other):
264 """ Comparison operator for tests to use.
265 """
266 return self.__dict__ == other.__dict__
268 def clone(self):
269 """ Make a clone of these options, for further refinement.
270 """
271 return copy.deepcopy(self)
273 def addToIncludePath(self, dirs):
274 """ Add directories to the include path.
275 """
276 dirs = dirs.split(os.pathsep)
277 self.includePath.extend(dirs)
279 def parseArgs(self, argv):
280 # Parse the command line arguments.
281 try:
282 opts, self.args = getopt.getopt(
283 argv,
284 'cdD:eI:n:o:rs:p:Uvw:xz',
285 [
286 'markers=',
287 'verbosity=',
288 ]
289 )
290 except getopt.error as msg:
291 raise CogUsageError(msg)
293 # Handle the command line arguments.
294 for o, a in opts:
295 if o == '-c':
296 self.bHashOutput = True
297 elif o == '-d':
298 self.bDeleteCode = True
299 elif o == '-D':
300 if a.count('=') < 1:
301 raise CogUsageError("-D takes a name=value argument")
302 name, value = a.split('=', 1)
303 self.defines[name] = value
304 elif o == '-e':
305 self.bWarnEmpty = True
306 elif o == '-I':
307 self.addToIncludePath(a)
308 elif o == '-n':
309 self.sEncoding = a
310 elif o == '-o':
311 self.sOutputName = a
312 elif o == '-r':
313 self.bReplace = True
314 elif o == '-s':
315 self.sSuffix = a
316 elif o == '-p':
317 self.sPrologue = a
318 elif o == '-U':
319 self.bNewlines = True
320 elif o == '-v':
321 self.bShowVersion = True
322 elif o == '-w':
323 self.sMakeWritableCmd = a
324 elif o == '-x':
325 self.bNoGenerate = True
326 elif o == '-z':
327 self.bEofCanBeEnd = True
328 elif o == '--markers':
329 self._parse_markers(a)
330 elif o == '--verbosity':
331 self.verbosity = int(a)
332 else:
333 # Since getopt.getopt is given a list of possible flags,
334 # this is an internal error.
335 raise CogInternalError("Don't understand argument %s" % o)
337 def _parse_markers(self, val):
338 try:
339 self.sBeginSpec, self.sEndSpec, self.sEndOutput = val.split(' ')
340 except ValueError:
341 raise CogUsageError(
342 '--markers requires 3 values separated by spaces, could not parse %r' % val
343 )
345 def validate(self):
346 """ Does nothing if everything is OK, raises CogError's if it's not.
347 """
348 if self.bReplace and self.bDeleteCode:
349 raise CogUsageError("Can't use -d with -r (or you would delete all your source!)")
351 if self.bReplace and self.sOutputName:
352 raise CogUsageError("Can't use -o with -r (they are opposites)")
355class Cog(Redirectable):
356 """ The Cog engine.
357 """
358 def __init__(self):
359 Redirectable.__init__(self)
360 self.options = CogOptions()
361 self._fixEndOutputPatterns()
362 self.cogmodulename = "cog"
363 self.installCogModule()
365 def _fixEndOutputPatterns(self):
366 end_output = re.escape(self.options.sEndOutput)
367 self.reEndOutput = re.compile(end_output + r'(?P<hashsect> *\(checksum: (?P<hash>[a-f0-9]+)\))')
368 self.sEndFormat = self.options.sEndOutput + ' (checksum: %s)'
370 def showWarning(self, msg):
371 self.prout("Warning: "+msg)
373 def isBeginSpecLine(self, s):
374 return self.options.sBeginSpec in s
376 def isEndSpecLine(self, s):
377 return self.options.sEndSpec in s and not self.isEndOutputLine(s)
379 def isEndOutputLine(self, s):
380 return self.options.sEndOutput in s
382 def installCogModule(self):
383 """ Magic mumbo-jumbo so that imported Python modules
384 can say "import cog" and get our state.
385 """
386 self.cogmodule = imp.new_module('cog')
387 self.cogmodule.path = []
389 def openOutputFile(self, fname):
390 """ Open an output file, taking all the details into account.
391 """
392 opts = {}
393 mode = "w"
394 if PY3:
395 opts['encoding'] = self.options.sEncoding
396 if self.options.bNewlines:
397 if PY3:
398 opts['newline'] = "\n"
399 else:
400 mode = "wb"
401 fdir = os.path.dirname(fname)
402 if os.path.dirname(fdir) and not os.path.exists(fdir):
403 os.makedirs(fdir)
404 return open(fname, mode, **opts)
406 def openInputFile(self, fname):
407 """ Open an input file. """
408 if fname == "-":
409 return sys.stdin
410 else:
411 opts = {}
412 if PY3:
413 opts['encoding'] = self.options.sEncoding
414 return open(fname, "r", **opts)
416 def processFile(self, fIn, fOut, fname=None, globals=None):
417 """ Process an input file object to an output file object.
418 fIn and fOut can be file objects, or file names.
419 """
421 sFileIn = fname or ''
422 sFileOut = fname or ''
423 fInToClose = fOutToClose = None
424 # Convert filenames to files.
425 if isinstance(fIn, string_types): 425 ↛ 427line 425 didn't jump to line 427, because the condition on line 425 was never true
426 # Open the input file.
427 sFileIn = fIn
428 fIn = fInToClose = self.openInputFile(fIn)
429 if isinstance(fOut, string_types): 429 ↛ 431line 429 didn't jump to line 431, because the condition on line 429 was never true
430 # Open the output file.
431 sFileOut = fOut
432 fOut = fOutToClose = self.openOutputFile(fOut)
434 try:
435 fIn = NumberedFileReader(fIn)
437 bSawCog = False
439 self.cogmodule.inFile = sFileIn
440 self.cogmodule.outFile = sFileOut
441 self.cogmodulename = 'cog_' + hashlib.md5(sFileOut.encode()).hexdigest()
442 sys.modules[self.cogmodulename] = self.cogmodule
443 # if "import cog" explicitly done in code by user, note threading will cause clashes.
444 sys.modules['cog'] = self.cogmodule
446 # The globals dict we'll use for this file.
447 if globals is None: 447 ↛ 451line 447 didn't jump to line 451, because the condition on line 447 was never false
448 globals = {}
450 # If there are any global defines, put them in the globals.
451 globals.update(self.options.defines)
453 # loop over generator chunks
454 l = fIn.readline()
455 while l:
456 # Find the next spec begin
457 while l and not self.isBeginSpecLine(l):
458 if self.isEndSpecLine(l): 458 ↛ 459line 458 didn't jump to line 459, because the condition on line 458 was never true
459 raise CogError("Unexpected '%s'" % self.options.sEndSpec,
460 file=sFileIn, line=fIn.linenumber())
461 if self.isEndOutputLine(l): 461 ↛ 462line 461 didn't jump to line 462, because the condition on line 461 was never true
462 raise CogError("Unexpected '%s'" % self.options.sEndOutput,
463 file=sFileIn, line=fIn.linenumber())
464 fOut.write(l)
465 l = fIn.readline()
466 if not l:
467 break
468 if not self.options.bDeleteCode: 468 ↛ 472line 468 didn't jump to line 472, because the condition on line 468 was never false
469 fOut.write(l)
471 # l is the begin spec
472 gen = CogGenerator(options=self.options)
473 gen.setOutput(stdout=self.stdout)
474 gen.parseMarker(l)
475 firstLineNum = fIn.linenumber()
476 self.cogmodule.firstLineNum = firstLineNum
478 # If the spec begin is also a spec end, then process the single
479 # line of code inside.
480 if self.isEndSpecLine(l):
481 beg = l.find(self.options.sBeginSpec)
482 end = l.find(self.options.sEndSpec)
483 if beg > end:
484 raise CogError("Cog code markers inverted",
485 file=sFileIn, line=firstLineNum)
486 else:
487 sCode = l[beg+len(self.options.sBeginSpec):end].strip()
488 gen.parseLine(sCode)
489 else:
490 # Deal with an ordinary code block.
491 l = fIn.readline()
493 # Get all the lines in the spec
494 while l and not self.isEndSpecLine(l):
495 if self.isBeginSpecLine(l): 495 ↛ 496line 495 didn't jump to line 496, because the condition on line 495 was never true
496 raise CogError("Unexpected '%s'" % self.options.sBeginSpec,
497 file=sFileIn, line=fIn.linenumber())
498 if self.isEndOutputLine(l): 498 ↛ 499line 498 didn't jump to line 499, because the condition on line 498 was never true
499 raise CogError("Unexpected '%s'" % self.options.sEndOutput,
500 file=sFileIn, line=fIn.linenumber())
501 if not self.options.bDeleteCode: 501 ↛ 503line 501 didn't jump to line 503, because the condition on line 501 was never false
502 fOut.write(l)
503 gen.parseLine(l)
504 l = fIn.readline()
505 if not l: 505 ↛ 506line 505 didn't jump to line 506, because the condition on line 505 was never true
506 raise CogError(
507 "Cog block begun but never ended.",
508 file=sFileIn, line=firstLineNum)
510 if not self.options.bDeleteCode: 510 ↛ 512line 510 didn't jump to line 512, because the condition on line 510 was never false
511 fOut.write(l)
512 gen.parseMarker(l)
514 l = fIn.readline()
516 # Eat all the lines in the output section. While reading past
517 # them, compute the md5 hash of the old output.
518 previous = ""
519 hasher = hashlib.md5()
520 while l and not self.isEndOutputLine(l):
521 if self.isBeginSpecLine(l): 521 ↛ 522line 521 didn't jump to line 522, because the condition on line 521 was never true
522 raise CogError("Unexpected '%s'" % self.options.sBeginSpec,
523 file=sFileIn, line=fIn.linenumber())
524 if self.isEndSpecLine(l): 524 ↛ 525line 524 didn't jump to line 525, because the condition on line 524 was never true
525 raise CogError("Unexpected '%s'" % self.options.sEndSpec,
526 file=sFileIn, line=fIn.linenumber())
527 previous += l
528 hasher.update(to_bytes(l))
529 l = fIn.readline()
530 curHash = hasher.hexdigest()
532 if not l and not self.options.bEofCanBeEnd: 532 ↛ 534line 532 didn't jump to line 534, because the condition on line 532 was never true
533 # We reached end of file before we found the end output line.
534 raise CogError("Missing '%s' before end of file." % self.options.sEndOutput,
535 file=sFileIn, line=fIn.linenumber())
537 # Make the previous output available to the current code
538 self.cogmodule.previous = previous
540 # Write the output of the spec to be the new output if we're
541 # supposed to generate code.
542 hasher = hashlib.md5()
543 if not self.options.bNoGenerate: 543 ↛ 549line 543 didn't jump to line 549, because the condition on line 543 was never false
544 sFile = "<cog %s:%d>" % (sFileIn, firstLineNum)
545 sGen = gen.evaluate(cog=self, globals=globals, fname=sFile)
546 sGen = self.suffixLines(sGen)
547 hasher.update(to_bytes(sGen))
548 fOut.write(sGen)
549 newHash = hasher.hexdigest()
551 bSawCog = True
553 # Write the ending output line
554 hashMatch = self.reEndOutput.search(l)
555 if self.options.bHashOutput: 555 ↛ 556line 555 didn't jump to line 556, because the condition on line 555 was never true
556 if hashMatch:
557 oldHash = hashMatch.groupdict()['hash']
558 if oldHash != curHash:
559 raise CogError("Output has been edited! Delete old checksum to unprotect.",
560 file=sFileIn, line=fIn.linenumber())
561 # Create a new end line with the correct hash.
562 endpieces = l.split(hashMatch.group(0), 1)
563 else:
564 # There was no old hash, but we want a new hash.
565 endpieces = l.split(self.options.sEndOutput, 1)
566 l = (self.sEndFormat % newHash).join(endpieces)
567 else:
568 # We don't want hashes output, so if there was one, get rid of
569 # it.
570 if hashMatch: 570 ↛ 571line 570 didn't jump to line 571, because the condition on line 570 was never true
571 l = l.replace(hashMatch.groupdict()['hashsect'], '', 1)
573 if not self.options.bDeleteCode: 573 ↛ 575line 573 didn't jump to line 575, because the condition on line 573 was never false
574 fOut.write(l)
575 l = fIn.readline()
577 if not bSawCog and self.options.bWarnEmpty: 577 ↛ 578line 577 didn't jump to line 578, because the condition on line 577 was never true
578 self.showWarning("no cog code found in %s" % sFileIn)
579 finally:
580 if fInToClose: 580 ↛ 581line 580 didn't jump to line 581, because the condition on line 580 was never true
581 fInToClose.close()
582 if fOutToClose: 582 ↛ 583line 582 didn't jump to line 583, because the condition on line 582 was never true
583 fOutToClose.close()
586 # A regex for non-empty lines, used by suffixLines.
587 reNonEmptyLines = re.compile(r"^\s*\S+.*$", re.MULTILINE)
589 def suffixLines(self, text):
590 """ Add suffixes to the lines in text, if our options desire it.
591 text is many lines, as a single string.
592 """
593 if self.options.sSuffix: 593 ↛ 595line 593 didn't jump to line 595, because the condition on line 593 was never true
594 # Find all non-blank lines, and add the suffix to the end.
595 repl = r"\g<0>" + self.options.sSuffix.replace('\\', '\\\\')
596 text = self.reNonEmptyLines.sub(repl, text)
597 return text
599 def processString(self, sInput, fname=None):
600 """ Process sInput as the text to cog.
601 Return the cogged output as a string.
602 """
603 fOld = StringIO(sInput)
604 fNew = StringIO()
605 self.processFile(fOld, fNew, fname=fname)
606 return fNew.getvalue()
608 def replaceFile(self, sOldPath, sNewText):
609 """ Replace file sOldPath with the contents sNewText
610 """
611 if not os.access(sOldPath, os.W_OK):
612 # Need to ensure we can write.
613 if self.options.sMakeWritableCmd:
614 # Use an external command to make the file writable.
615 cmd = self.options.sMakeWritableCmd.replace('%s', sOldPath)
616 self.stdout.write(os.popen(cmd).read())
617 if not os.access(sOldPath, os.W_OK):
618 raise CogError("Couldn't make %s writable" % sOldPath)
619 else:
620 # Can't write!
621 raise CogError("Can't overwrite %s" % sOldPath)
622 f = self.openOutputFile(sOldPath)
623 f.write(sNewText)
624 f.close()
626 def saveIncludePath(self):
627 self.savedInclude = self.options.includePath[:]
628 self.savedSysPath = sys.path[:]
630 def restoreIncludePath(self):
631 self.options.includePath = self.savedInclude
632 self.cogmodule.path = self.options.includePath
633 sys.path = self.savedSysPath
635 def addToIncludePath(self, includePath):
636 self.cogmodule.path.extend(includePath)
637 sys.path.extend(includePath)
639 def processOneFile(self, sFile):
640 """ Process one filename through cog.
641 """
643 self.saveIncludePath()
644 bNeedNewline = False
646 try:
647 self.addToIncludePath(self.options.includePath)
648 # Since we know where the input file came from,
649 # push its directory onto the include path.
650 self.addToIncludePath([os.path.dirname(sFile)])
652 # How we process the file depends on where the output is going.
653 if self.options.sOutputName:
654 self.processFile(sFile, self.options.sOutputName, sFile)
655 elif self.options.bReplace:
656 # We want to replace the cog file with the output,
657 # but only if they differ.
658 if self.options.verbosity >= 2:
659 self.prout("Cogging %s" % sFile, end="")
660 bNeedNewline = True
662 try:
663 fOldFile = self.openInputFile(sFile)
664 sOldText = fOldFile.read()
665 fOldFile.close()
666 sNewText = self.processString(sOldText, fname=sFile)
667 if sOldText != sNewText:
668 if self.options.verbosity >= 1:
669 if self.options.verbosity < 2:
670 self.prout("Cogging %s" % sFile, end="")
671 self.prout(" (changed)")
672 bNeedNewline = False
673 self.replaceFile(sFile, sNewText)
674 finally:
675 # The try-finally block is so we can print a partial line
676 # with the name of the file, and print (changed) on the
677 # same line, but also make sure to break the line before
678 # any traceback.
679 if bNeedNewline:
680 self.prout("")
681 else:
682 self.processFile(sFile, self.stdout, sFile)
683 finally:
684 self.restoreIncludePath()
686 def processWildcards(self, sFile):
687 files = glob.glob(sFile)
688 if files:
689 for sMatchingFile in files:
690 self.processOneFile(sMatchingFile)
691 else:
692 self.processOneFile(sFile)
694 def processFileList(self, sFileList):
695 """ Process the files in a file list.
696 """
697 flist = self.openInputFile(sFileList)
698 lines = flist.readlines()
699 flist.close()
700 for l in lines:
701 # Use shlex to parse the line like a shell.
702 lex = shlex.shlex(l, posix=True)
703 lex.whitespace_split = True
704 lex.commenters = '#'
705 # No escapes, so that backslash can be part of the path
706 lex.escape = ''
707 args = list(lex)
708 if args:
709 self.processArguments(args)
711 def processArguments(self, args):
712 """ Process one command-line.
713 """
714 saved_options = self.options
715 self.options = self.options.clone()
717 self.options.parseArgs(args[1:])
718 self.options.validate()
720 if args[0][0] == '@':
721 if self.options.sOutputName:
722 raise CogUsageError("Can't use -o with @file")
723 self.processFileList(args[0][1:])
724 else:
725 self.processWildcards(args[0])
727 self.options = saved_options
729 def callableMain(self, argv):
730 """ All of command-line cog, but in a callable form.
731 This is used by main.
732 argv is the equivalent of sys.argv.
733 """
734 argv = argv[1:]
736 # Provide help if asked for anywhere in the command line.
737 if '-?' in argv or '-h' in argv:
738 self.prerr(usage, end="")
739 return
741 self.options.parseArgs(argv)
742 self.options.validate()
743 self._fixEndOutputPatterns()
745 if self.options.bShowVersion:
746 self.prout("Cog version %s" % __version__)
747 return
749 if self.options.args:
750 for a in self.options.args:
751 self.processArguments([a])
752 else:
753 raise CogUsageError("No files to process")
755 def main(self, argv):
756 """ Handle the command-line execution for cog.
757 """
759 try:
760 self.callableMain(argv)
761 return 0
762 except CogUsageError as err:
763 self.prerr(err)
764 self.prerr("(for help use -?)")
765 return 2
766 except CogGeneratedError as err:
767 self.prerr("Error: %s" % err)
768 return 3
769 except CogUserException as err:
770 self.prerr("Traceback (most recent call last):")
771 self.prerr(err.args[0])
772 return 4
773 except CogError as err:
774 self.prerr(err)
775 return 1
778def find_cog_source(frame_summary, prologue):
779 """Find cog source lines in a frame summary list, for printing tracebacks.
781 Arguments:
782 frame_summary: a list of 4-item tuples, as returned by traceback.extract_tb.
783 prologue: the text of the code prologue.
785 Returns
786 A list of 4-item tuples, updated to correct the cog entries.
788 """
789 prolines = prologue.splitlines()
790 for filename, lineno, funcname, source in frame_summary:
791 if not source: 791 ↛ 803line 791 didn't jump to line 803, because the condition on line 791 was never false
792 m = re.search(r"^<cog ([^:]+):(\d+)>$", filename)
793 if m: 793 ↛ 794line 793 didn't jump to line 794, because the condition on line 793 was never true
794 if lineno <= len(prolines):
795 filename = '<prologue>'
796 source = prolines[lineno-1]
797 lineno -= 1 # Because "import cog" is the first line in the prologue
798 else:
799 filename, coglineno = m.groups()
800 coglineno = int(coglineno)
801 lineno += coglineno - len(prolines)
802 source = linecache.getline(filename, lineno).strip()
803 yield filename, lineno, funcname, source
806def main():
807 """Main function for entry_points to use."""
808 return Cog().main(sys.argv)