123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- #!/usr/bin/python
- from __future__ import print_function
- from __future__ import with_statement
- import sys
- import re
- import os
- KNOWN_GROUPS = set([
- "Minor bugfix",
- "Minor bugfixes",
- "Major bugfix",
- "Major bugfixes",
- "Minor feature",
- "Minor features",
- "Major feature",
- "Major features",
- "New system requirements",
- "Testing",
- "Documentation",
- "Code simplification and refactoring",
- "Removed features",
- "Deprecated features",
- "Directory authority changes"])
- NEEDS_SUBCATEGORIES = set([
- "Minor bugfix",
- "Minor bugfixes",
- "Major bugfix",
- "Major bugfixes",
- "Minor feature",
- "Minor features",
- "Major feature",
- "Major features",
- ])
- def lintfile(fname):
- have_warned = []
- def warn(s):
- if not have_warned:
- have_warned.append(1)
- print("{}:".format(fname))
- print("\t{}".format(s))
- m = re.search(r'(\d{3,})', os.path.basename(fname))
- if m:
- bugnum = m.group(1)
- else:
- bugnum = None
- with open(fname) as f:
- contents = f.read()
- if bugnum and bugnum not in contents:
- warn("bug number {} does not appear".format(bugnum))
- m = re.match(r'^[ ]{2}o ([^\(:]*)([^:]*):', contents)
- if not m:
- warn("Header not in format expected. (' o Foo:' or ' o Foo (Bar):')")
- elif m.group(1).strip() not in KNOWN_GROUPS:
- warn("Unrecognized header: %r" % m.group(1))
- elif (m.group(1) in NEEDS_SUBCATEGORIES and '(' not in m.group(2)):
- warn("Missing subcategory on %r" % m.group(1))
- if m:
- isBug = ("bug" in m.group(1).lower() or "fix" in m.group(1).lower())
- else:
- isBug = False
- contents = " ".join(contents.split())
- if re.search(r'\#\d{2,}', contents):
- warn("Don't use a # before ticket numbers. ('bug 1234' not '#1234')")
- if isBug and not re.search(r'(\d+)', contents):
- warn("Ticket marked as bugfix, but does not mention a number.")
- elif isBug and not re.search(r'Fixes ([a-z ]*)bugs? (\d+)', contents):
- warn("Ticket marked as bugfix, but does not say 'Fixes bug XXX'")
- if re.search(r'[bB]ug (\d+)', contents):
- if not re.search(r'[Bb]ugfix on ', contents):
- warn("Bugfix does not say 'bugfix on X.Y.Z'")
- elif not re.search('[fF]ixes ([a-z ]*)bugs? (\d+)((, \d+)* and \d+)?; bugfix on ',
- contents):
- warn("Bugfix does not say 'Fixes bug X; bugfix on Y'")
- elif re.search('tor-([0-9]+)', contents):
- warn("Do not prefix versions with 'tor-'. ('0.1.2', not 'tor-0.1.2'.)")
- return have_warned != []
- def files(args):
- """Walk through the arguments: for directories, yield their contents;
- for files, just yield the files. Only search one level deep, because
- that's how the changes directory is laid out."""
- for f in args:
- if os.path.isdir(f):
- for item in os.listdir(f):
- if item.startswith("."): #ignore dotfiles
- continue
- yield os.path.join(f, item)
- else:
- yield f
- if __name__ == '__main__':
- problems = 0
- for fname in files(sys.argv[1:]):
- if fname.endswith("~"):
- continue
- if lintfile(fname):
- problems += 1
- if problems:
- sys.exit(1)
- else:
- sys.exit(0)
|