doc: Clarify the release process for a first stable
[tor.git] / scripts / maint / sortChanges.py
blob5f6324e3878b185a09e6ef9cfbe160a26dd9ba17
1 #!/usr/bin/env python
2 # Copyright (c) 2014-2019, The Tor Project, Inc.
3 # See LICENSE for licensing information
5 """This script sorts a bunch of changes files listed on its command
6 line into roughly the order in which they should appear in the
7 changelog.
8 """
10 # Future imports for Python 2.7, mandatory in 3.0
11 from __future__ import division
12 from __future__ import print_function
13 from __future__ import unicode_literals
15 import re
16 import sys
18 def fetch(fn):
19 with open(fn) as f:
20 s = f.read()
21 s = "%s\n" % s.rstrip()
22 return s
24 CSR='Code simplification and refactoring'
26 REPLACEMENTS = {
27 # plurals
28 'Minor bugfix' : 'Minor bugfixes',
29 'Major bugfix' : 'Major bugfixes',
30 'Minor feature' : 'Minor features',
31 'Major feature' : 'Major features',
32 'Removed feature' : 'Removed features',
33 'Code simplification and refactorings' : CSR,
34 'Code simplifications and refactoring' : CSR,
35 'Code simplifications and refactorings' : CSR,
37 # wrong words
38 'Minor fix' : 'Minor bugfixes',
39 'Major fix' : 'Major bugfixes',
40 'Minor fixes' : 'Minor bugfixes',
41 'Major fixes' : 'Major bugfixes',
42 'Minor enhancement' : 'Minor features',
43 'Minor enhancements' : 'Minor features',
44 'Major enhancement' : 'Major features',
45 'Major enhancements' : 'Major features',
48 def score(s,fname=None):
49 m = re.match(r'^ +o ([^\n]*)\n(.*)', s, re.M|re.S)
50 if not m:
51 print("Can't score %r from %s"%(s,fname), file=sys.stderr)
52 heading = m.group(1)
53 heading = REPLACEMENTS.get(heading, heading)
54 lw = m.group(1).lower()
55 if lw.startswith("major feature"):
56 score = 0
57 elif lw.startswith("major bug"):
58 score = 1
59 elif lw.startswith("major"):
60 score = 2
61 elif lw.startswith("minor feature"):
62 score = 10
63 elif lw.startswith("minor bug"):
64 score = 11
65 elif lw.startswith("minor"):
66 score = 12
67 else:
68 score = 100
70 return (score, lw, heading, m.group(2))
72 def splitChanges(s):
73 this_entry = []
74 for line in s.split("\n"):
75 if line.strip() == "":
76 continue
77 if re.match(r" +o ", line):
78 if len(this_entry) > 2:
79 yield "".join(this_entry)
80 curHeader = line
81 this_entry = [ curHeader, "\n" ]
82 continue
83 elif re.match(r" +- ", line):
84 if len(this_entry) > 2:
85 yield "".join(this_entry)
86 this_entry = [ curHeader, "\n" ]
88 this_entry.append(line)
89 this_entry.append("\n")
91 if len(this_entry) > 2:
92 yield "".join(this_entry)
95 changes = []
97 for fn in sys.argv[1:]:
98 if fn.endswith('~'):
99 continue
100 for change in splitChanges(fetch(fn)):
101 changes.append(score(change,fn))
103 changes.sort()
105 last_lw = "this is not a header"
106 for _, lw, header, rest in changes:
107 if lw == last_lw:
108 print(rest, end="")
109 else:
110 print()
111 print(" o",header)
112 print(rest, end="")
113 last_lw = lw