format_changelog.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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 re
  12. import sys
  13. import textwrap
  14. TP_MAINHEAD = 0
  15. TP_HEADTEXT = 1
  16. TP_BLANK = 2
  17. TP_SECHEAD = 3
  18. TP_ITEMFIRST = 4
  19. TP_ITEMBODY = 5
  20. def head_parser(line):
  21. if re.match(r'^[A-Z]', line):
  22. return TP_MAINHEAD
  23. elif re.match(r'^ o ', line):
  24. return TP_SECHEAD
  25. elif re.match(r'^\s*$', line):
  26. return TP_BLANK
  27. else:
  28. return TP_HEADTEXT
  29. def body_parser(line):
  30. if re.match(r'^ o ', line):
  31. return TP_SECHEAD
  32. elif re.match(r'^ -',line):
  33. return TP_ITEMFIRST
  34. elif re.match(r'^ \S', line):
  35. return TP_ITEMBODY
  36. elif re.match(r'^\s*$', line):
  37. return TP_BLANK
  38. else:
  39. print "Weird line %r"%line
  40. class ChangeLog(object):
  41. def __init__(self):
  42. self.mainhead = None
  43. self.headtext = []
  44. self.curgraf = None
  45. self.sections = []
  46. self.cursection = None
  47. self.lineno = 0
  48. def addLine(self, tp, line):
  49. self.lineno += 1
  50. if tp == TP_MAINHEAD:
  51. assert not self.mainhead
  52. self.mainhead = line
  53. elif tp == TP_HEADTEXT:
  54. if self.curgraf is None:
  55. self.curgraf = []
  56. self.headtext.append(self.curgraf)
  57. self.curgraf.append(line)
  58. elif tp == TP_BLANK:
  59. self.curgraf = None
  60. elif tp == TP_SECHEAD:
  61. self.cursection = [ self.lineno, line, [] ]
  62. self.sections.append(self.cursection)
  63. elif tp == TP_ITEMFIRST:
  64. item = ( self.lineno, [ [line] ])
  65. self.curgraf = item[1][0]
  66. self.cursection[2].append(item)
  67. elif tp == TP_ITEMBODY:
  68. if self.curgraf is None:
  69. self.curgraf = []
  70. self.cursection[2][1][-1].append(self.curgraf)
  71. self.curgraf.append(line)
  72. else:
  73. assert "This" is "unreachable"
  74. def lint_head(self, line, head):
  75. m = re.match(r'^ *o ([^\(]+)((?:\([^\)]+\))?):', head)
  76. if not m:
  77. print >>sys.stderr, "Weird header format on line %s"%line
  78. def lint_item(self, line, grafs, head_type):
  79. pass
  80. def lint(self):
  81. self.head_lines = {}
  82. for sec_line, sec_head, items in self.sections:
  83. head_type = self.lint_head(sec_line, sec_head)
  84. for item_line, grafs in items:
  85. self.lint_item(item_line, grafs, head_type)
  86. def dumpGraf(self,par,indent1,indent2=-1):
  87. if indent2 == -1:
  88. indent2 = indent1
  89. text = " ".join(re.sub(r'\s+', ' ', line.strip()) for line in par)
  90. print textwrap.fill(text, width=72,
  91. initial_indent=" "*indent1,
  92. subsequent_indent=" "*indent2)
  93. def dump(self):
  94. print self.mainhead
  95. for par in self.headtext:
  96. self.dumpGraf(par, 2)
  97. print
  98. for _,head,items in self.sections:
  99. if not head.endswith(':'):
  100. print >>sys.stderr, "adding : to %r"%head
  101. head = head + ":"
  102. print head
  103. for _,grafs in items:
  104. self.dumpGraf(grafs[0],4,6)
  105. for par in grafs[1:]:
  106. print
  107. self.dumpGraf(par,6,6)
  108. print
  109. print
  110. CL = ChangeLog()
  111. parser = head_parser
  112. for line in sys.stdin:
  113. line = line.rstrip()
  114. tp = parser(line)
  115. CL.addLine(tp,line)
  116. if tp == TP_SECHEAD:
  117. parser = body_parser
  118. CL.lint()
  119. CL.dump()