| # Copyright 2013 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. |
| |
| from collections import defaultdict, Mapping |
| |
| from third_party.json_schema_compiler import json_parse, idl_schema, idl_parser |
| |
| |
| def RemoveNoDocs(item): |
| '''Removes nodes that should not be rendered from an API schema. |
| ''' |
| if json_parse.IsDict(item): |
| if item.get('nodoc', False): |
| return True |
| for key, value in item.items(): |
| if RemoveNoDocs(value): |
| del item[key] |
| elif type(item) == list: |
| to_remove = [] |
| for i in item: |
| if RemoveNoDocs(i): |
| to_remove.append(i) |
| for i in to_remove: |
| item.remove(i) |
| return False |
| |
| |
| def DetectInlineableTypes(schema): |
| '''Look for documents that are only referenced once and mark them as inline. |
| Actual inlining is done by _InlineDocs. |
| ''' |
| if not schema.get('types'): |
| return |
| |
| ignore = frozenset(('value', 'choices')) |
| refcounts = defaultdict(int) |
| # Use an explicit stack instead of recursion. |
| stack = [schema] |
| |
| while stack: |
| node = stack.pop() |
| if isinstance(node, list): |
| stack.extend(node) |
| elif isinstance(node, Mapping): |
| if '$ref' in node: |
| refcounts[node['$ref']] += 1 |
| stack.extend(v for k, v in node.iteritems() if k not in ignore) |
| |
| for type_ in schema['types']: |
| if not 'noinline_doc' in type_: |
| if refcounts[type_['id']] == 1: |
| type_['inline_doc'] = True |
| |
| |
| def InlineDocs(schema): |
| '''Replace '$ref's that refer to inline_docs with the json for those docs. |
| ''' |
| types = schema.get('types') |
| if types is None: |
| return |
| |
| inline_docs = {} |
| types_without_inline_doc = [] |
| |
| # Gather the types with inline_doc. |
| for type_ in types: |
| if type_.get('inline_doc'): |
| inline_docs[type_['id']] = type_ |
| for k in ('description', 'id', 'inline_doc'): |
| type_.pop(k, None) |
| else: |
| types_without_inline_doc.append(type_) |
| schema['types'] = types_without_inline_doc |
| |
| def apply_inline(node): |
| if isinstance(node, list): |
| for i in node: |
| apply_inline(i) |
| elif isinstance(node, Mapping): |
| ref = node.get('$ref') |
| if ref and ref in inline_docs: |
| node.update(inline_docs[ref]) |
| del node['$ref'] |
| for k, v in node.iteritems(): |
| apply_inline(v) |
| |
| apply_inline(schema) |
| |
| |
| def ProcessSchema(path, file_data): |
| '''Parses |file_data| using a method determined by checking the |
| extension of the file at the given |path|. Then, trims 'nodoc' and handles |
| inlineable types from the parsed schema data. |
| ''' |
| def trim_and_inline(schema, is_idl=False): |
| '''Modifies an API schema in place by removing nodes that shouldn't be |
| documented and inlining schema types that are only referenced once. |
| ''' |
| if RemoveNoDocs(schema): |
| # A return of True signifies that the entire schema should not be |
| # documented. Otherwise, only nodes that request 'nodoc' are removed. |
| return None |
| if is_idl: |
| DetectInlineableTypes(schema) |
| InlineDocs(schema) |
| return schema |
| |
| if path.endswith('.idl'): |
| idl = idl_schema.IDLSchema(idl_parser.IDLParser().ParseData(file_data)) |
| return trim_and_inline(idl.process()[0], is_idl=True) |
| |
| schemas = json_parse.Parse(file_data) |
| for schema in schemas: |
| # Schemas could consist of one API schema (data for a specific API file) |
| # or multiple (data from extension_api.json). |
| trim_and_inline(schema) |
| return schemas |