mirror of
				https://github.com/notepad-plus-plus/notepad-plus-plus.git
				synced 2025-11-04 05:23:56 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			224 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			224 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#!/usr/bin/env python3
 | 
						|
# 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.7 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:0: %s" % (filename, newOrChanged))
 | 
						|
 | 
						|
# 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 UpdateLineInPlistFile(path, key, value):
 | 
						|
    """Replace a single string value preceded by 'key' in an XML plist file.
 | 
						|
    """
 | 
						|
    lines = []
 | 
						|
    keyCurrent = ""
 | 
						|
    with codecs.open(path, "rb", "utf-8") as f:
 | 
						|
        for l in f.readlines():
 | 
						|
            ls = l.strip()
 | 
						|
            if ls.startswith("<key>"):
 | 
						|
                keyCurrent = ls.replace("<key>", "").replace("</key>", "")
 | 
						|
            elif ls.startswith("<string>"):
 | 
						|
                if keyCurrent == key:
 | 
						|
                    start, tag, rest = l.partition("<string>")
 | 
						|
                    _val, etag, end = rest.partition("</string>")
 | 
						|
                    l = start + tag + value + etag + end
 | 
						|
            lines.append(l)
 | 
						|
    contents = "".join(lines)
 | 
						|
    UpdateFile(path, contents)
 | 
						|
 | 
						|
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)
 | 
						|
    if not updated:
 | 
						|
        print(f"{path}:0: Can't find '{linePrefix}'")
 | 
						|
    contents = lineEnd.join(lines) + lineEnd
 | 
						|
    UpdateFile(path, contents)
 | 
						|
 | 
						|
def ReadFileAsList(path):
 | 
						|
    """Read all the lnes in the file and return as a list of strings without line ends.
 | 
						|
    """
 | 
						|
    with codecs.open(path, "r", "utf-8") as f:
 | 
						|
        return [l.rstrip('\n') for l in f]
 | 
						|
 | 
						|
def UpdateFileFromLines(path, lines, lineEndToUse):
 | 
						|
    """Join the lines with the lineEndToUse then update file if the result is different.
 | 
						|
    """
 | 
						|
    contents = lineEndToUse.join(lines) + lineEndToUse
 | 
						|
    UpdateFile(path, contents)
 | 
						|
 | 
						|
def FindSectionInList(lines, markers):
 | 
						|
    """Find a section defined by an initial start marker, an optional secondary
 | 
						|
    marker and an end marker.
 | 
						|
    The section is between the secondary/initial start and the end.
 | 
						|
    Report as a slice object so the section can be extracted or replaced.
 | 
						|
    Raises an exception if the markers can't be found.
 | 
						|
    """
 | 
						|
    start = -1
 | 
						|
    end = -1
 | 
						|
    state = 0
 | 
						|
    for i, l in enumerate(lines):
 | 
						|
        if markers[0] in l:
 | 
						|
            if markers[1]:
 | 
						|
                state = 1
 | 
						|
            else:
 | 
						|
                start = i+1
 | 
						|
                state = 2
 | 
						|
        elif state == 1:
 | 
						|
            if markers[1] in l:
 | 
						|
                start = i+1
 | 
						|
                state = 2
 | 
						|
        elif state == 2:
 | 
						|
            if markers[2] in l:
 | 
						|
                end = i
 | 
						|
                state = 3
 | 
						|
    # Check that section was found
 | 
						|
    if start == -1:
 | 
						|
        raise Exception("Could not find start marker(s) |" + markers[0] + "|" + markers[1] + "|")
 | 
						|
    if end == -1:
 | 
						|
        raise Exception("Could not find end marker " + markers[2])
 | 
						|
    return slice(start, end)
 | 
						|
 | 
						|
def ReplaceREInFile(path, match, replace, count=1):
 | 
						|
    with codecs.open(path, "r", "utf-8") as f:
 | 
						|
        contents = f.read()
 | 
						|
    contents = re.sub(match, replace, contents, count)
 | 
						|
    UpdateFile(path, contents)
 |