mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-11-24 14:26:09 +03:00
454 lines
16 KiB
Python
454 lines
16 KiB
Python
|
#!/usr/bin/env python
|
||
|
#
|
||
|
# Copyright 2014 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 collections
|
||
|
from datetime import date
|
||
|
import re
|
||
|
import optparse
|
||
|
import os
|
||
|
from string import Template
|
||
|
import sys
|
||
|
import textwrap
|
||
|
import zipfile
|
||
|
|
||
|
from util import build_utils
|
||
|
|
||
|
# List of C++ types that are compatible with the Java code generated by this
|
||
|
# script.
|
||
|
#
|
||
|
# This script can parse .idl files however, at present it ignores special
|
||
|
# rules such as [cpp_enum_prefix_override="ax_attr"].
|
||
|
ENUM_FIXED_TYPE_WHITELIST = ['char', 'unsigned char',
|
||
|
'short', 'unsigned short',
|
||
|
'int', 'int8_t', 'int16_t', 'int32_t', 'uint8_t', 'uint16_t']
|
||
|
|
||
|
class EnumDefinition(object):
|
||
|
def __init__(self, original_enum_name=None, class_name_override=None,
|
||
|
enum_package=None, entries=None, comments=None, fixed_type=None):
|
||
|
self.original_enum_name = original_enum_name
|
||
|
self.class_name_override = class_name_override
|
||
|
self.enum_package = enum_package
|
||
|
self.entries = collections.OrderedDict(entries or [])
|
||
|
self.comments = collections.OrderedDict(comments or [])
|
||
|
self.prefix_to_strip = None
|
||
|
self.fixed_type = fixed_type
|
||
|
|
||
|
def AppendEntry(self, key, value):
|
||
|
if key in self.entries:
|
||
|
raise Exception('Multiple definitions of key %s found.' % key)
|
||
|
self.entries[key] = value
|
||
|
|
||
|
def AppendEntryComment(self, key, value):
|
||
|
if key in self.comments:
|
||
|
raise Exception('Multiple definitions of key %s found.' % key)
|
||
|
self.comments[key] = value
|
||
|
|
||
|
@property
|
||
|
def class_name(self):
|
||
|
return self.class_name_override or self.original_enum_name
|
||
|
|
||
|
def Finalize(self):
|
||
|
self._Validate()
|
||
|
self._AssignEntryIndices()
|
||
|
self._StripPrefix()
|
||
|
self._NormalizeNames()
|
||
|
|
||
|
def _Validate(self):
|
||
|
assert self.class_name
|
||
|
assert self.enum_package
|
||
|
assert self.entries
|
||
|
if self.fixed_type and self.fixed_type not in ENUM_FIXED_TYPE_WHITELIST:
|
||
|
raise Exception('Fixed type %s for enum %s not whitelisted.' %
|
||
|
(self.fixed_type, self.class_name))
|
||
|
|
||
|
def _AssignEntryIndices(self):
|
||
|
# Enums, if given no value, are given the value of the previous enum + 1.
|
||
|
if not all(self.entries.values()):
|
||
|
prev_enum_value = -1
|
||
|
for key, value in self.entries.iteritems():
|
||
|
if not value:
|
||
|
self.entries[key] = prev_enum_value + 1
|
||
|
elif value in self.entries:
|
||
|
self.entries[key] = self.entries[value]
|
||
|
else:
|
||
|
try:
|
||
|
self.entries[key] = int(value)
|
||
|
except ValueError:
|
||
|
raise Exception('Could not interpret integer from enum value "%s" '
|
||
|
'for key %s.' % (value, key))
|
||
|
prev_enum_value = self.entries[key]
|
||
|
|
||
|
|
||
|
def _StripPrefix(self):
|
||
|
prefix_to_strip = self.prefix_to_strip
|
||
|
if not prefix_to_strip:
|
||
|
shout_case = self.original_enum_name
|
||
|
shout_case = re.sub('(?!^)([A-Z]+)', r'_\1', shout_case).upper()
|
||
|
shout_case += '_'
|
||
|
|
||
|
prefixes = [shout_case, self.original_enum_name,
|
||
|
'k' + self.original_enum_name]
|
||
|
|
||
|
for prefix in prefixes:
|
||
|
if all([w.startswith(prefix) for w in self.entries.keys()]):
|
||
|
prefix_to_strip = prefix
|
||
|
break
|
||
|
else:
|
||
|
prefix_to_strip = ''
|
||
|
|
||
|
def StripEntries(entries):
|
||
|
ret = collections.OrderedDict()
|
||
|
for k, v in entries.iteritems():
|
||
|
stripped_key = k.replace(prefix_to_strip, '', 1)
|
||
|
if isinstance(v, basestring):
|
||
|
stripped_value = v.replace(prefix_to_strip, '')
|
||
|
else:
|
||
|
stripped_value = v
|
||
|
ret[stripped_key] = stripped_value
|
||
|
|
||
|
return ret
|
||
|
|
||
|
self.entries = StripEntries(self.entries)
|
||
|
self.comments = StripEntries(self.comments)
|
||
|
|
||
|
def _NormalizeNames(self):
|
||
|
self.entries = _TransformKeys(self.entries, _KCamelToShouty)
|
||
|
self.comments = _TransformKeys(self.comments, _KCamelToShouty)
|
||
|
|
||
|
|
||
|
def _TransformKeys(d, func):
|
||
|
"""Normalize keys in |d| and update references to old keys in |d| values."""
|
||
|
normal_keys = {k: func(k) for k in d}
|
||
|
ret = collections.OrderedDict()
|
||
|
for k, v in d.iteritems():
|
||
|
# Need to transform values as well when the entry value was explicitly set
|
||
|
# (since it could contain references to other enum entry values).
|
||
|
if isinstance(v, basestring):
|
||
|
for normal_key in normal_keys:
|
||
|
v = v.replace(normal_key, normal_keys[normal_key])
|
||
|
ret[normal_keys[k]] = v
|
||
|
return ret
|
||
|
|
||
|
|
||
|
def _KCamelToShouty(s):
|
||
|
"""Convert |s| from kCamelCase or CamelCase to SHOUTY_CASE.
|
||
|
|
||
|
kFooBar -> FOO_BAR
|
||
|
FooBar -> FOO_BAR
|
||
|
FooBAR9 -> FOO_BAR9
|
||
|
FooBARBaz -> FOO_BAR_BAZ
|
||
|
"""
|
||
|
if not re.match(r'^k?([A-Z][^A-Z]+|[A-Z0-9]+)+$', s):
|
||
|
return s
|
||
|
# Strip the leading k.
|
||
|
s = re.sub(r'^k', '', s)
|
||
|
# Add _ between title words and anything else.
|
||
|
s = re.sub(r'([^_])([A-Z][^A-Z_0-9]+)', r'\1_\2', s)
|
||
|
# Add _ between lower -> upper transitions.
|
||
|
s = re.sub(r'([^A-Z_0-9])([A-Z])', r'\1_\2', s)
|
||
|
return s.upper()
|
||
|
|
||
|
|
||
|
class DirectiveSet(object):
|
||
|
class_name_override_key = 'CLASS_NAME_OVERRIDE'
|
||
|
enum_package_key = 'ENUM_PACKAGE'
|
||
|
prefix_to_strip_key = 'PREFIX_TO_STRIP'
|
||
|
|
||
|
known_keys = [class_name_override_key, enum_package_key, prefix_to_strip_key]
|
||
|
|
||
|
def __init__(self):
|
||
|
self._directives = {}
|
||
|
|
||
|
def Update(self, key, value):
|
||
|
if key not in DirectiveSet.known_keys:
|
||
|
raise Exception("Unknown directive: " + key)
|
||
|
self._directives[key] = value
|
||
|
|
||
|
@property
|
||
|
def empty(self):
|
||
|
return len(self._directives) == 0
|
||
|
|
||
|
def UpdateDefinition(self, definition):
|
||
|
definition.class_name_override = self._directives.get(
|
||
|
DirectiveSet.class_name_override_key, '')
|
||
|
definition.enum_package = self._directives.get(
|
||
|
DirectiveSet.enum_package_key)
|
||
|
definition.prefix_to_strip = self._directives.get(
|
||
|
DirectiveSet.prefix_to_strip_key)
|
||
|
|
||
|
|
||
|
class HeaderParser(object):
|
||
|
single_line_comment_re = re.compile(r'\s*//\s*([^\n]*)')
|
||
|
multi_line_comment_start_re = re.compile(r'\s*/\*')
|
||
|
enum_line_re = re.compile(r'^\s*(\w+)(\s*\=\s*([^,\n]+))?,?')
|
||
|
enum_end_re = re.compile(r'^\s*}\s*;\.*$')
|
||
|
generator_error_re = re.compile(r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*$')
|
||
|
generator_directive_re = re.compile(
|
||
|
r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*([\.\w]+)$')
|
||
|
multi_line_generator_directive_start_re = re.compile(
|
||
|
r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*\(([\.\w]*)$')
|
||
|
multi_line_directive_continuation_re = re.compile(r'^\s*//\s+([\.\w]+)$')
|
||
|
multi_line_directive_end_re = re.compile(r'^\s*//\s+([\.\w]*)\)$')
|
||
|
|
||
|
optional_class_or_struct_re = r'(class|struct)?'
|
||
|
enum_name_re = r'(\w+)'
|
||
|
optional_fixed_type_re = r'(\:\s*(\w+\s*\w+?))?'
|
||
|
enum_start_re = re.compile(r'^\s*(?:\[cpp.*\])?\s*enum\s+' +
|
||
|
optional_class_or_struct_re + '\s*' + enum_name_re + '\s*' +
|
||
|
optional_fixed_type_re + '\s*{\s*')
|
||
|
enum_single_line_re = re.compile(
|
||
|
r'^\s*(?:\[cpp.*\])?\s*enum.*{(?P<enum_entries>.*)}.*$')
|
||
|
|
||
|
def __init__(self, lines, path=''):
|
||
|
self._lines = lines
|
||
|
self._path = path
|
||
|
self._enum_definitions = []
|
||
|
self._in_enum = False
|
||
|
self._current_definition = None
|
||
|
self._current_comments = []
|
||
|
self._generator_directives = DirectiveSet()
|
||
|
self._multi_line_generator_directive = None
|
||
|
self._current_enum_entry = ''
|
||
|
|
||
|
def _ApplyGeneratorDirectives(self):
|
||
|
self._generator_directives.UpdateDefinition(self._current_definition)
|
||
|
self._generator_directives = DirectiveSet()
|
||
|
|
||
|
def ParseDefinitions(self):
|
||
|
for line in self._lines:
|
||
|
self._ParseLine(line)
|
||
|
return self._enum_definitions
|
||
|
|
||
|
def _ParseLine(self, line):
|
||
|
if self._multi_line_generator_directive:
|
||
|
self._ParseMultiLineDirectiveLine(line)
|
||
|
elif not self._in_enum:
|
||
|
self._ParseRegularLine(line)
|
||
|
else:
|
||
|
self._ParseEnumLine(line)
|
||
|
|
||
|
def _ParseEnumLine(self, line):
|
||
|
if HeaderParser.multi_line_comment_start_re.match(line):
|
||
|
raise Exception('Multi-line comments in enums are not supported in ' +
|
||
|
self._path)
|
||
|
|
||
|
enum_comment = HeaderParser.single_line_comment_re.match(line)
|
||
|
if enum_comment:
|
||
|
comment = enum_comment.groups()[0]
|
||
|
if comment:
|
||
|
self._current_comments.append(comment)
|
||
|
elif HeaderParser.enum_end_re.match(line):
|
||
|
self._FinalizeCurrentEnumDefinition()
|
||
|
else:
|
||
|
self._AddToCurrentEnumEntry(line)
|
||
|
if ',' in line:
|
||
|
self._ParseCurrentEnumEntry()
|
||
|
|
||
|
def _ParseSingleLineEnum(self, line):
|
||
|
for entry in line.split(','):
|
||
|
self._AddToCurrentEnumEntry(entry)
|
||
|
self._ParseCurrentEnumEntry()
|
||
|
|
||
|
self._FinalizeCurrentEnumDefinition()
|
||
|
|
||
|
def _ParseCurrentEnumEntry(self):
|
||
|
if not self._current_enum_entry:
|
||
|
return
|
||
|
|
||
|
enum_entry = HeaderParser.enum_line_re.match(self._current_enum_entry)
|
||
|
if not enum_entry:
|
||
|
raise Exception('Unexpected error while attempting to parse %s as enum '
|
||
|
'entry.' % self._current_enum_entry)
|
||
|
|
||
|
enum_key = enum_entry.groups()[0]
|
||
|
enum_value = enum_entry.groups()[2]
|
||
|
self._current_definition.AppendEntry(enum_key, enum_value)
|
||
|
if self._current_comments:
|
||
|
self._current_definition.AppendEntryComment(
|
||
|
enum_key, ' '.join(self._current_comments))
|
||
|
self._current_comments = []
|
||
|
self._current_enum_entry = ''
|
||
|
|
||
|
def _AddToCurrentEnumEntry(self, line):
|
||
|
self._current_enum_entry += ' ' + line.strip()
|
||
|
|
||
|
def _FinalizeCurrentEnumDefinition(self):
|
||
|
if self._current_enum_entry:
|
||
|
self._ParseCurrentEnumEntry()
|
||
|
self._ApplyGeneratorDirectives()
|
||
|
self._current_definition.Finalize()
|
||
|
self._enum_definitions.append(self._current_definition)
|
||
|
self._current_definition = None
|
||
|
self._in_enum = False
|
||
|
|
||
|
def _ParseMultiLineDirectiveLine(self, line):
|
||
|
multi_line_directive_continuation = (
|
||
|
HeaderParser.multi_line_directive_continuation_re.match(line))
|
||
|
multi_line_directive_end = (
|
||
|
HeaderParser.multi_line_directive_end_re.match(line))
|
||
|
|
||
|
if multi_line_directive_continuation:
|
||
|
value_cont = multi_line_directive_continuation.groups()[0]
|
||
|
self._multi_line_generator_directive[1].append(value_cont)
|
||
|
elif multi_line_directive_end:
|
||
|
directive_name = self._multi_line_generator_directive[0]
|
||
|
directive_value = "".join(self._multi_line_generator_directive[1])
|
||
|
directive_value += multi_line_directive_end.groups()[0]
|
||
|
self._multi_line_generator_directive = None
|
||
|
self._generator_directives.Update(directive_name, directive_value)
|
||
|
else:
|
||
|
raise Exception('Malformed multi-line directive declaration in ' +
|
||
|
self._path)
|
||
|
|
||
|
def _ParseRegularLine(self, line):
|
||
|
enum_start = HeaderParser.enum_start_re.match(line)
|
||
|
generator_directive_error = HeaderParser.generator_error_re.match(line)
|
||
|
generator_directive = HeaderParser.generator_directive_re.match(line)
|
||
|
multi_line_generator_directive_start = (
|
||
|
HeaderParser.multi_line_generator_directive_start_re.match(line))
|
||
|
single_line_enum = HeaderParser.enum_single_line_re.match(line)
|
||
|
|
||
|
if generator_directive_error:
|
||
|
raise Exception('Malformed directive declaration in ' + self._path +
|
||
|
'. Use () for multi-line directives. E.g.\n' +
|
||
|
'// GENERATED_JAVA_ENUM_PACKAGE: (\n' +
|
||
|
'// foo.package)')
|
||
|
elif generator_directive:
|
||
|
directive_name = generator_directive.groups()[0]
|
||
|
directive_value = generator_directive.groups()[1]
|
||
|
self._generator_directives.Update(directive_name, directive_value)
|
||
|
elif multi_line_generator_directive_start:
|
||
|
directive_name = multi_line_generator_directive_start.groups()[0]
|
||
|
directive_value = multi_line_generator_directive_start.groups()[1]
|
||
|
self._multi_line_generator_directive = (directive_name, [directive_value])
|
||
|
elif enum_start or single_line_enum:
|
||
|
if self._generator_directives.empty:
|
||
|
return
|
||
|
self._current_definition = EnumDefinition(
|
||
|
original_enum_name=enum_start.groups()[1],
|
||
|
fixed_type=enum_start.groups()[3])
|
||
|
self._in_enum = True
|
||
|
if single_line_enum:
|
||
|
self._ParseSingleLineEnum(single_line_enum.group('enum_entries'))
|
||
|
|
||
|
def GetScriptName():
|
||
|
return os.path.basename(os.path.abspath(sys.argv[0]))
|
||
|
|
||
|
def DoGenerate(source_paths):
|
||
|
for source_path in source_paths:
|
||
|
enum_definitions = DoParseHeaderFile(source_path)
|
||
|
if not enum_definitions:
|
||
|
raise Exception('No enums found in %s\n'
|
||
|
'Did you forget prefixing enums with '
|
||
|
'"// GENERATED_JAVA_ENUM_PACKAGE: foo"?' %
|
||
|
source_path)
|
||
|
for enum_definition in enum_definitions:
|
||
|
package_path = enum_definition.enum_package.replace('.', os.path.sep)
|
||
|
file_name = enum_definition.class_name + '.java'
|
||
|
output_path = os.path.join(package_path, file_name)
|
||
|
output = GenerateOutput(source_path, enum_definition)
|
||
|
yield output_path, output
|
||
|
|
||
|
|
||
|
def DoParseHeaderFile(path):
|
||
|
with open(path) as f:
|
||
|
return HeaderParser(f.readlines(), path).ParseDefinitions()
|
||
|
|
||
|
|
||
|
def GenerateOutput(source_path, enum_definition):
|
||
|
template = Template("""
|
||
|
// Copyright ${YEAR} 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.
|
||
|
|
||
|
// This file is autogenerated by
|
||
|
// ${SCRIPT_NAME}
|
||
|
// From
|
||
|
// ${SOURCE_PATH}
|
||
|
|
||
|
package ${PACKAGE};
|
||
|
|
||
|
import android.support.annotation.IntDef;
|
||
|
|
||
|
import java.lang.annotation.Retention;
|
||
|
import java.lang.annotation.RetentionPolicy;
|
||
|
|
||
|
@IntDef({
|
||
|
${INT_DEF}
|
||
|
})
|
||
|
@Retention(RetentionPolicy.SOURCE)
|
||
|
public @interface ${CLASS_NAME} {
|
||
|
${ENUM_ENTRIES}
|
||
|
}
|
||
|
""")
|
||
|
|
||
|
enum_template = Template(' int ${NAME} = ${VALUE};')
|
||
|
enum_entries_string = []
|
||
|
enum_names = []
|
||
|
for enum_name, enum_value in enum_definition.entries.iteritems():
|
||
|
values = {
|
||
|
'NAME': enum_name,
|
||
|
'VALUE': enum_value,
|
||
|
}
|
||
|
enum_comments = enum_definition.comments.get(enum_name)
|
||
|
if enum_comments:
|
||
|
enum_comments_indent = ' * '
|
||
|
comments_line_wrapper = textwrap.TextWrapper(
|
||
|
initial_indent=enum_comments_indent,
|
||
|
subsequent_indent=enum_comments_indent,
|
||
|
width=100)
|
||
|
enum_entries_string.append(' /**')
|
||
|
enum_entries_string.append(
|
||
|
'\n'.join(comments_line_wrapper.wrap(enum_comments)))
|
||
|
enum_entries_string.append(' */')
|
||
|
enum_entries_string.append(enum_template.substitute(values))
|
||
|
enum_names.append(enum_definition.class_name + '.' + enum_name)
|
||
|
enum_entries_string = '\n'.join(enum_entries_string)
|
||
|
|
||
|
enum_names_indent = ' ' * 4
|
||
|
wrapper = textwrap.TextWrapper(initial_indent = enum_names_indent,
|
||
|
subsequent_indent = enum_names_indent,
|
||
|
width = 100)
|
||
|
enum_names_string = '\n'.join(wrapper.wrap(', '.join(enum_names)))
|
||
|
|
||
|
values = {
|
||
|
'CLASS_NAME': enum_definition.class_name,
|
||
|
'ENUM_ENTRIES': enum_entries_string,
|
||
|
'PACKAGE': enum_definition.enum_package,
|
||
|
'INT_DEF': enum_names_string,
|
||
|
'SCRIPT_NAME': GetScriptName(),
|
||
|
'SOURCE_PATH': source_path,
|
||
|
'YEAR': str(date.today().year)
|
||
|
}
|
||
|
return template.substitute(values)
|
||
|
|
||
|
|
||
|
def DoMain(argv):
|
||
|
usage = 'usage: %prog [options] [output_dir] input_file(s)...'
|
||
|
parser = optparse.OptionParser(usage=usage)
|
||
|
build_utils.AddDepfileOption(parser)
|
||
|
|
||
|
parser.add_option('--srcjar',
|
||
|
help='When specified, a .srcjar at the given path is '
|
||
|
'created instead of individual .java files.')
|
||
|
|
||
|
options, args = parser.parse_args(argv)
|
||
|
|
||
|
if not args:
|
||
|
parser.error('Need to specify at least one input file')
|
||
|
input_paths = args
|
||
|
|
||
|
with zipfile.ZipFile(options.srcjar, 'w', zipfile.ZIP_STORED) as srcjar:
|
||
|
for output_path, data in DoGenerate(input_paths):
|
||
|
build_utils.AddToZipHermetic(srcjar, output_path, data=data)
|
||
|
|
||
|
if options.depfile:
|
||
|
build_utils.WriteDepfile(options.depfile, options.srcjar)
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
DoMain(sys.argv[1:])
|