FileGenerator.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. #!/usr/bin/env python
  2. # FileGenerator.py - implemented 2013 by Neil Hodgson [email protected]
  3. # Released to the public domain.
  4. # Generate or regenerate source files based on comments in those files.
  5. # May be modified in-place or a template may be generated into a complete file.
  6. # Requires Python 2.5 or later
  7. # The files are copied to a string apart from sections between a
  8. # ++Autogenerated comment and a --Autogenerated comment which is
  9. # generated by the CopyWithInsertion function. After the whole string is
  10. # instantiated, it is compared with the target file and if different the file
  11. # is rewritten.
  12. from __future__ import with_statement
  13. import codecs, os, re, string, sys
  14. lineEnd = "\r\n" if sys.platform == "win32" else "\n"
  15. def UpdateFile(filename, updated):
  16. """ If the file contents are different to updated then copy updated into the
  17. file else leave alone so Mercurial and make don't treat it as modified. """
  18. newOrChanged = "Changed"
  19. try:
  20. with codecs.open(filename, "r", "utf-8") as infile:
  21. original = infile.read()
  22. if updated == original:
  23. # Same as before so don't write
  24. return
  25. os.unlink(filename)
  26. except IOError: # File is not there yet
  27. newOrChanged = "New"
  28. with codecs.open(filename, "w", "utf-8") as outfile:
  29. outfile.write(updated)
  30. print("%s %s" % (newOrChanged, filename))
  31. # Automatically generated sections contain start and end comments,
  32. # a definition line and the results.
  33. # The results are replaced by regenerating based on the definition line.
  34. # The definition line is a comment prefix followed by "**".
  35. # If there is a digit after the ** then this indicates which list to use
  36. # and the digit and next character are not part of the definition
  37. # Backslash is used as an escape within the definition line.
  38. # The part between \( and \) is repeated for each item in the list.
  39. # \* is replaced by each list item. \t, and \n are tab and newline.
  40. # If there is no definition line than the first list is copied verbatim.
  41. # If retainDefs then the comments controlling generation are copied.
  42. def CopyWithInsertion(input, commentPrefix, retainDefs, lists):
  43. copying = 1
  44. generated = False
  45. listid = 0
  46. output = []
  47. for line in input.splitlines(0):
  48. isStartGenerated = line.lstrip().startswith(commentPrefix + "++Autogenerated")
  49. if copying and not isStartGenerated:
  50. output.append(line)
  51. if isStartGenerated:
  52. if retainDefs:
  53. output.append(line)
  54. copying = 0
  55. generated = False
  56. elif not copying and not generated:
  57. # Generating
  58. if line.startswith(commentPrefix + "**"):
  59. # Pattern to transform input data
  60. if retainDefs:
  61. output.append(line)
  62. definition = line[len(commentPrefix + "**"):]
  63. if (commentPrefix == "<!--") and (" -->" in definition):
  64. definition = definition.replace(" -->", "")
  65. listid = 0
  66. if definition[0] in string.digits:
  67. listid = int(definition[:1])
  68. definition = definition[2:]
  69. # Hide double slashes as a control character
  70. definition = definition.replace("\\\\", "\001")
  71. # Do some normal C style transforms
  72. definition = definition.replace("\\n", "\n")
  73. definition = definition.replace("\\t", "\t")
  74. # Get the doubled backslashes back as single backslashes
  75. definition = definition.replace("\001", "\\")
  76. startRepeat = definition.find("\\(")
  77. endRepeat = definition.find("\\)")
  78. intro = definition[:startRepeat]
  79. out = ""
  80. if intro.endswith("\n"):
  81. pos = 0
  82. else:
  83. pos = len(intro)
  84. out += intro
  85. middle = definition[startRepeat+2:endRepeat]
  86. for i in lists[listid]:
  87. item = middle.replace("\\*", i)
  88. if pos and (pos + len(item) >= 80):
  89. out += "\\\n"
  90. pos = 0
  91. out += item
  92. pos += len(item)
  93. if item.endswith("\n"):
  94. pos = 0
  95. outro = definition[endRepeat+2:]
  96. out += outro
  97. out = out.replace("\n", lineEnd) # correct EOLs in generated content
  98. output.append(out)
  99. else:
  100. # Simple form with no rule to transform input
  101. output.extend(lists[0])
  102. generated = True
  103. if line.lstrip().startswith(commentPrefix + "--Autogenerated") or \
  104. line.lstrip().startswith(commentPrefix + "~~Autogenerated"):
  105. copying = 1
  106. if retainDefs:
  107. output.append(line)
  108. output = [line.rstrip(" \t") for line in output] # trim trailing whitespace
  109. return lineEnd.join(output) + lineEnd
  110. def GenerateFile(inpath, outpath, commentPrefix, retainDefs, *lists):
  111. """Generate 'outpath' from 'inpath'.
  112. """
  113. try:
  114. with codecs.open(inpath, "r", "UTF-8") as infile:
  115. original = infile.read()
  116. updated = CopyWithInsertion(original, commentPrefix,
  117. retainDefs, lists)
  118. UpdateFile(outpath, updated)
  119. except IOError:
  120. print("Can not open %s" % inpath)
  121. def Generate(inpath, outpath, commentPrefix, *lists):
  122. """Generate 'outpath' from 'inpath'.
  123. """
  124. GenerateFile(inpath, outpath, commentPrefix, inpath == outpath, *lists)
  125. def Regenerate(filename, commentPrefix, *lists):
  126. """Regenerate the given file.
  127. """
  128. Generate(filename, filename, commentPrefix, *lists)
  129. def UpdateLineInFile(path, linePrefix, lineReplace):
  130. lines = []
  131. updated = False
  132. with codecs.open(path, "r", "utf-8") as f:
  133. for l in f.readlines():
  134. l = l.rstrip()
  135. if not updated and l.startswith(linePrefix):
  136. lines.append(lineReplace)
  137. updated = True
  138. else:
  139. lines.append(l)
  140. contents = lineEnd.join(lines) + lineEnd
  141. UpdateFile(path, contents)
  142. def ReplaceREInFile(path, match, replace):
  143. with codecs.open(path, "r", "utf-8") as f:
  144. contents = f.read()
  145. contents = re.sub(match, replace, contents)
  146. UpdateFile(path, contents)