blob: acaaf223efbb08b4012584948c6ebbc6ad64b1a7 [file] [log] [blame]
#!/user/bin/env python
#
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import os
import re
import sys
import zipfile
from util import build_utils
from util import java_cpp_utils
def _ToUpper(match):
return match.group(1).upper()
def _GetClassName(source_path):
name = os.path.basename(os.path.abspath(source_path))
(name, _) = os.path.splitext(name)
name = re.sub(r'_([a-z])', _ToUpper, name)
name = re.sub(r'^(.)', _ToUpper, name)
return name
class _String(object):
def __init__(self, name, value, comments):
self.name = java_cpp_utils.KCamelToShouty(name)
self.value = value
self.comments = '\n'.join(' ' + x for x in comments)
def Format(self):
return '%s\n public static final String %s = %s;' % (
self.comments, self.name, self.value)
def ParseTemplateFile(lines):
package_re = re.compile(r'^package (.*);')
class_re = re.compile(r'.*class (.*) {')
package = ''
class_name = ''
for line in lines:
package_line = package_re.match(line)
if package_line:
package = package_line.groups()[0]
class_line = class_re.match(line)
if class_line:
class_name = class_line.groups()[0]
break
return package, class_name
# TODO(crbug.com/937282): It should be possible to parse a file for more than
# string constants. However, this currently only handles extracting string
# constants from a file (and all string constants from that file). Work will
# be needed if we want to annotate specific constants or non string constants
# in the file to be parsed.
class StringFileParser(object):
SINGLE_LINE_COMMENT_RE = re.compile(r'\s*(// [^\n]*)')
STRING_RE = re.compile(r'\s*const char k(.*)\[\]\s*=\s*(?:(".*"))?')
VALUE_RE = re.compile(r'\s*("[^"]*")')
def __init__(self, lines, path=''):
self._lines = lines
self._path = path
self._in_string = False
self._in_comment = False
self._package = ''
self._current_comments = []
self._current_name = ''
self._current_value = ''
self._strings = []
def _Reset(self):
self._current_comments = []
self._current_name = ''
self._current_value = ''
self._in_string = False
self._in_comment = False
def _AppendString(self):
self._strings.append(
_String(self._current_name, self._current_value,
self._current_comments))
self._Reset()
def _ParseValue(self, line):
value_line = StringFileParser.VALUE_RE.match(line)
if value_line:
self._current_value = value_line.groups()[0]
self._AppendString()
else:
self._Reset()
def _ParseComment(self, line):
comment_line = StringFileParser.SINGLE_LINE_COMMENT_RE.match(line)
if comment_line:
self._current_comments.append(comment_line.groups()[0])
self._in_comment = True
self._in_string = True
return True
else:
self._in_comment = False
return False
def _ParseString(self, line):
string_line = StringFileParser.STRING_RE.match(line)
if string_line:
self._current_name = string_line.groups()[0]
if string_line.groups()[1]:
self._current_value = string_line.groups()[1]
self._AppendString()
return True
else:
self._in_string = False
return False
def _ParseLine(self, line):
if not self._in_string:
if not self._ParseString(line):
self._ParseComment(line)
return
if self._in_comment:
if self._ParseComment(line):
return
if not self._ParseString(line):
self._Reset()
return
if self._in_string:
self._ParseValue(line)
def Parse(self):
for line in self._lines:
self._ParseLine(line)
return self._strings
def _GenerateOutput(template, source_path, template_path, strings):
description_template = """
// This following string constants were inserted by
// {SCRIPT_NAME}
// From
// {SOURCE_PATH}
// Into
// {TEMPLATE_PATH}
"""
values = {
'SCRIPT_NAME': java_cpp_utils.GetScriptName(),
'SOURCE_PATH': source_path,
'TEMPLATE_PATH': template_path,
}
description = description_template.format(**values)
native_strings = '\n\n'.join(x.Format() for x in strings)
values = {
'NATIVE_STRINGS': description + native_strings,
}
return template.format(**values)
def _ParseStringFile(path):
with open(path) as f:
return StringFileParser(f.readlines(), path).Parse()
def _Generate(source_paths, template_path):
with open(template_path) as f:
lines = f.readlines()
template = ''.join(lines)
for source_path in source_paths:
strings = _ParseStringFile(source_path)
package, class_name = ParseTemplateFile(lines)
package_path = package.replace('.', os.path.sep)
file_name = class_name + '.java'
output_path = os.path.join(package_path, file_name)
output = _GenerateOutput(template, source_path, template_path, strings)
yield output, output_path
def _Main(argv):
parser = argparse.ArgumentParser()
parser.add_argument(
'--srcjar',
required=True,
help='When specified, a .srcjar at the given path is '
'created instead of individual .java files.')
parser.add_argument(
'--template',
required=True,
help='Can be used to provide a context into which the'
'new string constants will be inserted.')
parser.add_argument(
'inputs', nargs='+', help='Input file(s)', metavar='INPUTFILE')
args = parser.parse_args(argv)
with build_utils.AtomicOutput(args.srcjar) as f:
with zipfile.ZipFile(f, 'w', zipfile.ZIP_STORED) as srcjar:
for data, path in _Generate(args.inputs, args.template):
build_utils.AddToZipHermetic(srcjar, path, data=data)
if __name__ == '__main__':
_Main(sys.argv[1:])