#!/usr/bin/env python3 # # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Generate version string for NotoColorEmoji. This parses the color emoji template file and updates the lines containing version string info, writing a new file. The nameID 5 field in the emoji font should reflect the commit/date of the repo it was built from. This will build a string of the following format: Version 1.39;GOOG;noto-emoji:20170220:a8a215d2e889' This is intended to indicate that it was built by Google from noto-emoji at commit a8a215d2e889 and date 20170220 (since dates are a bit easier to locate in time than commit hashes). For building with external data we don't include the commit id as we might be using different resources. Instead the version string is: Version 1.39;GOOG;noto-emoji:20170518;BETA Here the date is the current date, and the message after 'BETA ' is provided using the '-b' flag. There's no commit hash. This also bypasses some checks about the state of the repo. The release number should have 2 or 3 minor digits. Right now we've been using 2 but at the next major release we probably want to use 3. This supports both. It will bump the version number if none is provided, maintaining the minor digit length. """ import argparse import datetime import re from nototools import tool_utils # These are not very lenient, we expect to be applied to the noto color # emoji template ttx file which matches these. Why then require the # input argument, you ask? Um... testing? _nameid_re = re.compile(r'\s*') def _get_existing_version(lines): """Scan lines for all existing version numbers, and ensure they match. Return the matched version number string.""" version = None def check_version(new_version): if version is not None and new_version != version: raise Exception( 'version %s and namerecord version %s do not match' % ( version, new_version)) return new_version saw_nameid = False for line in lines: if saw_nameid: saw_nameid = False m = _version_re.match(line) if not m: raise Exception('could not match line "%s" in namerecord' % line) version = check_version(m.group(1)) elif _nameid_re.match(line): saw_nameid = True else: m = _headrev_re.match(line) if m: version = check_version(m.group(1)) return version def _version_to_mm(version): majs, mins = version.split('.') minor_len = len(mins) return int(majs), int(mins), minor_len def _mm_to_version(major, minor, minor_len): fmt = '%%d.%%0%dd' % minor_len return fmt % (major, minor) def _version_compare(lhs, rhs): lmaj, lmin, llen = _version_to_mm(lhs) rmaj, rmin, rlen = _version_to_mm(rhs) # if major versions differ, we don't care about the minor length, else # they should be the same if lmaj != rmaj: return lmaj - rmaj if llen != rlen: raise Exception('minor version lengths differ: "%s" and "%s"' % (lhs, rhs)) return lmin - rmin def _version_bump(version): major, minor, minor_len = _version_to_mm(version) minor = (minor + 1) % (10 ** minor_len) if minor == 0: raise Exception('cannot bump version "%s", requires new major' % version) return _mm_to_version(major, minor, minor_len) def _get_repo_version_str(beta): """See above for description of this string.""" if beta is not None: date_str = datetime.date.today().strftime('%Y%m%d') return 'GOOG;noto-emoji:%s;BETA %s' % (date_str, beta) p = tool_utils.resolve_path('[emoji]') commit, date, _ = tool_utils.git_head_commit(p) if not tool_utils.git_check_remote_commit(p, commit): raise Exception('emoji not on upstream master branch') date_re = re.compile(r'(\d{4})-(\d{2})-(\d{2})') m = date_re.match(date) if not m: raise Exception('could not match "%s" with "%s"' % (date, date_re.pattern)) ymd = ''.join(m.groups()) return 'GOOG;noto-emoji:%s:%s' % (ymd, commit[:12]) def _replace_existing_version(lines, version, version_str): """Update lines with new version strings in appropriate places.""" saw_nameid = False for i in range(len(lines)): line = lines[i] if saw_nameid: saw_nameid = False # preserve indentation lead_ws = len(line) - len(line.lstrip()) lines[i] = line[:lead_ws] + version_str + '\n' elif _nameid_re.match(line): saw_nameid = True elif _headrev_re.match(line): lead_ws = len(line) - len(line.lstrip()) lines[i] = line[:lead_ws] + '\n' % version def update_version(srcfile, dstfile, version, beta): """Update version in srcfile and write to dstfile. If version is None, bumps the current version, else version must be greater than the current version.""" with open(srcfile, 'r') as f: lines = f.readlines() current_version = _get_existing_version(lines) if not version: version = _version_bump(current_version) elif version and _version_compare(version, current_version) <= 0: raise Exception('new version %s is <= current version %s' % ( version, current_version)) version_str = 'Version %s;%s' % (version, _get_repo_version_str(beta)) _replace_existing_version(lines, version, version_str) with open(dstfile, 'w') as f: for line in lines: f.write(line) def main(): parser = argparse.ArgumentParser() parser.add_argument( '-v', '--version', help='version number, default bumps the current ' 'version', metavar='ver') parser.add_argument( '-s', '--src', help='ttx file with name and head tables', metavar='file', required=True) parser.add_argument( '-d', '--dst', help='name of edited ttx file to write', metavar='file', required=True) parser.add_argument( '-b', '--beta', help='beta tag if font is built using external resources') args = parser.parse_args() update_version(args.src, args.dst, args.version, args.beta) if __name__ == '__main__': main()