File indexing completed on 2024-12-17 18:40:00 UTC
view on githubraw file Latest commit d775ee35 on 2024-05-14 13:31:51 UTC
d775ee353a Oliv*0001 """ Cog content generation tool.
8fbfd1f382 Oliv*0002 """
0003
d775ee353a Oliv*0004 import copy
0005 import getopt
0006 import glob
0007 import io
0008 import linecache
0009 import os
0010 import re
0011 import shlex
0012 import sys
0013 import traceback
0014 import types
8fbfd1f382 Oliv*0015
d775ee353a Oliv*0016 from .whiteutils import commonPrefix, reindentBlock, whitePrefix
0017 from .utils import NumberedFileReader, Redirectable, change_dir, md5
8fbfd1f382 Oliv*0018
d775ee353a Oliv*0019 __version__ = "3.4.1"
8fbfd1f382 Oliv*0020
0021 usage = """\
d775ee353a Oliv*0022 cog - generate content with inlined Python code.
8fbfd1f382 Oliv*0023
d775ee353a Oliv*0024 cog [OPTIONS] [INFILE | @FILELIST | &FILELIST] ...
8fbfd1f382 Oliv*0025
0026 INFILE is the name of an input file, '-' will read from stdin.
0027 FILELIST is the name of a text file containing file names or
d775ee353a Oliv*0028 other @FILELISTs.
0029
0030 For @FILELIST, paths in the file list are relative to the working
0031 directory where cog was called. For &FILELIST, paths in the file
0032 list are relative to the file list location.
8fbfd1f382 Oliv*0033
0034 OPTIONS:
0035 -c Checksum the output to protect it against accidental change.
0036 -d Delete the generator code from the output file.
0037 -D name=val Define a global string available to your generator code.
0038 -e Warn if a file has no cog code in it.
0039 -I PATH Add PATH to the list of directories for data files and modules.
d775ee353a Oliv*0040 -n ENCODING Use ENCODING when reading and writing files.
8fbfd1f382 Oliv*0041 -o OUTNAME Write the output to OUTNAME.
d775ee353a Oliv*0042 -p PROLOGUE Prepend the generator source with PROLOGUE. Useful to insert an
0043 import line. Example: -p "import math"
0044 -P Use print() instead of cog.outl() for code output.
8fbfd1f382 Oliv*0045 -r Replace the input file with the output.
0046 -s STRING Suffix all generated output lines with STRING.
0047 -U Write the output with Unix newlines (only LF line-endings).
0048 -w CMD Use CMD if the output file needs to be made writable.
0049 A %s in the CMD will be filled with the filename.
0050 -x Excise all the generated output without running the generators.
d775ee353a Oliv*0051 -z The end-output marker can be omitted, and is assumed at eof.
8fbfd1f382 Oliv*0052 -v Print the version of cog and exit.
d775ee353a Oliv*0053 --check Check that the files would not change if run again.
0054 --markers='START END END-OUTPUT'
0055 The patterns surrounding cog inline instructions. Should
0056 include three values separated by spaces, the start, end,
0057 and end-output markers. Defaults to '[[[cog ]]] [[[end]]]'.
0058 --verbosity=VERBOSITY
0059 Control the amount of output. 2 (the default) lists all files,
0060 1 lists only changed files, 0 lists no files.
8fbfd1f382 Oliv*0061 -h Print this help.
0062 """
0063
0064 class CogError(Exception):
0065 """ Any exception raised by Cog.
0066 """
0067 def __init__(self, msg, file='', line=0):
0068 if file:
d775ee353a Oliv*0069 super().__init__(f"{file}({line}): {msg}")
8fbfd1f382 Oliv*0070 else:
d775ee353a Oliv*0071 super().__init__(msg)
8fbfd1f382 Oliv*0072
0073 class CogUsageError(CogError):
0074 """ An error in usage of command-line arguments in cog.
0075 """
d775ee353a Oliv*0076 pass
8fbfd1f382 Oliv*0077
0078 class CogInternalError(CogError):
0079 """ An error in the coding of Cog. Should never happen.
0080 """
d775ee353a Oliv*0081 pass
8fbfd1f382 Oliv*0082
0083 class CogGeneratedError(CogError):
0084 """ An error raised by a user's cog generator.
0085 """
d775ee353a Oliv*0086 pass
8fbfd1f382 Oliv*0087
d775ee353a Oliv*0088 class CogUserException(CogError):
0089 """ An exception caught when running a user's cog generator.
0090 The argument is the traceback message to print.
8fbfd1f382 Oliv*0091 """
d775ee353a Oliv*0092 pass
8fbfd1f382 Oliv*0093
d775ee353a Oliv*0094 class CogCheckFailed(CogError):
0095 """ A --check failed.
0096 """
0097 pass
8fbfd1f382 Oliv*0098
0099
0100 class CogGenerator(Redirectable):
0101 """ A generator pulled from a source file.
0102 """
d775ee353a Oliv*0103 def __init__(self, options=None):
0104 super().__init__()
8fbfd1f382 Oliv*0105 self.markers = []
0106 self.lines = []
d775ee353a Oliv*0107 self.options = options or CogOptions()
8fbfd1f382 Oliv*0108
0109 def parseMarker(self, l):
0110 self.markers.append(l)
0111
0112 def parseLine(self, l):
0113 self.lines.append(l.strip('\n'))
0114
0115 def getCode(self):
0116 """ Extract the executable Python code from the generator.
0117 """
0118
0119
0120
0121 prefIn = commonPrefix(self.markers + self.lines)
0122 if prefIn:
0123 self.markers = [ l.replace(prefIn, '', 1) for l in self.markers ]
0124 self.lines = [ l.replace(prefIn, '', 1) for l in self.lines ]
0125
0126 return reindentBlock(self.lines, '')
0127
d775ee353a Oliv*0128 def evaluate(self, cog, globals, fname):
8fbfd1f382 Oliv*0129
0130 prefOut = whitePrefix(self.markers)
0131
0132 intext = self.getCode()
0133 if not intext:
0134 return ''
0135
d775ee353a Oliv*0136 prologue = "import " + cog.cogmodulename + " as cog\n"
0137 if self.options.sPrologue:
0138 prologue += self.options.sPrologue + '\n'
0139 code = compile(prologue + intext, str(fname), 'exec')
8fbfd1f382 Oliv*0140
0141
0142 cog.cogmodule.msg = self.msg
0143 cog.cogmodule.out = self.out
0144 cog.cogmodule.outl = self.outl
0145 cog.cogmodule.error = self.error
0146
d775ee353a Oliv*0147 real_stdout = sys.stdout
0148 if self.options.bPrintOutput:
0149 sys.stdout = captured_stdout = io.StringIO()
0150
8fbfd1f382 Oliv*0151 self.outstring = ''
d775ee353a Oliv*0152 try:
0153 eval(code, globals)
0154 except CogError:
0155 raise
0156 except:
0157 typ, err, tb = sys.exc_info()
0158 frames = (tuple(fr) for fr in traceback.extract_tb(tb.tb_next))
0159 frames = find_cog_source(frames, prologue)
0160 msg = "".join(traceback.format_list(frames))
0161 msg += f"{typ.__name__}: {err}"
0162 raise CogUserException(msg)
0163 finally:
0164 sys.stdout = real_stdout
0165
0166 if self.options.bPrintOutput:
0167 self.outstring = captured_stdout.getvalue()
8fbfd1f382 Oliv*0168
0169
0170
0171
0172 if self.outstring and self.outstring[-1] != '\n':
0173 self.outstring += '\n'
0174
0175
0176 return self.outstring
0177
0178 def msg(self, s):
0179 self.prout("Message: "+s)
0180
0181 def out(self, sOut='', dedent=False, trimblanklines=False):
0182 """ The cog.out function.
0183 """
0184 if trimblanklines and ('\n' in sOut):
0185 lines = sOut.split('\n')
0186 if lines[0].strip() == '':
0187 del lines[0]
0188 if lines and lines[-1].strip() == '':
0189 del lines[-1]
0190 sOut = '\n'.join(lines)+'\n'
0191 if dedent:
0192 sOut = reindentBlock(sOut)
0193 self.outstring += sOut
0194
0195 def outl(self, sOut='', **kw):
0196 """ The cog.outl function.
0197 """
0198 self.out(sOut, **kw)
0199 self.out('\n')
0200
0201 def error(self, msg='Error raised by cog generator.'):
0202 """ The cog.error function.
0203 Instead of raising standard python errors, cog generators can use
0204 this function. It will display the error without a scary Python
0205 traceback.
0206 """
0207 raise CogGeneratedError(msg)
0208
0209
0210 class CogOptions:
0211 """ Options for a run of cog.
0212 """
0213 def __init__(self):
0214
0215 self.args = []
0216 self.includePath = []
0217 self.defines = {}
0218 self.bShowVersion = False
0219 self.sMakeWritableCmd = None
0220 self.bReplace = False
0221 self.bNoGenerate = False
0222 self.sOutputName = None
0223 self.bWarnEmpty = False
0224 self.bHashOutput = False
0225 self.bDeleteCode = False
0226 self.bEofCanBeEnd = False
0227 self.sSuffix = None
0228 self.bNewlines = False
d775ee353a Oliv*0229 self.sBeginSpec = '[[[cog'
0230 self.sEndSpec = ']]]'
0231 self.sEndOutput = '[[[end]]]'
0232 self.sEncoding = "utf-8"
0233 self.verbosity = 2
0234 self.sPrologue = ''
0235 self.bPrintOutput = False
0236 self.bCheck = False
8fbfd1f382 Oliv*0237
0238 def __eq__(self, other):
0239 """ Comparison operator for tests to use.
0240 """
0241 return self.__dict__ == other.__dict__
0242
0243 def clone(self):
0244 """ Make a clone of these options, for further refinement.
0245 """
0246 return copy.deepcopy(self)
0247
0248 def addToIncludePath(self, dirs):
0249 """ Add directories to the include path.
0250 """
0251 dirs = dirs.split(os.pathsep)
0252 self.includePath.extend(dirs)
0253
0254 def parseArgs(self, argv):
0255
0256 try:
d775ee353a Oliv*0257 opts, self.args = getopt.getopt(
0258 argv,
0259 'cdD:eI:n:o:rs:p:PUvw:xz',
0260 [
0261 'check',
0262 'markers=',
0263 'verbosity=',
0264 ]
0265 )
8fbfd1f382 Oliv*0266 except getopt.error as msg:
0267 raise CogUsageError(msg)
0268
0269
0270 for o, a in opts:
0271 if o == '-c':
0272 self.bHashOutput = True
0273 elif o == '-d':
0274 self.bDeleteCode = True
0275 elif o == '-D':
0276 if a.count('=') < 1:
0277 raise CogUsageError("-D takes a name=value argument")
0278 name, value = a.split('=', 1)
0279 self.defines[name] = value
0280 elif o == '-e':
0281 self.bWarnEmpty = True
0282 elif o == '-I':
d775ee353a Oliv*0283 self.addToIncludePath(os.path.abspath(a))
0284 elif o == '-n':
0285 self.sEncoding = a
8fbfd1f382 Oliv*0286 elif o == '-o':
0287 self.sOutputName = a
0288 elif o == '-r':
0289 self.bReplace = True
0290 elif o == '-s':
0291 self.sSuffix = a
d775ee353a Oliv*0292 elif o == '-p':
0293 self.sPrologue = a
0294 elif o == '-P':
0295 self.bPrintOutput = True
8fbfd1f382 Oliv*0296 elif o == '-U':
0297 self.bNewlines = True
0298 elif o == '-v':
0299 self.bShowVersion = True
0300 elif o == '-w':
0301 self.sMakeWritableCmd = a
0302 elif o == '-x':
0303 self.bNoGenerate = True
0304 elif o == '-z':
0305 self.bEofCanBeEnd = True
d775ee353a Oliv*0306 elif o == '--check':
0307 self.bCheck = True
0308 elif o == '--markers':
0309 self._parse_markers(a)
0310 elif o == '--verbosity':
0311 self.verbosity = int(a)
8fbfd1f382 Oliv*0312 else:
0313
0314
d775ee353a Oliv*0315 raise CogInternalError(f"Don't understand argument {o}")
0316
0317 def _parse_markers(self, val):
0318 try:
0319 self.sBeginSpec, self.sEndSpec, self.sEndOutput = val.split(" ")
0320 except ValueError:
0321 raise CogUsageError(
0322 f"--markers requires 3 values separated by spaces, could not parse {val!r}"
0323 )
8fbfd1f382 Oliv*0324
0325 def validate(self):
0326 """ Does nothing if everything is OK, raises CogError's if it's not.
0327 """
0328 if self.bReplace and self.bDeleteCode:
0329 raise CogUsageError("Can't use -d with -r (or you would delete all your source!)")
0330
0331 if self.bReplace and self.sOutputName:
0332 raise CogUsageError("Can't use -o with -r (they are opposites)")
0333
0334
0335 class Cog(Redirectable):
0336 """ The Cog engine.
0337 """
0338 def __init__(self):
d775ee353a Oliv*0339 super().__init__()
8fbfd1f382 Oliv*0340 self.options = CogOptions()
d775ee353a Oliv*0341 self._fixEndOutputPatterns()
0342 self.cogmodulename = "cog"
0343 self.createCogModule()
0344 self.bCheckFailed = False
8fbfd1f382 Oliv*0345
d775ee353a Oliv*0346 def _fixEndOutputPatterns(self):
0347 end_output = re.escape(self.options.sEndOutput)
0348 self.reEndOutput = re.compile(end_output + r"(?P<hashsect> *\(checksum: (?P<hash>[a-f0-9]+)\))")
0349 self.sEndFormat = self.options.sEndOutput + " (checksum: %s)"
8fbfd1f382 Oliv*0350
0351 def showWarning(self, msg):
d775ee353a Oliv*0352 self.prout(f"Warning: {msg}")
8fbfd1f382 Oliv*0353
0354 def isBeginSpecLine(self, s):
d775ee353a Oliv*0355 return self.options.sBeginSpec in s
8fbfd1f382 Oliv*0356
0357 def isEndSpecLine(self, s):
d775ee353a Oliv*0358 return self.options.sEndSpec in s and not self.isEndOutputLine(s)
8fbfd1f382 Oliv*0359
0360 def isEndOutputLine(self, s):
d775ee353a Oliv*0361 return self.options.sEndOutput in s
8fbfd1f382 Oliv*0362
d775ee353a Oliv*0363 def createCogModule(self):
0364 """ Make a cog "module" object so that imported Python modules
8fbfd1f382 Oliv*0365 can say "import cog" and get our state.
0366 """
d775ee353a Oliv*0367 self.cogmodule = types.SimpleNamespace()
8fbfd1f382 Oliv*0368 self.cogmodule.path = []
0369
0370 def openOutputFile(self, fname):
0371 """ Open an output file, taking all the details into account.
0372 """
0373 opts = {}
0374 mode = "w"
d775ee353a Oliv*0375 opts['encoding'] = self.options.sEncoding
8fbfd1f382 Oliv*0376 if self.options.bNewlines:
d775ee353a Oliv*0377 opts["newline"] = "\n"
0378 fdir = os.path.dirname(fname)
0379 if os.path.dirname(fdir) and not os.path.exists(fdir):
0380 os.makedirs(fdir)
8fbfd1f382 Oliv*0381 return open(fname, mode, **opts)
0382
0383 def openInputFile(self, fname):
d775ee353a Oliv*0384 """ Open an input file.
0385 """
8fbfd1f382 Oliv*0386 if fname == "-":
0387 return sys.stdin
0388 else:
d775ee353a Oliv*0389 return open(fname, encoding=self.options.sEncoding)
8fbfd1f382 Oliv*0390
0391 def processFile(self, fIn, fOut, fname=None, globals=None):
0392 """ Process an input file object to an output file object.
0393 fIn and fOut can be file objects, or file names.
0394 """
0395
0396 sFileIn = fname or ''
0397 sFileOut = fname or ''
0398 fInToClose = fOutToClose = None
0399
d775ee353a Oliv*0400 if isinstance(fIn, (bytes, str)):
8fbfd1f382 Oliv*0401
0402 sFileIn = fIn
0403 fIn = fInToClose = self.openInputFile(fIn)
d775ee353a Oliv*0404 if isinstance(fOut, (bytes, str)):
8fbfd1f382 Oliv*0405
0406 sFileOut = fOut
0407 fOut = fOutToClose = self.openOutputFile(fOut)
0408
0409 try:
0410 fIn = NumberedFileReader(fIn)
0411
0412 bSawCog = False
0413
0414 self.cogmodule.inFile = sFileIn
0415 self.cogmodule.outFile = sFileOut
d775ee353a Oliv*0416 self.cogmodulename = 'cog_' + md5(sFileOut.encode()).hexdigest()
0417 sys.modules[self.cogmodulename] = self.cogmodule
0418
0419 sys.modules['cog'] = self.cogmodule
8fbfd1f382 Oliv*0420
0421
0422 if globals is None:
0423 globals = {}
0424
0425
0426 globals.update(self.options.defines)
0427
0428
0429 l = fIn.readline()
0430 while l:
0431
0432 while l and not self.isBeginSpecLine(l):
0433 if self.isEndSpecLine(l):
d775ee353a Oliv*0434 raise CogError(
0435 f"Unexpected {self.options.sEndSpec!r}",
0436 file=sFileIn,
0437 line=fIn.linenumber(),
0438 )
8fbfd1f382 Oliv*0439 if self.isEndOutputLine(l):
d775ee353a Oliv*0440 raise CogError(
0441 f"Unexpected {self.options.sEndOutput!r}",
0442 file=sFileIn,
0443 line=fIn.linenumber(),
0444 )
8fbfd1f382 Oliv*0445 fOut.write(l)
0446 l = fIn.readline()
0447 if not l:
0448 break
0449 if not self.options.bDeleteCode:
0450 fOut.write(l)
0451
0452
d775ee353a Oliv*0453 gen = CogGenerator(options=self.options)
8fbfd1f382 Oliv*0454 gen.setOutput(stdout=self.stdout)
0455 gen.parseMarker(l)
0456 firstLineNum = fIn.linenumber()
0457 self.cogmodule.firstLineNum = firstLineNum
0458
0459
0460
0461 if self.isEndSpecLine(l):
d775ee353a Oliv*0462 beg = l.find(self.options.sBeginSpec)
0463 end = l.find(self.options.sEndSpec)
8fbfd1f382 Oliv*0464 if beg > end:
0465 raise CogError("Cog code markers inverted",
0466 file=sFileIn, line=firstLineNum)
0467 else:
d775ee353a Oliv*0468 sCode = l[beg+len(self.options.sBeginSpec):end].strip()
8fbfd1f382 Oliv*0469 gen.parseLine(sCode)
0470 else:
0471
0472 l = fIn.readline()
0473
0474
0475 while l and not self.isEndSpecLine(l):
0476 if self.isBeginSpecLine(l):
d775ee353a Oliv*0477 raise CogError(
0478 f"Unexpected {self.options.sBeginSpec!r}",
0479 file=sFileIn,
0480 line=fIn.linenumber(),
0481 )
8fbfd1f382 Oliv*0482 if self.isEndOutputLine(l):
d775ee353a Oliv*0483 raise CogError(
0484 f"Unexpected {self.options.sEndOutput!r}",
0485 file=sFileIn,
0486 line=fIn.linenumber(),
0487 )
8fbfd1f382 Oliv*0488 if not self.options.bDeleteCode:
0489 fOut.write(l)
0490 gen.parseLine(l)
0491 l = fIn.readline()
0492 if not l:
0493 raise CogError(
0494 "Cog block begun but never ended.",
0495 file=sFileIn, line=firstLineNum)
0496
0497 if not self.options.bDeleteCode:
0498 fOut.write(l)
0499 gen.parseMarker(l)
0500
0501 l = fIn.readline()
0502
0503
0504
d775ee353a Oliv*0505 previous = []
0506 hasher = md5()
8fbfd1f382 Oliv*0507 while l and not self.isEndOutputLine(l):
0508 if self.isBeginSpecLine(l):
d775ee353a Oliv*0509 raise CogError(
0510 f"Unexpected {self.options.sBeginSpec!r}",
0511 file=sFileIn,
0512 line=fIn.linenumber(),
0513 )
8fbfd1f382 Oliv*0514 if self.isEndSpecLine(l):
d775ee353a Oliv*0515 raise CogError(
0516 f"Unexpected {self.options.sEndSpec!r}",
0517 file=sFileIn,
0518 line=fIn.linenumber(),
0519 )
0520 previous.append(l)
0521 hasher.update(l.encode("utf-8"))
8fbfd1f382 Oliv*0522 l = fIn.readline()
0523 curHash = hasher.hexdigest()
0524
0525 if not l and not self.options.bEofCanBeEnd:
0526
d775ee353a Oliv*0527 raise CogError(
0528 f"Missing {self.options.sEndOutput!r} before end of file.",
0529 file=sFileIn,
0530 line=fIn.linenumber(),
0531 )
8fbfd1f382 Oliv*0532
0533
d775ee353a Oliv*0534 self.cogmodule.previous = "".join(previous)
8fbfd1f382 Oliv*0535
0536
0537
d775ee353a Oliv*0538 hasher = md5()
8fbfd1f382 Oliv*0539 if not self.options.bNoGenerate:
d775ee353a Oliv*0540 sFile = f"<cog {sFileIn}:{firstLineNum}>"
8fbfd1f382 Oliv*0541 sGen = gen.evaluate(cog=self, globals=globals, fname=sFile)
0542 sGen = self.suffixLines(sGen)
d775ee353a Oliv*0543 hasher.update(sGen.encode("utf-8"))
8fbfd1f382 Oliv*0544 fOut.write(sGen)
0545 newHash = hasher.hexdigest()
0546
0547 bSawCog = True
0548
0549
0550 hashMatch = self.reEndOutput.search(l)
0551 if self.options.bHashOutput:
0552 if hashMatch:
d775ee353a Oliv*0553 oldHash = hashMatch['hash']
8fbfd1f382 Oliv*0554 if oldHash != curHash:
0555 raise CogError("Output has been edited! Delete old checksum to unprotect.",
0556 file=sFileIn, line=fIn.linenumber())
0557
0558 endpieces = l.split(hashMatch.group(0), 1)
0559 else:
0560
d775ee353a Oliv*0561 endpieces = l.split(self.options.sEndOutput, 1)
8fbfd1f382 Oliv*0562 l = (self.sEndFormat % newHash).join(endpieces)
0563 else:
0564
0565
0566 if hashMatch:
d775ee353a Oliv*0567 l = l.replace(hashMatch['hashsect'], '', 1)
8fbfd1f382 Oliv*0568
0569 if not self.options.bDeleteCode:
0570 fOut.write(l)
0571 l = fIn.readline()
0572
0573 if not bSawCog and self.options.bWarnEmpty:
d775ee353a Oliv*0574 self.showWarning(f"no cog code found in {sFileIn}")
8fbfd1f382 Oliv*0575 finally:
0576 if fInToClose:
0577 fInToClose.close()
0578 if fOutToClose:
0579 fOutToClose.close()
0580
0581
0582
d775ee353a Oliv*0583 reNonEmptyLines = re.compile(r"^\s*\S+.*$", re.MULTILINE)
8fbfd1f382 Oliv*0584
0585 def suffixLines(self, text):
0586 """ Add suffixes to the lines in text, if our options desire it.
0587 text is many lines, as a single string.
0588 """
0589 if self.options.sSuffix:
0590
0591 repl = r"\g<0>" + self.options.sSuffix.replace('\\', '\\\\')
0592 text = self.reNonEmptyLines.sub(repl, text)
0593 return text
0594
0595 def processString(self, sInput, fname=None):
0596 """ Process sInput as the text to cog.
0597 Return the cogged output as a string.
0598 """
d775ee353a Oliv*0599 fOld = io.StringIO(sInput)
0600 fNew = io.StringIO()
8fbfd1f382 Oliv*0601 self.processFile(fOld, fNew, fname=fname)
0602 return fNew.getvalue()
0603
0604 def replaceFile(self, sOldPath, sNewText):
0605 """ Replace file sOldPath with the contents sNewText
0606 """
0607 if not os.access(sOldPath, os.W_OK):
0608
0609 if self.options.sMakeWritableCmd:
0610
0611 cmd = self.options.sMakeWritableCmd.replace('%s', sOldPath)
d775ee353a Oliv*0612 with os.popen(cmd) as cmdout:
0613 self.stdout.write(cmdout.read())
8fbfd1f382 Oliv*0614 if not os.access(sOldPath, os.W_OK):
d775ee353a Oliv*0615 raise CogError(f"Couldn't make {sOldPath} writable")
8fbfd1f382 Oliv*0616 else:
0617
d775ee353a Oliv*0618 raise CogError(f"Can't overwrite {sOldPath}")
8fbfd1f382 Oliv*0619 f = self.openOutputFile(sOldPath)
0620 f.write(sNewText)
0621 f.close()
0622
0623 def saveIncludePath(self):
0624 self.savedInclude = self.options.includePath[:]
0625 self.savedSysPath = sys.path[:]
0626
0627 def restoreIncludePath(self):
0628 self.options.includePath = self.savedInclude
0629 self.cogmodule.path = self.options.includePath
0630 sys.path = self.savedSysPath
0631
0632 def addToIncludePath(self, includePath):
0633 self.cogmodule.path.extend(includePath)
0634 sys.path.extend(includePath)
0635
0636 def processOneFile(self, sFile):
0637 """ Process one filename through cog.
0638 """
0639
0640 self.saveIncludePath()
d775ee353a Oliv*0641 bNeedNewline = False
8fbfd1f382 Oliv*0642
0643 try:
0644 self.addToIncludePath(self.options.includePath)
0645
0646
0647 self.addToIncludePath([os.path.dirname(sFile)])
0648
0649
0650 if self.options.sOutputName:
0651 self.processFile(sFile, self.options.sOutputName, sFile)
d775ee353a Oliv*0652 elif self.options.bReplace or self.options.bCheck:
8fbfd1f382 Oliv*0653
0654
d775ee353a Oliv*0655 verb = "Cogging" if self.options.bReplace else "Checking"
0656 if self.options.verbosity >= 2:
0657 self.prout(f"{verb} {sFile}", end="")
0658 bNeedNewline = True
8fbfd1f382 Oliv*0659
0660 try:
0661 fOldFile = self.openInputFile(sFile)
0662 sOldText = fOldFile.read()
0663 fOldFile.close()
0664 sNewText = self.processString(sOldText, fname=sFile)
0665 if sOldText != sNewText:
d775ee353a Oliv*0666 if self.options.verbosity >= 1:
0667 if self.options.verbosity < 2:
0668 self.prout(f"{verb} {sFile}", end="")
0669 self.prout(" (changed)")
0670 bNeedNewline = False
0671 if self.options.bReplace:
0672 self.replaceFile(sFile, sNewText)
0673 else:
0674 assert self.options.bCheck
0675 self.bCheckFailed = True
8fbfd1f382 Oliv*0676 finally:
0677
0678
0679
0680
0681 if bNeedNewline:
0682 self.prout("")
0683 else:
0684 self.processFile(sFile, self.stdout, sFile)
0685 finally:
0686 self.restoreIncludePath()
0687
d775ee353a Oliv*0688 def processWildcards(self, sFile):
0689 files = glob.glob(sFile)
0690 if files:
0691 for sMatchingFile in files:
0692 self.processOneFile(sMatchingFile)
0693 else:
0694 self.processOneFile(sFile)
0695
8fbfd1f382 Oliv*0696 def processFileList(self, sFileList):
0697 """ Process the files in a file list.
0698 """
0699 flist = self.openInputFile(sFileList)
0700 lines = flist.readlines()
0701 flist.close()
0702 for l in lines:
0703
0704 lex = shlex.shlex(l, posix=True)
0705 lex.whitespace_split = True
0706 lex.commenters = '#'
0707
0708 lex.escape = ''
0709 args = list(lex)
0710 if args:
0711 self.processArguments(args)
0712
0713 def processArguments(self, args):
0714 """ Process one command-line.
0715 """
0716 saved_options = self.options
0717 self.options = self.options.clone()
0718
0719 self.options.parseArgs(args[1:])
0720 self.options.validate()
0721
0722 if args[0][0] == '@':
0723 if self.options.sOutputName:
0724 raise CogUsageError("Can't use -o with @file")
0725 self.processFileList(args[0][1:])
d775ee353a Oliv*0726 elif args[0][0] == '&':
0727 if self.options.sOutputName:
0728 raise CogUsageError("Can't use -o with &file")
0729 file_list = args[0][1:]
0730 with change_dir(os.path.dirname(file_list)):
0731 self.processFileList(os.path.basename(file_list))
8fbfd1f382 Oliv*0732 else:
d775ee353a Oliv*0733 self.processWildcards(args[0])
8fbfd1f382 Oliv*0734
0735 self.options = saved_options
0736
0737 def callableMain(self, argv):
0738 """ All of command-line cog, but in a callable form.
0739 This is used by main.
0740 argv is the equivalent of sys.argv.
0741 """
d775ee353a Oliv*0742 argv = argv[1:]
8fbfd1f382 Oliv*0743
0744
0745 if '-?' in argv or '-h' in argv:
0746 self.prerr(usage, end="")
0747 return
0748
0749 self.options.parseArgs(argv)
0750 self.options.validate()
d775ee353a Oliv*0751 self._fixEndOutputPatterns()
8fbfd1f382 Oliv*0752
0753 if self.options.bShowVersion:
d775ee353a Oliv*0754 self.prout(f"Cog version {__version__}")
8fbfd1f382 Oliv*0755 return
0756
0757 if self.options.args:
0758 for a in self.options.args:
0759 self.processArguments([a])
0760 else:
0761 raise CogUsageError("No files to process")
0762
d775ee353a Oliv*0763 if self.bCheckFailed:
0764 raise CogCheckFailed("Check failed")
0765
8fbfd1f382 Oliv*0766 def main(self, argv):
0767 """ Handle the command-line execution for cog.
0768 """
0769
0770 try:
0771 self.callableMain(argv)
0772 return 0
0773 except CogUsageError as err:
0774 self.prerr(err)
d775ee353a Oliv*0775 self.prerr("(for help use -h)")
8fbfd1f382 Oliv*0776 return 2
0777 except CogGeneratedError as err:
d775ee353a Oliv*0778 self.prerr(f"Error: {err}")
8fbfd1f382 Oliv*0779 return 3
d775ee353a Oliv*0780 except CogUserException as err:
0781 self.prerr("Traceback (most recent call last):")
0782 self.prerr(err.args[0])
0783 return 4
0784 except CogCheckFailed as err:
0785 self.prerr(err)
0786 return 5
8fbfd1f382 Oliv*0787 except CogError as err:
0788 self.prerr(err)
0789 return 1
0790
d775ee353a Oliv*0791
0792 def find_cog_source(frame_summary, prologue):
0793 """Find cog source lines in a frame summary list, for printing tracebacks.
0794
0795 Arguments:
0796 frame_summary: a list of 4-item tuples, as returned by traceback.extract_tb.
0797 prologue: the text of the code prologue.
0798
0799 Returns
0800 A list of 4-item tuples, updated to correct the cog entries.
0801
0802 """
0803 prolines = prologue.splitlines()
0804 for filename, lineno, funcname, source in frame_summary:
0805 if not source:
0806 m = re.search(r"^<cog ([^:]+):(\d+)>$", filename)
0807 if m:
0808 if lineno <= len(prolines):
0809 filename = '<prologue>'
0810 source = prolines[lineno-1]
0811 lineno -= 1
0812 else:
0813 filename, coglineno = m.groups()
0814 coglineno = int(coglineno)
0815 lineno += coglineno - len(prolines)
0816 source = linecache.getline(filename, lineno).strip()
0817 yield filename, lineno, funcname, source
0818
0819
0820 def main():
0821 """Main function for entry_points to use."""
0822 return Cog().main(sys.argv)