Coverage for cogapp/cogapp.py : 47.12%

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
# coding: utf8 http://nedbatchelder.com/code/cog
Copyright 2004-2016, Ned Batchelder. """
cog - generate code with inlined Python code.
cog [OPTIONS] [INFILE | @FILELIST] ...
INFILE is the name of an input file, '-' will read from stdin. FILELIST is the name of a text file containing file names or other @FILELISTs.
OPTIONS: -c Checksum the output to protect it against accidental change. -d Delete the generator code from the output file. -D name=val Define a global string available to your generator code. -e Warn if a file has no cog code in it. -I PATH Add PATH to the list of directories for data files and modules. -n ENCODING Use ENCODING when reading and writing files. -o OUTNAME Write the output to OUTNAME. -r Replace the input file with the output. -s STRING Suffix all generated output lines with STRING. -U Write the output with Unix newlines (only LF line-endings). -w CMD Use CMD if the output file needs to be made writable. A %s in the CMD will be filled with the filename. -x Excise all the generated output without running the generators. -z The end-output marker can be omitted, and is assumed at eof. -v Print the version of cog and exit. --verbosity=VERBOSITY Control the amount of output. 2 (the default) lists all files, 1 lists only changed files, 0 lists no files. --markers='START END END-OUTPUT' The patterns surrounding cog inline instructions. Should include three values separated by spaces, the start, end, and end-output markers. Defaults to '[[[cog ]]] [[[end]]]'. -h Print this help. """
# Other package modules
""" Any exception raised by Cog. """ else: Exception.__init__(self, msg)
""" An error in usage of command-line arguments in cog. """ pass #pragma: no cover
""" An error in the coding of Cog. Should never happen. """ pass #pragma: no cover
""" An error raised by a user's cog generator. """ pass #pragma: no cover
""" An object with its own stdout and stderr files. """
""" Assign new files for standard out and/or standard error. """ self.stderr = stderr
print(s, file=self.stdout, end=end)
print(s, file=self.stderr, end=end)
""" A generator pulled from a source file. """
""" Extract the executable Python code from the generator. """ # If the markers and lines all have the same prefix # (end-of-line comment chars, for example), # then remove it from all the lines.
# figure out the right whitespace prefix for the output
# In Python 2.2, the last line has to end in a newline.
# Make sure the "cog" module has our state.
# We need to make sure that the last line in the output # ends with a newline, or it will be joined to the # end-output line, ruining cog's idempotency.
self.prout("Message: "+s)
""" The cog.out function. """ sOut = sOut.encode(self.sEncoding)
""" The cog.outl function. """
""" The cog.error function. Instead of raising standard python errors, cog generators can use this function. It will display the error without a scary Python traceback. """ raise CogGeneratedError(msg)
""" A decorator for files that counts the readline()'s called. """
""" Options for a run of cog. """ # Defaults for argument values.
""" Comparison operator for tests to use. """ return self.__dict__ == other.__dict__
""" Make a clone of these options, for further refinement. """ return copy.deepcopy(self)
""" Add directories to the include path. """ dirs = dirs.split(os.pathsep) self.includePath.extend(dirs)
# Parse the command line arguments. try: opts, self.args = getopt.getopt( argv, 'cdD:eI:n:o:rs:Uvw:xz', [ 'markers=', 'verbosity=', ] ) except getopt.error as msg: raise CogUsageError(msg)
# Handle the command line arguments. for o, a in opts: if o == '-c': self.bHashOutput = True elif o == '-d': self.bDeleteCode = True elif o == '-D': if a.count('=') < 1: raise CogUsageError("-D takes a name=value argument") name, value = a.split('=', 1) self.defines[name] = value elif o == '-e': self.bWarnEmpty = True elif o == '-I': self.addToIncludePath(a) elif o == '-n': self.sEncoding = a elif o == '-o': self.sOutputName = a elif o == '-r': self.bReplace = True elif o == '-s': self.sSuffix = a elif o == '-U': self.bNewlines = True elif o == '-v': self.bShowVersion = True elif o == '-w': self.sMakeWritableCmd = a elif o == '-x': self.bNoGenerate = True elif o == '-z': self.bEofCanBeEnd = True elif o == '--markers': self._parse_markers(a) elif o == '--verbosity': self.verbosity = int(a) else: # Since getopt.getopt is given a list of possible flags, # this is an internal error. raise CogInternalError("Don't understand argument %s" % o)
try: self.sBeginSpec, self.sEndSpec, self.sEndOutput = val.split(' ') except ValueError: raise CogUsageError( '--markers requires 3 values separated by spaces, could not parse %r' % val )
""" Does nothing if everything is OK, raises CogError's if it's not. """ if self.bReplace and self.bDeleteCode: raise CogUsageError("Can't use -d with -r (or you would delete all your source!)")
if self.bReplace and self.sOutputName: raise CogUsageError("Can't use -o with -r (they are opposites)")
""" The Cog engine. """
self.prout("Warning: "+msg)
""" Magic mumbo-jumbo so that imported Python modules can say "import cog" and get our state. """
""" Open an output file, taking all the details into account. """ opts = {} mode = "w" if PY3: opts['encoding'] = self.options.sEncoding if self.options.bNewlines: if PY3: opts['newline'] = "\n" else: mode = "wb" fdir = os.path.dirname(fname) if os.path.dirname(fdir) and not os.path.exists(fdir): os.makedirs(fdir) return open(fname, mode, **opts)
""" Open an input file. """ if fname == "-": return sys.stdin else: opts = {} if PY3: opts['encoding'] = self.options.sEncoding return open(fname, "r", **opts)
""" Process an input file object to an output file object. fIn and fOut can be file objects, or file names. """
# Convert filenames to files. # Open the input file. sFileIn = fIn fIn = fInToClose = self.openInputFile(fIn) # Open the output file. sFileOut = fOut fOut = fOutToClose = self.openOutputFile(fOut)
# The globals dict we'll use for this file.
# If there are any global defines, put them in the globals.
# loop over generator chunks # Find the next spec begin raise CogError("Unexpected '%s'" % self.options.sEndSpec, file=sFileIn, line=fIn.linenumber()) raise CogError("Unexpected '%s'" % self.options.sEndOutput, file=sFileIn, line=fIn.linenumber())
# l is the begin spec
# If the spec begin is also a spec end, then process the single # line of code inside. file=sFileIn, line=firstLineNum) else: else: # Deal with an ordinary code block.
# Get all the lines in the spec raise CogError("Unexpected '%s'" % self.options.sBeginSpec, file=sFileIn, line=fIn.linenumber()) raise CogError("Unexpected '%s'" % self.options.sEndOutput, file=sFileIn, line=fIn.linenumber()) raise CogError( "Cog block begun but never ended.", file=sFileIn, line=firstLineNum)
# Eat all the lines in the output section. While reading past # them, compute the md5 hash of the old output. raise CogError("Unexpected '%s'" % self.options.sBeginSpec, file=sFileIn, line=fIn.linenumber()) raise CogError("Unexpected '%s'" % self.options.sEndSpec, file=sFileIn, line=fIn.linenumber())
# We reached end of file before we found the end output line. raise CogError("Missing '%s' before end of file." % self.options.sEndOutput, file=sFileIn, line=fIn.linenumber())
# Make the previous output available to the current code
# Write the output of the spec to be the new output if we're # supposed to generate code.
# Write the ending output line if hashMatch: oldHash = hashMatch.groupdict()['hash'] if oldHash != curHash: raise CogError("Output has been edited! Delete old checksum to unprotect.", file=sFileIn, line=fIn.linenumber()) # Create a new end line with the correct hash. endpieces = l.split(hashMatch.group(0), 1) else: # There was no old hash, but we want a new hash. endpieces = l.split(self.options.sEndOutput, 1) l = (self.sEndFormat % newHash).join(endpieces) else: # We don't want hashes output, so if there was one, get rid of # it. l = l.replace(hashMatch.groupdict()['hashsect'], '', 1)
self.showWarning("no cog code found in %s" % sFileIn) finally: fInToClose.close() fOutToClose.close()
# A regex for non-empty lines, used by suffixLines.
""" Add suffixes to the lines in text, if our options desire it. text is many lines, as a single string. """ # Find all non-blank lines, and add the suffix to the end. repl = r"\g<0>" + self.options.sSuffix.replace('\\', '\\\\') text = self.reNonEmptyLines.sub(repl, text)
""" Process sInput as the text to cog. Return the cogged output as a string. """
""" Replace file sOldPath with the contents sNewText """ if not os.access(sOldPath, os.W_OK): # Need to ensure we can write. if self.options.sMakeWritableCmd: # Use an external command to make the file writable. cmd = self.options.sMakeWritableCmd.replace('%s', sOldPath) self.stdout.write(os.popen(cmd).read()) if not os.access(sOldPath, os.W_OK): raise CogError("Couldn't make %s writable" % sOldPath) else: # Can't write! raise CogError("Can't overwrite %s" % sOldPath) f = self.openOutputFile(sOldPath) f.write(sNewText) f.close()
self.savedInclude = self.options.includePath[:] self.savedSysPath = sys.path[:]
self.options.includePath = self.savedInclude self.cogmodule.path = self.options.includePath sys.path = self.savedSysPath
self.cogmodule.path.extend(includePath) sys.path.extend(includePath)
""" Process one filename through cog. """
self.saveIncludePath() bNeedNewline = False
try: self.addToIncludePath(self.options.includePath) # Since we know where the input file came from, # push its directory onto the include path. self.addToIncludePath([os.path.dirname(sFile)])
# How we process the file depends on where the output is going. if self.options.sOutputName: self.processFile(sFile, self.options.sOutputName, sFile) elif self.options.bReplace: # We want to replace the cog file with the output, # but only if they differ. if self.options.verbosity >= 2: self.prout("Cogging %s" % sFile, end="") bNeedNewline = True
try: fOldFile = self.openInputFile(sFile) sOldText = fOldFile.read() fOldFile.close() sNewText = self.processString(sOldText, fname=sFile) if sOldText != sNewText: if self.options.verbosity >= 1: if self.options.verbosity < 2: self.prout("Cogging %s" % sFile, end="") self.prout(" (changed)") bNeedNewline = False self.replaceFile(sFile, sNewText) finally: # The try-finally block is so we can print a partial line # with the name of the file, and print (changed) on the # same line, but also make sure to break the line before # any traceback. if bNeedNewline: self.prout("") else: self.processFile(sFile, self.stdout, sFile) finally: self.restoreIncludePath()
""" Process the files in a file list. """ flist = self.openInputFile(sFileList) lines = flist.readlines() flist.close() for l in lines: # Use shlex to parse the line like a shell. lex = shlex.shlex(l, posix=True) lex.whitespace_split = True lex.commenters = '#' # No escapes, so that backslash can be part of the path lex.escape = '' args = list(lex) if args: self.processArguments(args)
""" Process one command-line. """ saved_options = self.options self.options = self.options.clone()
self.options.parseArgs(args[1:]) self.options.validate()
if args[0][0] == '@': if self.options.sOutputName: raise CogUsageError("Can't use -o with @file") self.processFileList(args[0][1:]) else: self.processOneFile(args[0])
self.options = saved_options
""" All of command-line cog, but in a callable form. This is used by main. argv is the equivalent of sys.argv. """ argv = argv[1:]
# Provide help if asked for anywhere in the command line. if '-?' in argv or '-h' in argv: self.prerr(usage, end="") return
self.options.parseArgs(argv) self.options.validate() self._fixEndOutputPatterns()
if self.options.bShowVersion: self.prout("Cog version %s" % __version__) return
if self.options.args: for a in self.options.args: self.processArguments([a]) else: raise CogUsageError("No files to process")
""" Handle the command-line execution for cog. """
try: self.callableMain(argv) return 0 except CogUsageError as err: self.prerr(err) self.prerr("(for help use -?)") return 2 except CogGeneratedError as err: self.prerr("Error: %s" % err) return 3 except CogError as err: self.prerr(err) return 1 except: traceback.print_exc(None, self.stderr) return 1
# History: # 20040210: First public version. # 20040220: Text preceding the start and end marker are removed from Python lines. # -v option on the command line shows the version. # 20040311: Make sure the last line of output is properly ended with a newline. # 20040605: Fixed some blank line handling in cog. # Fixed problems with assigning to xml elements in handyxml. # 20040621: Changed all line-ends to LF from CRLF. # 20041002: Refactor some option handling to simplify unittesting the options. # 20041118: cog.out and cog.outl have optional string arguments. # 20041119: File names weren't being properly passed around for warnings, etc. # 20041122: Added cog.firstLineNum: a property with the line number of the [[[cog line. # Added cog.inFile and cog.outFile: the names of the input and output file. # 20041218: Single-line cog generators, with start marker and end marker on # the same line. # 20041230: Keep a single globals dict for all the code fragments in a single # file so they can share state. # 20050206: Added the -x switch to remove all generated output. # 20050218: Now code can be on the marker lines as well. # 20050219: Added -c switch to checksum the output so that edits can be # detected before they are obliterated. # 20050521: Added cog.error, contributed by Alexander Belchenko. # 20050720: Added code deletion and settable globals contributed by Blake Winton. # 20050724: Many tweaks to improve code coverage. # 20050726: Error messages are now printed with no traceback. # Code can no longer appear on the marker lines, # except for single-line style. # -z allows omission of the [[[end]]] marker, and it will be assumed # at the end of the file. # 20050729: Refactor option parsing into a separate class, in preparation for # future features. # 20050805: The cogmodule.path wasn't being properly maintained. # 20050808: Added the -D option to define a global value. # 20050810: The %s in the -w command is dealt with more robustly. # Added the -s option to suffix output lines with a marker. # 20050817: Now @files can have arguments on each line to change the cog's # behavior for that line. # 20051006: Version 2.0 # 20080521: -U options lets you create Unix newlines on Windows. Thanks, # Alexander Belchenko. # 20080522: It's now ok to have -d with output to stdout, and now we validate # the args after each line of an @file. # 20090520: Use hashlib where it's available, to avoid a warning. # Use the builtin compile() instead of compiler, for Jython. # Explicitly close files we opened, Jython likes this. # 20120205: Port to Python 3. Lowest supported version is 2.6. # 20150104: --markers option added by Doug Hellmann. # 20150104: -n ENCODING option added by Petr Gladkiy. # 20150107: Added --verbose to control what files get listed. # 20150111: Version 2.4 # 20160213: v2.5: -o makes needed directories, thanks Jean-François Giraud. # 20161019: Added a LICENSE.txt file. |