Chromium Code Reviews| Index: tools/json_schema_compiler/model.py |
| diff --git a/tools/json_schema_compiler/model.py b/tools/json_schema_compiler/model.py |
| index 2c356fff20d06f0815e7a2662586bdacbfa77e01..c179b902be3f1a946b220ab03323219f731c0e15 100644 |
| --- a/tools/json_schema_compiler/model.py |
| +++ b/tools/json_schema_compiler/model.py |
| @@ -3,18 +3,23 @@ |
| # found in the LICENSE file. |
| import os.path |
| +import re |
| class Model(object): |
| """Model of all namespaces that comprise an API. |
| + |
| + Properties: |
| + - |namespaces| a map of a namespace name to its model.Namespace |
| """ |
| def __init__(self): |
| self.namespaces = {} |
| def AddNamespace(self, json, source_file): |
| - """Add a namespace's json to the model if it has a "compile" property set |
| - to true. Returns the new namespace or None if a namespace wasn't added. |
| + """Add a namespace's json to the model if it doesn't have "nocompile" |
| + property set to true. Returns the new namespace or None if a namespace |
| + wasn't added. |
| """ |
| - if not json.get('compile'): |
| + if json.get('nocompile', False): |
| return None |
| namespace = Namespace(json, source_file) |
| self.namespaces[namespace.name] = namespace |
| @@ -22,24 +27,36 @@ class Model(object): |
| class Namespace(object): |
| """An API namespace. |
| + |
| + Properties: |
| + - |name| the name of the namespace |
| + - |source_file| the file that contained the namespace definition |
|
Yoyo Zhou
2012/02/08 19:04:12
This seems redundant given the 2 below it.
calamity
2012/02/09 00:56:52
It's really just for convenience, otherwise, I'd n
|
| + - |source_file_dir| the directory component of |source_file| |
| + - |source_file_filename| the filename component of |source_file| |
| + - |types| a map of type names to their model.Type |
| + - |functions| a map of function names to their model.Function |
| """ |
| def __init__(self, json, source_file): |
| self.name = json['namespace'] |
| self.source_file = source_file |
| self.source_file_dir, self.source_file_filename = os.path.split(source_file) |
| - self.type_dependencies = {} |
| self.types = {} |
| self.functions = {} |
| for type_json in json['types']: |
| type_ = Type(type_json) |
| self.types[type_.name] = type_ |
| for function_json in json['functions']: |
| - if not function_json.get('nocompile'): |
| + if not function_json.get('nocompile', False): |
| function = Function(function_json) |
| self.functions[function.name] = function |
| class Type(object): |
| """A Type defined in the json. |
| + |
| + Properties: |
| + - |name| the type name |
| + - |description| the description of the type (if provided) |
| + - |properties| a map of property names to their model.Property |
| """ |
| def __init__(self, json): |
| self.name = json['id'] |
| @@ -50,46 +67,70 @@ class Type(object): |
| class Callback(object): |
| """A callback parameter to a Function. |
| + |
| + Properties: |
| + - |params| the parameters to this callback. |
| """ |
| def __init__(self, json): |
| params = json['parameters'] |
| + self.params = [] |
| if len(params) == 0: |
| - self.param = None |
| + return |
| elif len(params) == 1: |
| param = params[0] |
| - self.param = Property(param['name'], param) |
| + self.params.append(Property(param['name'], param)) |
| else: |
| raise AssertionError("Callbacks can have at most a single parameter") |
| class Function(object): |
| """A Function defined in the API. |
| + |
| + Properties: |
| + - |name| the function name |
| + - |params| a list of parameters to the function (order matters). A separate |
| + parameter is used for each choice of a 'choices' parameter. |
| + - |description| a description of the function (if provided) |
| + - |callback| the callback parameter to the function. There should be exactly |
| + one |
| """ |
| def __init__(self, json): |
| self.name = json['name'] |
| self.params = [] |
| self.description = json['description'] |
| self.callback = None |
| - self.type_dependencies = {} |
| for param in json['parameters']: |
| if param.get('type') == 'function': |
| - assert (not self.callback), "Function has more than one callback" |
| + assert (not self.callback), self.name + " has more than one callback" |
| self.callback = Callback(param) |
| else: |
| self.params.append(Property(param['name'], param)) |
| - assert (self.callback), "Function does not support callback" |
| + assert (self.callback), self.name + " does not support callback" |
| -# TODO(calamity): handle Enum/choices |
| +# TODO(calamity): handle Enum |
| class Property(object): |
| """A property of a type OR a parameter to a function. |
| - Members will change based on PropertyType. Check self.type_ to determine which |
| - members actually exist. |
| + Properties: |
| + - |name| name of the property as in the json. This shouldn't change since |
| + it is the key used to access DictionaryValues |
| + - |unix_name| the unix_style_name of the property. Used as variable name |
| + - |optional| a boolean representing whether the property is optional |
| + - |description| a description of the property (if provided) |
| + - |type_| the model.PropertyType of this property |
| + - |ref_type| the type that the REF property is referencing. Can be used to |
| + map to its model.Type |
| + - |item_type| a model.Property representing the type of each element in an |
| + ARRAY |
| + - |properties| the properties of an OBJECT parameter |
| """ |
| def __init__(self, name, json): |
| + if not re.match('^[a-z][a-zA-Z0-9]*$', name): |
| + raise AssertionError('Name %s must be lowerCamelCase' % name) |
| self.name = name |
| + self._unix_name = _UnixName(self.name) |
| + self._unix_name_used = False |
| self.optional = json.get('optional', False) |
| self.description = json.get('description') |
| - # TODO(calamity) maybe check for circular refs? could that be a problem? |
| if '$ref' in json: |
| self.ref_type = json['$ref'] |
| self.type_ = PropertyType.REF |
| @@ -97,14 +138,16 @@ class Property(object): |
| json_type = json['type'] |
| if json_type == 'string': |
| self.type_ = PropertyType.STRING |
| - elif json_type == 'boolean': |
| + elif json_type == 'any': |
| + self.type_ = PropertyType.ANY |
| + elif json_type == 'boolean': |
| self.type_ = PropertyType.BOOLEAN |
| elif json_type == 'integer': |
| self.type_ = PropertyType.INTEGER |
| - elif json_type == 'double': |
| + elif json_type == 'number': |
| self.type_ = PropertyType.DOUBLE |
| elif json_type == 'array': |
| - self.item_type = Property(name + "_inner", json['items']) |
| + self.item_type = Property(name + "Element", json['items']) |
| self.type_ = PropertyType.ARRAY |
| elif json_type == 'object': |
| self.properties = {} |
| @@ -114,21 +157,61 @@ class Property(object): |
| else: |
| raise NotImplementedError(json_type) |
| elif 'choices' in json: |
| - self.type_ = PropertyType.CHOICES |
| + assert len(json['choices']), 'Choices has no choices\n%s' % json |
| self.choices = {} |
| + self.type_ = PropertyType.CHOICES |
| + for choice_json in json['choices']: |
| + choice = Property(self.name, choice_json) |
| + # A choice needs to have its unix_name set elsewhere |
|
Yoyo Zhou
2012/02/08 19:04:12
Can you discuss where SetUnixName might be called
calamity
2012/02/09 00:56:52
unix_name is set as a property of this class (line
|
| + choice._unix_name = None |
| + # The existence of any single choice is optional |
| + choice.optional = True |
| + self.choices[choice.type_] = choice |
| + else: |
| + raise NotImplementedError(json) |
| + |
| + def GetUnixName(self): |
| + """Gets the property's unix_name. Raises AttributeError if not set. |
| + """ |
| + if self._unix_name is None: |
| + raise AttributeError('No unix_name set on %s' % self.name) |
| + self._unix_name_used = True |
| + return self._unix_name |
| + |
| + def SetUnixName(self, unix_name): |
| + """Set the property's unix_name. Raises AttributeError if the unix_name has |
| + already been used (GetUnixName has been called). |
| + """ |
| + if self._unix_name_used: |
| + raise AttributeError( |
| + 'Cannot set the unix_name on %s; it is already used elsewhere as %s') |
|
Yoyo Zhou
2012/02/08 19:04:12
missing substitutions here?
calamity
2012/02/09 00:56:52
Done.
|
| + self._unix_name = unix_name |
| + unix_name = property(GetUnixName, SetUnixName) |
|
not at google - send to devlin
2012/02/08 05:02:08
Getter/Setter can be a bit simpler to take advanta
calamity
2012/02/08 07:01:18
If the property is a choice, we unset it. GetUnixN
|
| class PropertyType(object): |
| """Enum of different types of properties/parameters. |
| """ |
| class _Info(object): |
| - def __init__(self, is_fundamental): |
| + def __init__(self, is_fundamental, name): |
| self.is_fundamental = is_fundamental |
| + self.name = name |
| + |
| + def __repr__(self): |
| + return self.name |
| + |
| + INTEGER = _Info(True, "INTEGER") |
| + DOUBLE = _Info(True, "DOUBLE") |
| + BOOLEAN = _Info(True, "BOOLEAN") |
| + STRING = _Info(True, "STRING") |
| + ARRAY = _Info(False, "ARRAY") |
| + REF = _Info(False, "REF") |
| + CHOICES = _Info(False, "CHOICES") |
| + OBJECT = _Info(False, "OBJECT") |
| + ANY = _Info(False, "ANY") |
| + |
| +def _UnixName(name): |
| + """Returns the unix_style name for a given string. |
|
Yoyo Zhou
2012/02/08 19:04:12
Comment here that the name is expected to be lower
calamity
2012/02/09 00:56:52
Done.
|
| + """ |
| + return '_'.join([x.lower() |
| + for x in re.findall('[A-Z][a-z_]*', name[0].upper() + name[1:])]) |
| - INTEGER = _Info(True) |
| - DOUBLE = _Info(True) |
| - BOOLEAN = _Info(True) |
| - STRING = _Info(True) |
| - ARRAY = _Info(False) |
| - REF = _Info(False) |
| - CHOICES = _Info(False) |
| - OBJECT = _Info(False) |