notepad-plus-plus/scintilla/scripts/FileGenerator.py

159 lines
6.5 KiB
Python
Raw Normal View History

#!/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)