| #!/usr/bin/python |
| # |
| |
| # Copyright (C) 2011, 2012, 2013 Google Inc. |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 2 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, but |
| # WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| # General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program; if not, write to the Free Software |
| # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| # 02110-1301, USA. |
| |
| |
| """Script to check NEWS file. |
| |
| """ |
| |
| # pylint: disable=C0103 |
| # [C0103] Invalid name |
| |
| import sys |
| import time |
| import datetime |
| import locale |
| import fileinput |
| import re |
| import os |
| |
| |
| DASHES_RE = re.compile(r"^\s*-+\s*$") |
| RELEASED_RE = re.compile(r"^\*\(Released (?P<day>[A-Z][a-z]{2})," |
| r" (?P<date>.+)\)\*$") |
| UNRELEASED_RE = re.compile(r"^\*\(unreleased\)\*$") |
| VERSION_RE = re.compile(r"^Version (\d+(\.\d+)+( (alpha|beta|rc)\d+)?)$") |
| |
| #: How many days release timestamps may be in the future |
| TIMESTAMP_FUTURE_DAYS_MAX = 3 |
| |
| errors = [] |
| |
| |
| def Error(msg): |
| """Log an error for later display. |
| |
| """ |
| errors.append(msg) |
| |
| |
| def ReqNLines(req, count_empty, lineno, line): |
| """Check if we have N empty lines before the current one. |
| |
| """ |
| if count_empty < req: |
| Error("Line %s: Missing empty line(s) before %s," |
| " %d needed but got only %d" % |
| (lineno, line, req, count_empty)) |
| if count_empty > req: |
| Error("Line %s: Too many empty lines before %s," |
| " %d needed but got %d" % |
| (lineno, line, req, count_empty)) |
| |
| |
| def IsAlphaVersion(version): |
| return "alpha" in version |
| |
| |
| def UpdateAllowUnreleased(allow_unreleased, version_match, release): |
| if not allow_unreleased: |
| return False |
| if IsAlphaVersion(release): |
| return True |
| version = version_match.group(1) |
| if version == release: |
| return False |
| return True |
| |
| |
| def main(): |
| # Ensure "C" locale is used |
| curlocale = locale.getlocale() |
| if curlocale != (None, None): |
| Error("Invalid locale %s" % curlocale) |
| |
| # Get the release version, but replace "~" with " " as the version |
| # in the NEWS file uses spaces for beta and rc releases. |
| release = os.environ.get('RELEASE', "").replace("~", " ") |
| |
| prevline = None |
| expect_date = False |
| count_empty = 0 |
| allow_unreleased = True |
| found_versions = set() |
| |
| for line in fileinput.input(): |
| line = line.rstrip("\n") |
| |
| version_match = VERSION_RE.match(line) |
| if version_match: |
| ReqNLines(2, count_empty, fileinput.filelineno(), line) |
| version = version_match.group(1) |
| if version in found_versions: |
| Error("Line %s: Duplicate release %s found" % |
| (fileinput.filelineno(), version)) |
| found_versions.add(version) |
| allow_unreleased = UpdateAllowUnreleased(allow_unreleased, version_match, |
| release) |
| |
| unreleased_match = UNRELEASED_RE.match(line) |
| if unreleased_match and not allow_unreleased: |
| Error("Line %s: Unreleased version after current release %s" % |
| (fileinput.filelineno(), release)) |
| |
| if unreleased_match or RELEASED_RE.match(line): |
| ReqNLines(1, count_empty, fileinput.filelineno(), line) |
| |
| if line: |
| count_empty = 0 |
| else: |
| count_empty += 1 |
| |
| if DASHES_RE.match(line): |
| if not VERSION_RE.match(prevline): |
| Error("Line %s: Invalid title" % |
| (fileinput.filelineno() - 1)) |
| if len(line) != len(prevline): |
| Error("Line %s: Invalid dashes length" % |
| (fileinput.filelineno())) |
| expect_date = True |
| |
| elif expect_date: |
| if not line: |
| # Ignore empty lines |
| continue |
| |
| if UNRELEASED_RE.match(line): |
| # Ignore unreleased versions |
| expect_date = False |
| continue |
| |
| m = RELEASED_RE.match(line) |
| if not m: |
| Error("Line %s: Invalid release line" % fileinput.filelineno()) |
| expect_date = False |
| continue |
| |
| # Including the weekday in the date string does not work as time.strptime |
| # would return an inconsistent result if the weekday is incorrect. |
| parsed_ts = time.mktime(time.strptime(m.group("date"), "%d %b %Y")) |
| parsed = datetime.date.fromtimestamp(parsed_ts) |
| today = datetime.date.today() |
| |
| if (parsed - datetime.timedelta(TIMESTAMP_FUTURE_DAYS_MAX)) > today: |
| Error("Line %s: %s is more than %s days in the future (today is %s)" % |
| (fileinput.filelineno(), parsed, TIMESTAMP_FUTURE_DAYS_MAX, |
| today)) |
| |
| weekday = parsed.strftime("%a") |
| |
| # Check weekday |
| if m.group("day") != weekday: |
| Error("Line %s: %s was/is a %s, not %s" % |
| (fileinput.filelineno(), parsed, weekday, |
| m.group("day"))) |
| |
| expect_date = False |
| |
| prevline = line |
| |
| if errors: |
| for msg in errors: |
| print >> sys.stderr, msg |
| sys.exit(1) |
| else: |
| sys.exit(0) |
| |
| |
| if __name__ == "__main__": |
| main() |