summaryrefslogtreecommitdiffstats
path: root/scintilla/scripts/FileGenerator.py
blob: e2750ebe0fdad084681151f543f6f3f4bb3bd1e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#!/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 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)
    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)