| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- #!/usr/bin/env python
- # FileGenerator.py - implemented 2013 by Neil Hodgson [email protected]
- # Released to the public domain.
- # Generate or regenerate source files based on comments in those files.
- # May be modified in-place or a template may be generated into a complete file.
- # Requires Python 2.5 or later
- # The files are copied to a string apart from sections between a
- # ++Autogenerated comment and a --Autogenerated comment which is
- # generated by the CopyWithInsertion function. After the whole string is
- # instantiated, it is compared with the target file and if different the file
- # is rewritten.
- from __future__ import with_statement
- import codecs, os, re, string, sys
- lineEnd = "\r\n" if sys.platform == "win32" else "\n"
- def UpdateFile(filename, updated):
- """ If the file contents are different to updated then copy updated into the
- file else leave alone so Mercurial and make don't treat it as modified. """
- newOrChanged = "Changed"
- try:
- with codecs.open(filename, "r", "utf-8") as infile:
- original = infile.read()
- if updated == original:
- # Same as before so don't write
- return
- os.unlink(filename)
- except IOError: # File is not there yet
- newOrChanged = "New"
- with codecs.open(filename, "w", "utf-8") as outfile:
- outfile.write(updated)
- print("%s %s" % (newOrChanged, filename))
- # Automatically generated sections contain start and end comments,
- # a definition line and the results.
- # The results are replaced by regenerating based on the definition line.
- # The definition line is a comment prefix followed by "**".
- # If there is a digit after the ** then this indicates which list to use
- # and the digit and next character are not part of the definition
- # Backslash is used as an escape within the definition line.
- # The part between \( and \) is repeated for each item in the list.
- # \* is replaced by each list item. \t, and \n are tab and newline.
- # If there is no definition line than the first list is copied verbatim.
- # If retainDefs then the comments controlling generation are copied.
- def CopyWithInsertion(input, commentPrefix, retainDefs, lists):
- copying = 1
- generated = False
- listid = 0
- output = []
- for line in input.splitlines(0):
- isStartGenerated = line.lstrip().startswith(commentPrefix + "++Autogenerated")
- if copying and not isStartGenerated:
- output.append(line)
- if isStartGenerated:
- if retainDefs:
- output.append(line)
- copying = 0
- generated = False
- elif not copying and not generated:
- # Generating
- if line.startswith(commentPrefix + "**"):
- # Pattern to transform input data
- if retainDefs:
- output.append(line)
- definition = line[len(commentPrefix + "**"):]
- if (commentPrefix == "<!--") and (" -->" in definition):
- definition = definition.replace(" -->", "")
- listid = 0
- if definition[0] in string.digits:
- listid = int(definition[:1])
- definition = definition[2:]
- # Hide double slashes as a control character
- definition = definition.replace("\\\\", "\001")
- # Do some normal C style transforms
- definition = definition.replace("\\n", "\n")
- definition = definition.replace("\\t", "\t")
- # Get the doubled backslashes back as single backslashes
- definition = definition.replace("\001", "\\")
- startRepeat = definition.find("\\(")
- endRepeat = definition.find("\\)")
- intro = definition[:startRepeat]
- out = ""
- if intro.endswith("\n"):
- pos = 0
- else:
- pos = len(intro)
- out += intro
- middle = definition[startRepeat+2:endRepeat]
- for i in lists[listid]:
- item = middle.replace("\\*", i)
- if pos and (pos + len(item) >= 80):
- out += "\\\n"
- pos = 0
- out += item
- pos += len(item)
- if item.endswith("\n"):
- pos = 0
- outro = definition[endRepeat+2:]
- out += outro
- out = out.replace("\n", lineEnd) # correct EOLs in generated content
- output.append(out)
- else:
- # Simple form with no rule to transform input
- output.extend(lists[0])
- generated = True
- if line.lstrip().startswith(commentPrefix + "--Autogenerated") or \
- line.lstrip().startswith(commentPrefix + "~~Autogenerated"):
- copying = 1
- if retainDefs:
- output.append(line)
- output = [line.rstrip(" \t") for line in output] # trim trailing whitespace
- return lineEnd.join(output) + lineEnd
- def GenerateFile(inpath, outpath, commentPrefix, retainDefs, *lists):
- """Generate 'outpath' from 'inpath'.
- """
- try:
- with codecs.open(inpath, "r", "UTF-8") as infile:
- original = infile.read()
- updated = CopyWithInsertion(original, commentPrefix,
- retainDefs, lists)
- UpdateFile(outpath, updated)
- except IOError:
- print("Can not open %s" % inpath)
- def Generate(inpath, outpath, commentPrefix, *lists):
- """Generate 'outpath' from 'inpath'.
- """
- GenerateFile(inpath, outpath, commentPrefix, inpath == outpath, *lists)
- def Regenerate(filename, commentPrefix, *lists):
- """Regenerate the given file.
- """
- Generate(filename, filename, commentPrefix, *lists)
- def UpdateLineInFile(path, linePrefix, lineReplace):
- lines = []
- updated = False
- with codecs.open(path, "r", "utf-8") as f:
- for l in f.readlines():
- l = l.rstrip()
- if not updated and l.startswith(linePrefix):
- lines.append(lineReplace)
- updated = True
- else:
- lines.append(l)
- contents = lineEnd.join(lines) + lineEnd
- UpdateFile(path, contents)
- def ReplaceREInFile(path, match, replace):
- with codecs.open(path, "r", "utf-8") as f:
- contents = f.read()
- contents = re.sub(match, replace, contents)
- UpdateFile(path, contents)
|