model.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903
  1. # Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2. # Use of this source code is governed by a BSD-style license that can be
  3. # found in the LICENSE file.
  4. from __future__ import print_function
  5. import os.path
  6. from json_parse import OrderedDict
  7. from memoize import memoize
  8. def _IsTypeFromManifestKeys(namespace, typename, fallback):
  9. # type(Namespace, str, bool) -> bool
  10. """Computes whether 'from_manifest_keys' is true for the given type.
  11. """
  12. if typename in namespace._manifest_referenced_types:
  13. return True
  14. return fallback
  15. class ParseException(Exception):
  16. """Thrown when data in the model is invalid.
  17. """
  18. def __init__(self, parent, message):
  19. hierarchy = _GetModelHierarchy(parent)
  20. hierarchy.append(message)
  21. Exception.__init__(
  22. self, 'Model parse exception at:\n' + '\n'.join(hierarchy))
  23. class Model(object):
  24. """Model of all namespaces that comprise an API.
  25. Properties:
  26. - |namespaces| a map of a namespace name to its model.Namespace
  27. """
  28. def __init__(self, allow_inline_enums=True):
  29. self._allow_inline_enums = allow_inline_enums
  30. self.namespaces = {}
  31. def AddNamespace(self,
  32. json,
  33. source_file,
  34. include_compiler_options=False,
  35. environment=None):
  36. """Add a namespace's json to the model and returns the namespace.
  37. """
  38. namespace = Namespace(json,
  39. source_file,
  40. include_compiler_options=include_compiler_options,
  41. environment=environment,
  42. allow_inline_enums=self._allow_inline_enums)
  43. self.namespaces[namespace.name] = namespace
  44. return namespace
  45. def CreateFeature(name, model):
  46. if isinstance(model, dict):
  47. return SimpleFeature(name, model)
  48. return ComplexFeature(name, [SimpleFeature(name, child) for child in model])
  49. class ComplexFeature(object):
  50. """A complex feature which may be made of several simple features.
  51. Properties:
  52. - |name| the name of the feature
  53. - |unix_name| the unix_name of the feature
  54. - |feature_list| a list of simple features which make up the feature
  55. """
  56. def __init__(self, feature_name, features):
  57. self.name = feature_name
  58. self.unix_name = UnixName(self.name)
  59. self.feature_list = features
  60. class SimpleFeature(object):
  61. """A simple feature, which can make up a complex feature, as specified in
  62. files such as chrome/common/extensions/api/_permission_features.json.
  63. Properties:
  64. - |name| the name of the feature
  65. - |unix_name| the unix_name of the feature
  66. - |channel| the channel where the feature is released
  67. - |extension_types| the types which can use the feature
  68. - |allowlist| a list of extensions allowed to use the feature
  69. """
  70. def __init__(self, feature_name, feature_def):
  71. self.name = feature_name
  72. self.unix_name = UnixName(self.name)
  73. self.channel = feature_def['channel']
  74. self.extension_types = feature_def['extension_types']
  75. self.allowlist = feature_def.get('allowlist')
  76. class Namespace(object):
  77. """An API namespace.
  78. Properties:
  79. - |name| the name of the namespace
  80. - |description| the description of the namespace
  81. - |deprecated| a reason and possible alternative for a deprecated api
  82. - |unix_name| the unix_name of the namespace
  83. - |source_file| the file that contained the namespace definition
  84. - |source_file_dir| the directory component of |source_file|
  85. - |source_file_filename| the filename component of |source_file|
  86. - |platforms| if not None, the list of platforms that the namespace is
  87. available to
  88. - |types| a map of type names to their model.Type
  89. - |functions| a map of function names to their model.Function
  90. - |events| a map of event names to their model.Function
  91. - |properties| a map of property names to their model.Property
  92. - |compiler_options| the compiler_options dict, only not empty if
  93. |include_compiler_options| is True
  94. - |manifest_keys| is a Type representing the manifest keys for this namespace.
  95. """
  96. def __init__(self,
  97. json,
  98. source_file,
  99. include_compiler_options=False,
  100. environment=None,
  101. allow_inline_enums=True):
  102. self.name = json['namespace']
  103. if 'description' not in json:
  104. # TODO(kalman): Go back to throwing an error here.
  105. print('%s must have a "description" field. This will appear '
  106. 'on the API summary page.' % self.name)
  107. json['description'] = ''
  108. self.description = json['description']
  109. self.nodoc = json.get('nodoc', False)
  110. self.deprecated = json.get('deprecated', None)
  111. self.unix_name = UnixName(self.name)
  112. self.source_file = source_file
  113. self.source_file_dir, self.source_file_filename = os.path.split(source_file)
  114. self.short_filename = os.path.basename(source_file).split('.')[0]
  115. self.parent = None
  116. self.allow_inline_enums = allow_inline_enums
  117. self.platforms = _GetPlatforms(json)
  118. toplevel_origin = Origin(from_client=True, from_json=True)
  119. # While parsing manifest keys, we store all the types referenced by manifest
  120. # keys. This is useful for computing the correct Origin for types in the
  121. # namespace. This also necessitates parsing manifest keys before types.
  122. self._manifest_referenced_types = set()
  123. self.manifest_keys = _GetManifestKeysType(self, json)
  124. self.types = _GetTypes(self, json, self, toplevel_origin)
  125. self.functions = _GetFunctions(self, json, self)
  126. self.events = _GetEvents(self, json, self)
  127. self.properties = _GetProperties(self, json, self, toplevel_origin)
  128. if include_compiler_options:
  129. self.compiler_options = json.get('compiler_options', {})
  130. else:
  131. self.compiler_options = {}
  132. self.environment = environment
  133. self.documentation_options = json.get('documentation_options', {})
  134. class Origin(object):
  135. """Stores the possible origin of model object as a pair of bools. These are:
  136. |from_client| indicating that instances can originate from users of
  137. generated code (for example, function results), or
  138. |from_json| indicating that instances can originate from the JSON (for
  139. example, function parameters)
  140. |from_manifest_keys| indicating that instances for this type can be parsed
  141. from manifest keys.
  142. It is possible for model objects to originate from both the client and json,
  143. for example Types defined in the top-level schema, in which case both
  144. |from_client| and |from_json| would be True.
  145. """
  146. def __init__(self,
  147. from_client=False,
  148. from_json=False,
  149. from_manifest_keys=False):
  150. if not from_client and not from_json and not from_manifest_keys:
  151. raise ValueError(
  152. 'One of (from_client, from_json, from_manifest_keys) must be true')
  153. self.from_client = from_client
  154. self.from_json = from_json
  155. self.from_manifest_keys = from_manifest_keys
  156. class Type(object):
  157. """A Type defined in the json.
  158. Properties:
  159. - |name| the type name
  160. - |namespace| the Type's namespace
  161. - |description| the description of the type (if provided)
  162. - |properties| a map of property unix_names to their model.Property
  163. - |functions| a map of function names to their model.Function
  164. - |events| a map of event names to their model.Event
  165. - |origin| the Origin of the type
  166. - |property_type| the PropertyType of this Type
  167. - |item_type| if this is an array, the type of items in the array
  168. - |simple_name| the name of this Type without a namespace
  169. - |additional_properties| the type of the additional properties, if any is
  170. specified
  171. """
  172. def __init__(self,
  173. parent,
  174. name,
  175. json,
  176. namespace,
  177. input_origin):
  178. self.name = name
  179. # The typename "ManifestKeys" is reserved.
  180. if name == 'ManifestKeys':
  181. assert parent == namespace and input_origin.from_manifest_keys, \
  182. 'ManifestKeys type is reserved'
  183. self.namespace = namespace
  184. self.simple_name = _StripNamespace(self.name, namespace)
  185. self.unix_name = UnixName(self.name)
  186. self.description = json.get('description', None)
  187. self.jsexterns = json.get('jsexterns', None)
  188. self.nodoc = json.get('nodoc', False)
  189. # Copy the Origin and override the |from_manifest_keys| value as necessary.
  190. # We need to do this to ensure types reference by manifest types have the
  191. # correct value for |origin.from_manifest_keys|.
  192. self.origin = Origin(
  193. input_origin.from_client, input_origin.from_json,
  194. _IsTypeFromManifestKeys(namespace, name, input_origin.from_manifest_keys))
  195. self.parent = parent
  196. self.instance_of = json.get('isInstanceOf', None)
  197. self.is_serializable_function = json.get('serializableFunction', False)
  198. # TODO(kalman): Only objects need functions/events/properties, but callers
  199. # assume that all types have them. Fix this.
  200. self.functions = _GetFunctions(self, json, namespace)
  201. self.events = _GetEvents(self, json, namespace)
  202. self.properties = _GetProperties(self, json, namespace, self.origin)
  203. json_type = json.get('type', None)
  204. if json_type == 'array':
  205. self.property_type = PropertyType.ARRAY
  206. self.item_type = Type(self, '%sType' % name, json['items'], namespace,
  207. self.origin)
  208. elif '$ref' in json:
  209. self.property_type = PropertyType.REF
  210. self.ref_type = json['$ref']
  211. # Record all types referenced by manifest types so that the proper Origin
  212. # can be set for them during type parsing.
  213. if self.origin.from_manifest_keys:
  214. namespace._manifest_referenced_types.add(self.ref_type)
  215. elif 'enum' in json and json_type == 'string':
  216. if not namespace.allow_inline_enums and not isinstance(parent, Namespace):
  217. raise ParseException(
  218. self,
  219. 'Inline enum "%s" found in namespace "%s". These are not allowed. '
  220. 'See crbug.com/472279' % (name, namespace.name))
  221. self.property_type = PropertyType.ENUM
  222. self.enum_values = [EnumValue(value) for value in json['enum']]
  223. self.cpp_enum_prefix_override = json.get('cpp_enum_prefix_override', None)
  224. elif json_type == 'any':
  225. self.property_type = PropertyType.ANY
  226. elif json_type == 'binary':
  227. self.property_type = PropertyType.BINARY
  228. elif json_type == 'boolean':
  229. self.property_type = PropertyType.BOOLEAN
  230. elif json_type == 'integer':
  231. self.property_type = PropertyType.INTEGER
  232. elif (json_type == 'double' or
  233. json_type == 'number'):
  234. self.property_type = PropertyType.DOUBLE
  235. elif json_type == 'string':
  236. self.property_type = PropertyType.STRING
  237. elif 'choices' in json:
  238. self.property_type = PropertyType.CHOICES
  239. def generate_type_name(type_json):
  240. if 'items' in type_json:
  241. return '%ss' % generate_type_name(type_json['items'])
  242. if '$ref' in type_json:
  243. return type_json['$ref']
  244. if 'type' in type_json:
  245. return type_json['type']
  246. return None
  247. self.choices = [
  248. Type(self,
  249. generate_type_name(choice) or 'choice%s' % i,
  250. choice,
  251. namespace,
  252. self.origin)
  253. for i, choice in enumerate(json['choices'])]
  254. elif json_type == 'object':
  255. if not (
  256. 'isInstanceOf' in json or
  257. 'properties' in json or
  258. 'additionalProperties' in json or
  259. 'functions' in json or
  260. 'events' in json):
  261. raise ParseException(self, name + " has no properties or functions")
  262. self.property_type = PropertyType.OBJECT
  263. additional_properties_json = json.get('additionalProperties', None)
  264. if additional_properties_json is not None:
  265. self.additional_properties = Type(self,
  266. 'additionalProperties',
  267. additional_properties_json,
  268. namespace,
  269. self.origin)
  270. else:
  271. self.additional_properties = None
  272. elif json_type == 'function':
  273. self.property_type = PropertyType.FUNCTION
  274. # Sometimes we might have an unnamed function, e.g. if it's a property
  275. # of an object. Use the name of the property in that case.
  276. function_name = json.get('name', name)
  277. self.function = Function(
  278. self, function_name, json, namespace, self.origin)
  279. else:
  280. raise ParseException(self, 'Unsupported JSON type %s' % json_type)
  281. def IsRootManifestKeyType(self):
  282. # type: () -> boolean
  283. ''' Returns true if this type corresponds to the top level ManifestKeys
  284. type.
  285. '''
  286. return self.name == 'ManifestKeys'
  287. class Function(object):
  288. """A Function defined in the API.
  289. Properties:
  290. - |name| the function name
  291. - |platforms| if not None, the list of platforms that the function is
  292. available to
  293. - |params| a list of parameters to the function (order matters). A separate
  294. parameter is used for each choice of a 'choices' parameter
  295. - |deprecated| a reason and possible alternative for a deprecated function
  296. - |description| a description of the function (if provided)
  297. - |returns_async| an asynchronous return for the function. This may be
  298. specified either though the returns_async field or a
  299. callback function at the end of the parameters, but not both
  300. - |optional| whether the Function is "optional"; this only makes sense to be
  301. present when the Function is representing a callback property
  302. - |simple_name| the name of this Function without a namespace
  303. - |returns| the return type of the function; None if the function does not
  304. return a value
  305. """
  306. def __init__(self,
  307. parent,
  308. name,
  309. json,
  310. namespace,
  311. origin):
  312. self.name = name
  313. self.simple_name = _StripNamespace(self.name, namespace)
  314. self.platforms = _GetPlatforms(json)
  315. self.params = []
  316. self.description = json.get('description')
  317. self.deprecated = json.get('deprecated')
  318. self.returns_async = None
  319. self.optional = _GetWithDefaultChecked(parent, json, 'optional', False)
  320. self.parent = parent
  321. self.nocompile = json.get('nocompile')
  322. options = json.get('options', {})
  323. self.conditions = options.get('conditions', [])
  324. self.actions = options.get('actions', [])
  325. self.supports_listeners = options.get('supportsListeners', True)
  326. self.supports_rules = options.get('supportsRules', False)
  327. self.supports_dom = options.get('supportsDom', False)
  328. self.nodoc = json.get('nodoc', False)
  329. def GeneratePropertyFromParam(p):
  330. return Property(self, p['name'], p, namespace, origin)
  331. self.filters = [GeneratePropertyFromParam(filter_instance)
  332. for filter_instance in json.get('filters', [])]
  333. returns_async = json.get('returns_async', None)
  334. if returns_async:
  335. returns_async_params = returns_async.get('parameters')
  336. if (returns_async_params is None):
  337. raise ValueError(
  338. 'parameters key not specified on returns_async: %s.%s in %s' %
  339. (namespace.name, name, namespace.source_file))
  340. if len(returns_async_params) > 1:
  341. raise ValueError('Only a single parameter can be specific on '
  342. 'returns_async: %s.%s in %s' %
  343. (namespace.name, name, namespace.source_file))
  344. self.returns_async = ReturnsAsync(self, returns_async, namespace,
  345. Origin(from_client=True), True)
  346. # TODO(https://crbug.com/1143032): Returning a synchronous value is
  347. # incompatible with returning a promise. There are APIs that specify this,
  348. # though. Some appear to be incorrectly specified (i.e., don't return a
  349. # value, but claim to), but others actually do return something. We'll
  350. # need to handle those when converting them to allow promises.
  351. if json.get('returns') is not None:
  352. raise ValueError(
  353. 'Cannot specify both returns and returns_async: %s.%s'
  354. % (namespace.name, name))
  355. params = json.get('parameters', [])
  356. callback_param = None
  357. for i, param in enumerate(params):
  358. # We consider the final function argument to the API to be the callback
  359. # parameter if returns_async wasn't specified. Otherwise, we consider all
  360. # function arguments to just be properties.
  361. if i == len(params) - 1 and param.get(
  362. 'type') == 'function' and not self.returns_async:
  363. callback_param = param
  364. else:
  365. # Treat all intermediate function arguments as properties. Certain APIs,
  366. # such as the webstore, have these.
  367. self.params.append(GeneratePropertyFromParam(param))
  368. if callback_param:
  369. # Even though we are creating a ReturnsAsync type here, this does not
  370. # support being returned via a Promise, as this is implied by
  371. # "returns_async" being found in the JSON.
  372. # This is just a holder type for the callback.
  373. self.returns_async = ReturnsAsync(self, callback_param, namespace,
  374. Origin(from_client=True), False)
  375. self.returns = None
  376. if 'returns' in json:
  377. self.returns = Type(self,
  378. '%sReturnType' % name,
  379. json['returns'],
  380. namespace,
  381. origin)
  382. class ReturnsAsync(object):
  383. """A structure documenting asynchronous return values (through a callback or
  384. promise) for an API function.
  385. Properties:
  386. - |name| the name of the asynchronous return, generally 'callback'
  387. - |simple_name| the name of this ReturnsAsync without a namespace
  388. - |description| a description of the ReturnsAsync (if provided)
  389. - |optional| whether specifying the ReturnsAsync is "optional" (in situations
  390. where promises are supported, this will be ignored as promises
  391. inheriently make a callback optional)
  392. - |params| a list of parameters supplied to the function in the case of using
  393. callbacks, or the list of properties on the returned object in the
  394. case of using promises
  395. - |can_return_promise| whether this can be treated as a Promise as well as
  396. callback
  397. """
  398. def __init__(self, parent, json, namespace, origin, can_return_promise):
  399. self.name = json.get('name')
  400. self.simple_name = _StripNamespace(self.name, namespace)
  401. self.description = json.get('description')
  402. self.optional = _GetWithDefaultChecked(parent, json, 'optional', False)
  403. self.nocompile = json.get('nocompile')
  404. self.parent = parent
  405. self.can_return_promise = can_return_promise
  406. if json.get('returns') is not None:
  407. raise ValueError('Cannot return a value from an asynchronous return: '
  408. '%s.%s' % (namespace.name, self.name))
  409. if json.get('deprecated') is not None:
  410. raise ValueError('Cannot specify deprecated on an asynchronous return: '
  411. '%s.%s' % (namespace.name, self.name))
  412. def GeneratePropertyFromParam(p):
  413. return Property(self, p['name'], p, namespace, origin)
  414. params = json.get('parameters', [])
  415. self.params = []
  416. for _, param in enumerate(params):
  417. self.params.append(GeneratePropertyFromParam(param))
  418. class Property(object):
  419. """A property of a type OR a parameter to a function.
  420. Properties:
  421. - |name| name of the property as in the json. This shouldn't change since
  422. it is the key used to access DictionaryValues
  423. - |unix_name| the unix_style_name of the property. Used as variable name
  424. - |optional| a boolean representing whether the property is optional
  425. - |description| a description of the property (if provided)
  426. - |type_| the model.Type of this property
  427. - |simple_name| the name of this Property without a namespace
  428. - |deprecated| a reason and possible alternative for a deprecated property
  429. """
  430. def __init__(self, parent, name, json, namespace, origin):
  431. """Creates a Property from JSON.
  432. """
  433. self.parent = parent
  434. self.name = name
  435. self._unix_name = UnixName(self.name)
  436. self._unix_name_used = False
  437. self.origin = origin
  438. self.simple_name = _StripNamespace(self.name, namespace)
  439. self.description = json.get('description', None)
  440. self.optional = json.get('optional', None)
  441. self.instance_of = json.get('isInstanceOf', None)
  442. self.deprecated = json.get('deprecated')
  443. self.nodoc = json.get('nodoc', False)
  444. # HACK: only support very specific value types.
  445. is_allowed_value = (
  446. '$ref' not in json and
  447. ('type' not in json or json['type'] == 'integer'
  448. or json['type'] == 'number'
  449. or json['type'] == 'string'))
  450. self.value = None
  451. if 'value' in json and is_allowed_value:
  452. self.value = json['value']
  453. if 'type' not in json:
  454. # Sometimes the type of the value is left out, and we need to figure
  455. # it out for ourselves.
  456. if isinstance(self.value, int):
  457. json['type'] = 'integer'
  458. elif isinstance(self.value, float):
  459. json['type'] = 'double'
  460. elif isinstance(self.value, basestring):
  461. json['type'] = 'string'
  462. else:
  463. # TODO(kalman): support more types as necessary.
  464. raise ParseException(
  465. parent,
  466. '"%s" is not a supported type for "value"' % type(self.value))
  467. self.type_ = Type(parent, name, json, namespace, origin)
  468. def GetUnixName(self):
  469. """Gets the property's unix_name. Raises AttributeError if not set.
  470. """
  471. if not self._unix_name:
  472. raise AttributeError('No unix_name set on %s' % self.name)
  473. self._unix_name_used = True
  474. return self._unix_name
  475. def SetUnixName(self, unix_name):
  476. """Set the property's unix_name. Raises AttributeError if the unix_name has
  477. already been used (GetUnixName has been called).
  478. """
  479. if unix_name == self._unix_name:
  480. return
  481. if self._unix_name_used:
  482. raise AttributeError(
  483. 'Cannot set the unix_name on %s; '
  484. 'it is already used elsewhere as %s' %
  485. (self.name, self._unix_name))
  486. self._unix_name = unix_name
  487. unix_name = property(GetUnixName, SetUnixName)
  488. class EnumValue(object):
  489. """A single value from an enum.
  490. Properties:
  491. - |name| name of the property as in the json.
  492. - |description| a description of the property (if provided)
  493. """
  494. def __init__(self, json):
  495. if isinstance(json, dict):
  496. self.name = json['name']
  497. self.description = json.get('description')
  498. else:
  499. self.name = json
  500. self.description = None
  501. def CamelName(self):
  502. return CamelName(self.name)
  503. class _Enum(object):
  504. """Superclass for enum types with a "name" field, setting up repr/eq/ne.
  505. Enums need to do this so that equality/non-equality work over pickling.
  506. """
  507. @staticmethod
  508. def GetAll(cls):
  509. """Yields all _Enum objects declared in |cls|.
  510. """
  511. for prop_key in dir(cls):
  512. prop_value = getattr(cls, prop_key)
  513. if isinstance(prop_value, _Enum):
  514. yield prop_value
  515. def __init__(self, name):
  516. self.name = name
  517. def __eq__(self, other):
  518. return type(other) == type(self) and other.name == self.name
  519. def __ne__(self, other):
  520. return not (self == other)
  521. def __repr__(self):
  522. return self.name
  523. def __str__(self):
  524. return repr(self)
  525. def __hash__(self):
  526. return hash(self.name)
  527. class _PropertyTypeInfo(_Enum):
  528. def __init__(self, is_fundamental, name):
  529. _Enum.__init__(self, name)
  530. self.is_fundamental = is_fundamental
  531. def __repr__(self):
  532. return self.name
  533. class PropertyType(object):
  534. """Enum of different types of properties/parameters.
  535. """
  536. ANY = _PropertyTypeInfo(False, "any")
  537. ARRAY = _PropertyTypeInfo(False, "array")
  538. BINARY = _PropertyTypeInfo(False, "binary")
  539. BOOLEAN = _PropertyTypeInfo(True, "boolean")
  540. CHOICES = _PropertyTypeInfo(False, "choices")
  541. DOUBLE = _PropertyTypeInfo(True, "double")
  542. ENUM = _PropertyTypeInfo(False, "enum")
  543. FUNCTION = _PropertyTypeInfo(False, "function")
  544. INT64 = _PropertyTypeInfo(True, "int64")
  545. INTEGER = _PropertyTypeInfo(True, "integer")
  546. OBJECT = _PropertyTypeInfo(False, "object")
  547. REF = _PropertyTypeInfo(False, "ref")
  548. STRING = _PropertyTypeInfo(True, "string")
  549. def IsCPlusPlusKeyword(name):
  550. """Returns true if `name` is a C++ reserved keyword.
  551. """
  552. # Obtained from https://en.cppreference.com/w/cpp/keyword.
  553. keywords = {
  554. "alignas",
  555. "alignof",
  556. "and",
  557. "and_eq",
  558. "asm",
  559. "atomic_cancel",
  560. "atomic_commit",
  561. "atomic_noexcept",
  562. "auto",
  563. "bitand",
  564. "bitor",
  565. "bool",
  566. "break",
  567. "case",
  568. "catch",
  569. "char",
  570. "char8_t",
  571. "char16_t",
  572. "char32_t",
  573. "class",
  574. "compl",
  575. "concept",
  576. "const",
  577. "consteval",
  578. "constexpr",
  579. "constinit",
  580. "const_cast",
  581. "continue",
  582. "co_await",
  583. "co_return",
  584. "co_yield",
  585. "decltype",
  586. "default",
  587. "delete",
  588. "do",
  589. "double",
  590. "dynamic_cast",
  591. "else",
  592. "enum",
  593. "explicit",
  594. "export",
  595. "extern",
  596. "false",
  597. "float",
  598. "for",
  599. "friend",
  600. "goto",
  601. "if",
  602. "inline",
  603. "int",
  604. "long",
  605. "mutable",
  606. "namespace",
  607. "new",
  608. "noexcept",
  609. "not",
  610. "not_eq",
  611. "nullptr",
  612. "operator",
  613. "or",
  614. "or_eq",
  615. "private",
  616. "protected",
  617. "public",
  618. "reflexpr",
  619. "register",
  620. "reinterpret_cast",
  621. "requires",
  622. "return",
  623. "short",
  624. "signed",
  625. "sizeof",
  626. "static",
  627. "static_assert",
  628. "static_cast",
  629. "struct",
  630. "switch",
  631. "synchronized",
  632. "template",
  633. "this",
  634. "thread_local",
  635. "throw",
  636. "true",
  637. "try",
  638. "typedef",
  639. "typeid",
  640. "typename",
  641. "union",
  642. "unsigned",
  643. "using",
  644. "virtual",
  645. "void",
  646. "volatile",
  647. "wchar_t",
  648. "while",
  649. "xor",
  650. "xor_eq"
  651. }
  652. return name in keywords
  653. @memoize
  654. def UnixName(name):
  655. '''Returns the unix_style name for a given lowerCamelCase string.
  656. '''
  657. # Append an extra underscore to the |name|'s end if it's a reserved C++
  658. # keyword in order to avoid compilation errors in generated code.
  659. # Note: In some cases, this is overly greedy, because the unix name is
  660. # appended to another string (such as in choices, where it becomes
  661. # "as_double_"). We can fix this if this situation becomes common, but for now
  662. # it's only hit in tests, and not worth the complexity.
  663. if IsCPlusPlusKeyword(name):
  664. name = name + '_'
  665. # Prepend an extra underscore to the |name|'s start if it doesn't start with a
  666. # letter or underscore to ensure the generated unix name follows C++
  667. # identifier rules.
  668. assert(name)
  669. if name[0].isdigit():
  670. name = '_' + name
  671. unix_name = []
  672. for i, c in enumerate(name):
  673. if c.isupper() and i > 0 and name[i - 1] != '_':
  674. # Replace lowerUpper with lower_Upper.
  675. if name[i - 1].islower():
  676. unix_name.append('_')
  677. # Replace ACMEWidgets with ACME_Widgets
  678. elif i + 1 < len(name) and name[i + 1].islower():
  679. unix_name.append('_')
  680. if c == '.':
  681. # Replace hello.world with hello_world.
  682. unix_name.append('_')
  683. else:
  684. # Everything is lowercase.
  685. unix_name.append(c.lower())
  686. return ''.join(unix_name)
  687. @memoize
  688. def CamelName(snake):
  689. ''' Converts a snake_cased_string to a camelCasedOne. '''
  690. pieces = snake.split('_')
  691. camel = []
  692. for i, piece in enumerate(pieces):
  693. if i == 0:
  694. camel.append(piece)
  695. else:
  696. camel.append(piece.capitalize())
  697. return ''.join(camel)
  698. def _StripNamespace(name, namespace):
  699. if name.startswith(namespace.name + '.'):
  700. return name[len(namespace.name + '.'):]
  701. return name
  702. def _GetModelHierarchy(entity):
  703. """Returns the hierarchy of the given model entity."""
  704. hierarchy = []
  705. while entity is not None:
  706. hierarchy.append(getattr(entity, 'name', repr(entity)))
  707. if isinstance(entity, Namespace):
  708. hierarchy.insert(0, ' in %s' % entity.source_file)
  709. entity = getattr(entity, 'parent', None)
  710. hierarchy.reverse()
  711. return hierarchy
  712. def _GetTypes(parent, json, namespace, origin):
  713. """Creates Type objects extracted from |json|.
  714. """
  715. assert hasattr(namespace, 'manifest_keys'), \
  716. 'Types should be parsed after parsing manifest keys.'
  717. types = OrderedDict()
  718. for type_json in json.get('types', []):
  719. type_ = Type(parent, type_json['id'], type_json, namespace, origin)
  720. types[type_.name] = type_
  721. return types
  722. def _GetFunctions(parent, json, namespace):
  723. """Creates Function objects extracted from |json|.
  724. """
  725. functions = OrderedDict()
  726. for function_json in json.get('functions', []):
  727. function = Function(parent,
  728. function_json['name'],
  729. function_json,
  730. namespace,
  731. Origin(from_json=True))
  732. functions[function.name] = function
  733. return functions
  734. def _GetEvents(parent, json, namespace):
  735. """Creates Function objects generated from the events in |json|.
  736. """
  737. events = OrderedDict()
  738. for event_json in json.get('events', []):
  739. event = Function(parent,
  740. event_json['name'],
  741. event_json,
  742. namespace,
  743. Origin(from_client=True))
  744. events[event.name] = event
  745. return events
  746. def _GetProperties(parent, json, namespace, origin):
  747. """Generates Property objects extracted from |json|.
  748. """
  749. properties = OrderedDict()
  750. for name, property_json in json.get('properties', {}).items():
  751. properties[name] = Property(parent, name, property_json, namespace, origin)
  752. return properties
  753. def _GetManifestKeysType(self, json):
  754. # type: (OrderedDict) -> Type
  755. """Returns the Type for manifest keys parsing, or None if there are no
  756. manifest keys in this namespace.
  757. """
  758. if not json.get('manifest_keys'):
  759. return None
  760. # Create a dummy object to parse "manifest_keys" as a type.
  761. manifest_keys_type = {
  762. 'type': 'object',
  763. 'properties': json['manifest_keys'],
  764. }
  765. return Type(self, 'ManifestKeys', manifest_keys_type, self,
  766. Origin(from_manifest_keys=True))
  767. def _GetWithDefaultChecked(self, json, key, default):
  768. if json.get(key) == default:
  769. raise ParseException(
  770. self, 'The attribute "%s" is specified as "%s", but this is the '
  771. 'default value if the attribute is not included. It should be removed.'
  772. % (key, default))
  773. return json.get(key, default)
  774. class _PlatformInfo(_Enum):
  775. def __init__(self, name):
  776. _Enum.__init__(self, name)
  777. class Platforms(object):
  778. """Enum of the possible platforms.
  779. """
  780. CHROMEOS = _PlatformInfo("chromeos")
  781. FUCHSIA = _PlatformInfo("fuchsia")
  782. LACROS = _PlatformInfo("lacros")
  783. LINUX = _PlatformInfo("linux")
  784. MAC = _PlatformInfo("mac")
  785. WIN = _PlatformInfo("win")
  786. def _GetPlatforms(json):
  787. if 'platforms' not in json or json['platforms'] == None:
  788. return None
  789. # Sanity check: platforms should not be an empty list.
  790. if not json['platforms']:
  791. raise ValueError('"platforms" cannot be an empty list')
  792. platforms = []
  793. for platform_name in json['platforms']:
  794. platform_enum = None
  795. for platform in _Enum.GetAll(Platforms):
  796. if platform_name == platform.name:
  797. platform_enum = platform
  798. break
  799. if not platform_enum:
  800. raise ValueError('Invalid platform specified: ' + platform_name)
  801. platforms.append(platform_enum)
  802. return platforms