diff options
Diffstat (limited to 'scintilla/scripts/FileGenerator.py')
-rw-r--r-- | scintilla/scripts/FileGenerator.py | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/scintilla/scripts/FileGenerator.py b/scintilla/scripts/FileGenerator.py new file mode 100644 index 0000000..01a79bf --- /dev/null +++ b/scintilla/scripts/FileGenerator.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python +# FileGenerator.py - implemented 2013 by Neil Hodgson neilh@scintilla.org +# 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) |