format_changelog.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. #!/usr/bin/python
  2. # Copyright (c) 2014, The Tor Project, Inc.
  3. # See LICENSE for licensing information
  4. #
  5. # This script reformats a section of the changelog to wrap everything to
  6. # the right width and put blank lines in the right places. Eventually,
  7. # it might include a linter.
  8. #
  9. # To run it, pipe a section of the changelog (starting with "Changes
  10. # in Tor 0.x.y.z-alpha" through the script.)
  11. import os
  12. import re
  13. import sys
  14. import textwrap
  15. TP_MAINHEAD = 0
  16. TP_HEADTEXT = 1
  17. TP_BLANK = 2
  18. TP_SECHEAD = 3
  19. TP_ITEMFIRST = 4
  20. TP_ITEMBODY = 5
  21. TP_END = 6
  22. def head_parser(line):
  23. if re.match(r'^[A-Z]', line):
  24. return TP_MAINHEAD
  25. elif re.match(r'^ o ', line):
  26. return TP_SECHEAD
  27. elif re.match(r'^\s*$', line):
  28. return TP_BLANK
  29. else:
  30. return TP_HEADTEXT
  31. def body_parser(line):
  32. if re.match(r'^ o ', line):
  33. return TP_SECHEAD
  34. elif re.match(r'^ -',line):
  35. return TP_ITEMFIRST
  36. elif re.match(r'^ \S', line):
  37. return TP_ITEMBODY
  38. elif re.match(r'^\s*$', line):
  39. return TP_BLANK
  40. elif re.match(r'^Changes in', line):
  41. return TP_END
  42. else:
  43. print "Weird line %r"%line
  44. class ChangeLog(object):
  45. def __init__(self):
  46. self.mainhead = None
  47. self.headtext = []
  48. self.curgraf = None
  49. self.sections = []
  50. self.cursection = None
  51. self.lineno = 0
  52. def addLine(self, tp, line):
  53. self.lineno += 1
  54. if tp == TP_MAINHEAD:
  55. assert not self.mainhead
  56. self.mainhead = line
  57. elif tp == TP_HEADTEXT:
  58. if self.curgraf is None:
  59. self.curgraf = []
  60. self.headtext.append(self.curgraf)
  61. self.curgraf.append(line)
  62. elif tp == TP_BLANK:
  63. self.curgraf = None
  64. elif tp == TP_SECHEAD:
  65. self.cursection = [ self.lineno, line, [] ]
  66. self.sections.append(self.cursection)
  67. elif tp == TP_ITEMFIRST:
  68. item = ( self.lineno, [ [line] ])
  69. self.curgraf = item[1][0]
  70. self.cursection[2].append(item)
  71. elif tp == TP_ITEMBODY:
  72. if self.curgraf is None:
  73. self.curgraf = []
  74. self.cursection[2][1][-1].append(self.curgraf)
  75. self.curgraf.append(line)
  76. else:
  77. assert "This" is "unreachable"
  78. def lint_head(self, line, head):
  79. m = re.match(r'^ *o ([^\(]+)((?:\([^\)]+\))?):', head)
  80. if not m:
  81. print >>sys.stderr, "Weird header format on line %s"%line
  82. def lint_item(self, line, grafs, head_type):
  83. pass
  84. def lint(self):
  85. self.head_lines = {}
  86. for sec_line, sec_head, items in self.sections:
  87. head_type = self.lint_head(sec_line, sec_head)
  88. for item_line, grafs in items:
  89. self.lint_item(item_line, grafs, head_type)
  90. def dumpGraf(self,par,indent1,indent2=-1):
  91. if indent2 == -1:
  92. indent2 = indent1
  93. text = " ".join(re.sub(r'\s+', ' ', line.strip()) for line in par)
  94. print textwrap.fill(text, width=72,
  95. initial_indent=" "*indent1,
  96. subsequent_indent=" "*indent2,
  97. break_on_hyphens=False)
  98. def dump(self):
  99. print self.mainhead
  100. for par in self.headtext:
  101. self.dumpGraf(par, 2)
  102. print
  103. for _,head,items in self.sections:
  104. if not head.endswith(':'):
  105. print >>sys.stderr, "adding : to %r"%head
  106. head = head + ":"
  107. print head
  108. for _,grafs in items:
  109. self.dumpGraf(grafs[0],4,6)
  110. for par in grafs[1:]:
  111. print
  112. self.dumpGraf(par,6,6)
  113. print
  114. print
  115. CL = ChangeLog()
  116. parser = head_parser
  117. sys.stdin = open('ChangeLog', 'r')
  118. for line in sys.stdin:
  119. line = line.rstrip()
  120. tp = parser(line)
  121. if tp == TP_SECHEAD:
  122. parser = body_parser
  123. elif tp == TP_END:
  124. nextline = line
  125. break
  126. CL.addLine(tp,line)
  127. CL.lint()
  128. sys.stdout = open('ChangeLog.new', 'w')
  129. CL.dump()
  130. print nextline
  131. for line in sys.stdin:
  132. sys.stdout.write(line)
  133. os.rename('ChangeLog.new', 'ChangeLog')