• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4
5from collections import defaultdict, Mapping
6import traceback
7
8from third_party.json_schema_compiler import json_parse, idl_schema, idl_parser
9
10
11def RemoveNoDocs(item):
12  '''Removes nodes that should not be rendered from an API schema.
13  '''
14  if json_parse.IsDict(item):
15    if item.get('nodoc', False):
16      return True
17    for key, value in item.items():
18      if RemoveNoDocs(value):
19        del item[key]
20  elif type(item) == list:
21    to_remove = []
22    for i in item:
23      if RemoveNoDocs(i):
24        to_remove.append(i)
25    for i in to_remove:
26      item.remove(i)
27  return False
28
29
30def DetectInlineableTypes(schema):
31  '''Look for documents that are only referenced once and mark them as inline.
32  Actual inlining is done by _InlineDocs.
33  '''
34  if not schema.get('types'):
35    return
36
37  ignore = frozenset(('value', 'choices'))
38  refcounts = defaultdict(int)
39  # Use an explicit stack instead of recursion.
40  stack = [schema]
41
42  while stack:
43    node = stack.pop()
44    if isinstance(node, list):
45      stack.extend(node)
46    elif isinstance(node, Mapping):
47      if '$ref' in node:
48        refcounts[node['$ref']] += 1
49      stack.extend(v for k, v in node.iteritems() if k not in ignore)
50
51  for type_ in schema['types']:
52    if not 'noinline_doc' in type_:
53      if refcounts[type_['id']] == 1:
54        type_['inline_doc'] = True
55
56
57def InlineDocs(schema):
58  '''Replace '$ref's that refer to inline_docs with the json for those docs.
59  '''
60  types = schema.get('types')
61  if types is None:
62    return
63
64  inline_docs = {}
65  types_without_inline_doc = []
66
67  # Gather the types with inline_doc.
68  for type_ in types:
69    if type_.get('inline_doc'):
70      inline_docs[type_['id']] = type_
71      for k in ('description', 'id', 'inline_doc'):
72        type_.pop(k, None)
73    else:
74      types_without_inline_doc.append(type_)
75  schema['types'] = types_without_inline_doc
76
77  def apply_inline(node):
78    if isinstance(node, list):
79      for i in node:
80        apply_inline(i)
81    elif isinstance(node, Mapping):
82      ref = node.get('$ref')
83      if ref and ref in inline_docs:
84        node.update(inline_docs[ref])
85        del node['$ref']
86      for k, v in node.iteritems():
87        apply_inline(v)
88
89  apply_inline(schema)
90
91
92def ProcessSchema(path, file_data):
93  '''Parses |file_data| using a method determined by checking the
94  extension of the file at the given |path|. Then, trims 'nodoc' and handles
95  inlineable types from the parsed schema data.
96  '''
97  def trim_and_inline(schema, is_idl=False):
98    '''Modifies an API schema in place by removing nodes that shouldn't be
99    documented and inlining schema types that are only referenced once.
100    '''
101    if RemoveNoDocs(schema):
102      # A return of True signifies that the entire schema should not be
103      # documented. Otherwise, only nodes that request 'nodoc' are removed.
104      return None
105    if is_idl:
106      DetectInlineableTypes(schema)
107    InlineDocs(schema)
108    return schema
109
110  if path.endswith('.idl'):
111    idl = idl_schema.IDLSchema(idl_parser.IDLParser().ParseData(file_data))
112    # Wrap the result in a list so that it behaves like JSON API data.
113    return [trim_and_inline(idl.process()[0], is_idl=True)]
114
115  try:
116    schemas = json_parse.Parse(file_data)
117  except:
118    raise ValueError('Cannot parse "%s" as JSON:\n%s' %
119                     (path, traceback.format_exc()))
120  for schema in schemas:
121    # Schemas could consist of one API schema (data for a specific API file)
122    # or multiple (data from extension_api.json).
123    trim_and_inline(schema)
124  return schemas
125