193 lines
6.5 KiB
Python
193 lines
6.5 KiB
Python
|
#!/usr/bin/env python
|
||
|
#
|
||
|
# 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 resoruces. Instead the version string is:
|
||
|
Version 1.39;GOOG;noto-emoji:20170518;BETA <msg>
|
||
|
|
||
|
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 relase number should have 2 or 3 minor digits. Right now we've been
|
||
|
using 2 but at the next major relase 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*<namerecord nameID="5"')
|
||
|
_version_re = re.compile(r'\s*Version\s(\d+.\d{2,3})')
|
||
|
_headrev_re = re.compile(r'\s*<fontRevision value="(\d+.\d{2,3})"/>')
|
||
|
|
||
|
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] + '<fontRevision value="%s"/>\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 verison."""
|
||
|
|
||
|
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()
|