1 # Copyright 2013 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.
5 from collections
import defaultdict
, Mapping
8 from third_party
.json_schema_compiler
import json_parse
, idl_schema
, idl_parser
9 from reference_resolver
import ReferenceResolver
10 from compiled_file_system
import CompiledFileSystem
12 class SchemaProcessorForTest(object):
13 '''Fake SchemaProcessor class. Returns the original schema, without
16 def Process(self
, path
, file_data
):
17 if path
.endswith('.idl'):
18 idl
= idl_schema
.IDLSchema(idl_parser
.IDLParser().ParseData(file_data
))
19 # Wrap the result in a list so that it behaves like JSON API data.
20 return [idl
.process()[0]]
21 return json_parse
.Parse(file_data
)
23 class SchemaProcessorFactoryForTest(object):
24 '''Returns a fake SchemaProcessor class to be used for testing.
26 def Create(self
, retain_inlined_types
):
27 return SchemaProcessorForTest()
30 class SchemaProcessorFactory(object):
31 '''Factory for creating the schema processing utility.
39 self
._reference
_resolver
= reference_resolver
40 self
._api
_models
= api_models
41 self
._features
_bundle
= features_bundle
42 self
._compiled
_fs
_factory
= compiled_fs_factory
43 self
._file
_system
= file_system
45 def Create(self
, retain_inlined_types
):
46 return SchemaProcessor(self
._reference
_resolver
.Get(),
47 self
._api
_models
.Get(),
48 self
._features
_bundle
.Get(),
49 self
._compiled
_fs
_factory
,
54 class SchemaProcessor(object):
55 '''Helper for parsing the API schema.
63 retain_inlined_types
):
64 self
._reference
_resolver
= reference_resolver
65 self
._api
_models
= api_models
66 self
._features
_bundle
= features_bundle
67 self
._retain
_inlined
_types
= retain_inlined_types
68 self
._compiled
_file
_system
= compiled_fs_factory
.Create(
69 file_system
, self
.Process
, SchemaProcessor
, category
='json-cache')
72 def _RemoveNoDocs(self
, item
):
73 '''Removes nodes that should not be rendered from an API schema.
75 if json_parse
.IsDict(item
):
76 if item
.get('nodoc', False):
78 for key
, value
in item
.items():
79 if self
._RemoveNoDocs
(value
):
81 elif type(item
) == list:
84 if self
._RemoveNoDocs
(i
):
91 def _DetectInlineableTypes(self
, schema
):
92 '''Look for documents that are only referenced once and mark them as inline.
93 Actual inlining is done by _InlineDocs.
95 if not schema
.get('types'):
98 ignore
= frozenset(('value', 'choices'))
99 refcounts
= defaultdict(int)
100 # Use an explicit stack instead of recursion.
105 if isinstance(node
, list):
107 elif isinstance(node
, Mapping
):
109 refcounts
[node
['$ref']] += 1
110 stack
.extend(v
for k
, v
in node
.iteritems() if k
not in ignore
)
112 for type_
in schema
['types']:
113 if not 'noinline_doc' in type_
:
114 if refcounts
[type_
['id']] == 1:
115 type_
['inline_doc'] = True
118 def _InlineDocs(self
, schema
):
119 '''Replace '$ref's that refer to inline_docs with the json for those docs.
120 If |retain_inlined_types| is False, then the inlined nodes are removed
124 types_without_inline_doc
= []
127 api_features
= self
._features
_bundle
.GetAPIFeatures().Get()
128 # We don't want to inline the events API, as it's handled differently
129 # Also, the webviewTag API is handled differently, as it only exists
130 # for the purpose of documentation, it's not a true internal api
131 namespace
= schema
.get('namespace', '')
132 if namespace
!= 'events' and namespace
!= 'webviewTag':
133 internal_api
= api_features
.get(schema
.get('namespace', ''), {}).get(
137 # Gather refs to internal APIs
138 def gather_api_refs(node
):
139 if isinstance(node
, list):
142 elif isinstance(node
, Mapping
):
143 ref
= node
.get('$ref')
146 for k
, v
in node
.iteritems():
148 gather_api_refs(schema
)
150 if len(api_refs
) > 0:
151 api_list
= self
._api
_models
.GetNames()
152 api_name
= schema
.get('namespace', '')
153 self
._api
_stack
.append(api_name
)
154 for api
in self
._api
_stack
:
158 model
, node_info
= self
._reference
_resolver
.GetRefModel(ref
, api_list
)
159 if model
and api_features
.get(model
.name
, {}).get('internal', False):
160 category
, name
= node_info
161 for ref_schema
in self
._compiled
_file
_system
.GetFromFile(
162 model
.source_file
).Get():
163 if category
== 'type':
164 for type_json
in ref_schema
.get('types'):
165 if type_json
['id'] == name
:
166 inline_docs
[ref
] = type_json
167 elif category
== 'event':
168 for type_json
in ref_schema
.get('events'):
169 if type_json
['name'] == name
:
170 inline_docs
[ref
] = type_json
171 self
._api
_stack
.remove(api_name
)
173 types
= schema
.get('types')
175 # Gather the types with inline_doc.
177 if type_
.get('inline_doc'):
178 inline_docs
[type_
['id']] = type_
179 if not self
._retain
_inlined
_types
:
180 for k
in ('description', 'id', 'inline_doc'):
183 inline_docs
[type_
['id']] = type_
184 # For internal apis that are not inline_doc we want to retain them
185 # in the schema (i.e. same behaviour as remain_inlined_types)
186 types_without_inline_doc
.append(type_
)
188 types_without_inline_doc
.append(type_
)
189 if not self
._retain
_inlined
_types
:
190 schema
['types'] = types_without_inline_doc
192 def apply_inline(node
):
193 if isinstance(node
, list):
196 elif isinstance(node
, Mapping
):
197 ref
= node
.get('$ref')
198 if ref
and ref
in inline_docs
:
199 node
.update(inline_docs
[ref
])
201 for k
, v
in node
.iteritems():
207 def Process(self
, path
, file_data
):
208 '''Parses |file_data| using a method determined by checking the
209 extension of the file at the given |path|. Then, trims 'nodoc' and if
210 |self.retain_inlined_types| is given and False, removes inlineable types
211 from the parsed schema data.
213 def trim_and_inline(schema
, is_idl
=False):
214 '''Modifies an API schema in place by removing nodes that shouldn't be
215 documented and inlining schema types that are only referenced once.
217 if self
._RemoveNoDocs
(schema
):
218 # A return of True signifies that the entire schema should not be
219 # documented. Otherwise, only nodes that request 'nodoc' are removed.
222 self
._DetectInlineableTypes
(schema
)
223 self
._InlineDocs
(schema
)
226 if path
.endswith('.idl'):
227 idl
= idl_schema
.IDLSchema(
228 idl_parser
.IDLParser().ParseData(file_data
))
229 # Wrap the result in a list so that it behaves like JSON API data.
230 return [trim_and_inline(idl
.process()[0], is_idl
=True)]
233 schemas
= json_parse
.Parse(file_data
)
235 raise ValueError('Cannot parse "%s" as JSON:\n%s' %
236 (path
, traceback
.format_exc()))
237 for schema
in schemas
:
238 # Schemas could consist of one API schema (data for a specific API file)
239 # or multiple (data from extension_api.json).
240 trim_and_inline(schema
)