Skip to content

Commit 4075196

Browse files
committed
Merge commit for internal changes
2 parents c2d361f + b61f4d7 commit 4075196

File tree

7 files changed

+1653
-0
lines changed

7 files changed

+1653
-0
lines changed

BUILD

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,74 @@ py_binary(
1616
deps = ["//tensorflow:tensorflow_py"],
1717
)
1818

19+
py_library(
20+
name = "doc_generator_visitor",
21+
srcs = [
22+
"doc_generator_visitor.py",
23+
],
24+
srcs_version = "PY2AND3",
25+
)
26+
27+
py_test(
28+
name = "doc_generator_visitor_test",
29+
size = "small",
30+
srcs = [
31+
"doc_generator_visitor_test.py",
32+
],
33+
srcs_version = "PY2AND3",
34+
deps = [
35+
":doc_generator_visitor",
36+
"//tensorflow/python:platform_test",
37+
],
38+
)
39+
40+
py_library(
41+
name = "parser",
42+
srcs = [
43+
"parser.py",
44+
],
45+
srcs_version = "PY2AND3",
46+
)
47+
48+
py_test(
49+
name = "parser_test",
50+
size = "small",
51+
srcs = [
52+
"parser_test.py",
53+
],
54+
srcs_version = "PY2AND3",
55+
deps = [
56+
":parser",
57+
"//tensorflow/python:platform_test",
58+
],
59+
)
60+
61+
py_binary(
62+
name = "generate",
63+
srcs = ["generate.py"],
64+
srcs_version = "PY2AND3",
65+
deps = [
66+
"//tensorflow:tensorflow_py",
67+
"//tensorflow/tools/common:public_api",
68+
"//tensorflow/tools/common:traverse",
69+
"//tensorflow/tools/docs:doc_generator_visitor",
70+
"//tensorflow/tools/docs:parser",
71+
],
72+
)
73+
74+
py_test(
75+
name = "generate_test",
76+
size = "small",
77+
srcs = [
78+
"generate_test.py",
79+
],
80+
srcs_version = "PY2AND3",
81+
deps = [
82+
":generate",
83+
"//tensorflow/python:platform_test",
84+
],
85+
)
86+
1987
filegroup(
2088
name = "doxy_config",
2189
srcs = ["tf-doxy_for_md-config"],

doc_generator_visitor.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
# ==============================================================================
15+
"""A `traverse` visitor for processing documentation."""
16+
17+
from __future__ import absolute_import
18+
from __future__ import division
19+
from __future__ import print_function
20+
21+
import inspect
22+
23+
24+
class DocGeneratorVisitor(object):
25+
"""A visitor that generates docs for a python object when __call__ed."""
26+
27+
def __init__(self):
28+
self._index = {}
29+
self._tree = {}
30+
31+
@property
32+
def index(self):
33+
"""A map from fully qualified names to objects to be documented.
34+
35+
The index is filled when the visitor is passed to `traverse`.
36+
37+
Returns:
38+
The index filled by traversal.
39+
"""
40+
return self._index
41+
42+
@property
43+
def tree(self):
44+
"""A map from fully qualified names to all its child names for traversal.
45+
46+
The full name to member names map is filled when the visitor is passed to
47+
`traverse`.
48+
49+
Returns:
50+
The full name to member name map filled by traversal.
51+
"""
52+
return self._tree
53+
54+
def __call__(self, parent_name, parent, children):
55+
"""Visitor interface, see `tensorflow/tools/common:traverse` for details.
56+
57+
This method is called for each symbol found in a traversal using
58+
`tensorflow/tools/common:traverse`. It should not be called directly in
59+
user code.
60+
61+
Args:
62+
parent_name: The fully qualified name of a symbol found during traversal.
63+
parent: The Python object referenced by `parent_name`.
64+
children: A list of `(name, py_object)` pairs enumerating, in alphabetical
65+
order, the children (as determined by `inspect.getmembers`) of `parent`.
66+
`name` is the local name of `py_object` in `parent`.
67+
68+
Raises:
69+
RuntimeError: If this visitor is called with a `parent` that is not a
70+
class or module.
71+
"""
72+
self._index[parent_name] = parent
73+
self._tree[parent_name] = []
74+
75+
if inspect.ismodule(parent):
76+
print('module %s: %r' % (parent_name, parent))
77+
elif inspect.isclass(parent):
78+
print('class %s: %r' % (parent_name, parent))
79+
else:
80+
raise RuntimeError('Unexpected type in visitor -- %s: %r' %
81+
(parent_name, parent))
82+
83+
for name, child in children:
84+
full_name = '.'.join([parent_name, name]) if parent_name else name
85+
self._index[full_name] = child
86+
self._tree[parent_name].append(name)
87+
88+
def find_duplicates(self):
89+
"""Compute data structures containing information about duplicates.
90+
91+
Find duplicates in `index` and decide on one to be the "master" name.
92+
93+
Returns a map `duplicate_of` from aliases to their master name (the master
94+
name itself has no entry in this map), and a map `duplicates` from master
95+
names to a lexicographically sorted list of all aliases for that name (incl.
96+
the master name).
97+
98+
Returns:
99+
A tuple `(duplicate_of, duplicates)` as described above.
100+
"""
101+
# Maps the id of a symbol to its fully qualified name. For symbols that have
102+
# several aliases, this map contains the first one found.
103+
# We use id(py_object) to get a hashable value for py_object. Note all
104+
# objects in _index are in memory at the same time so this is safe.
105+
reverse_index = {}
106+
107+
# Make a preliminary duplicates map. For all sets of duplicate names, it
108+
# maps the first name found to a list of all duplicate names.
109+
raw_duplicates = {}
110+
for full_name, py_object in self._index.iteritems():
111+
# We cannot use the duplicate mechanism for constants, since e.g.,
112+
# id(c1) == id(c2) with c1=1, c2=1. This is unproblematic since constants
113+
# have no usable docstring and won't be documented automatically.
114+
if (inspect.ismodule(py_object) or
115+
inspect.isclass(py_object) or
116+
inspect.isfunction(py_object) or
117+
inspect.isroutine(py_object) or
118+
inspect.ismethod(py_object) or
119+
isinstance(py_object, property)):
120+
object_id = id(py_object)
121+
if object_id in reverse_index:
122+
master_name = reverse_index[object_id]
123+
if master_name in raw_duplicates:
124+
raw_duplicates[master_name].append(full_name)
125+
else:
126+
raw_duplicates[master_name] = [master_name, full_name]
127+
else:
128+
reverse_index[object_id] = full_name
129+
130+
# Decide on master names, rewire duplicates and make a duplicate_of map
131+
# mapping all non-master duplicates to the master name. The master symbol
132+
# does not have an entry in this map.
133+
duplicate_of = {}
134+
# Duplicates maps the main symbols to the set of all duplicates of that
135+
# symbol (incl. itself).
136+
duplicates = {}
137+
for names in raw_duplicates.values():
138+
names = sorted(names)
139+
140+
# Choose the lexicographically first name with the minimum number of
141+
# submodules. This will prefer highest level namespace for any symbol.
142+
master_name = min(names, key=lambda name: name.count('.'))
143+
144+
duplicates[master_name] = names
145+
for name in names:
146+
if name != master_name:
147+
duplicate_of[name] = master_name
148+
149+
return duplicate_of, duplicates

doc_generator_visitor_test.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
# ==============================================================================
15+
"""Tests for tools.docs.doc_generator_visitor."""
16+
17+
from __future__ import absolute_import
18+
from __future__ import division
19+
from __future__ import print_function
20+
21+
from tensorflow.python.platform import googletest
22+
from tensorflow.tools.docs import doc_generator_visitor
23+
24+
25+
class DocGeneratorVisitorTest(googletest.TestCase):
26+
27+
def test_call_module(self):
28+
visitor = doc_generator_visitor.DocGeneratorVisitor()
29+
visitor(
30+
'doc_generator_visitor', doc_generator_visitor,
31+
[('DocGeneratorVisitor', doc_generator_visitor.DocGeneratorVisitor)])
32+
33+
self.assertEqual({'doc_generator_visitor': ['DocGeneratorVisitor']},
34+
visitor.tree)
35+
self.assertEqual({
36+
'doc_generator_visitor': doc_generator_visitor,
37+
'doc_generator_visitor.DocGeneratorVisitor':
38+
doc_generator_visitor.DocGeneratorVisitor,
39+
}, visitor.index)
40+
41+
def test_call_class(self):
42+
visitor = doc_generator_visitor.DocGeneratorVisitor()
43+
visitor(
44+
'DocGeneratorVisitor', doc_generator_visitor.DocGeneratorVisitor,
45+
[('index', doc_generator_visitor.DocGeneratorVisitor.index)])
46+
47+
self.assertEqual({'DocGeneratorVisitor': ['index']},
48+
visitor.tree)
49+
self.assertEqual({
50+
'DocGeneratorVisitor': doc_generator_visitor.DocGeneratorVisitor,
51+
'DocGeneratorVisitor.index':
52+
doc_generator_visitor.DocGeneratorVisitor.index
53+
}, visitor.index)
54+
55+
def test_call_raises(self):
56+
visitor = doc_generator_visitor.DocGeneratorVisitor()
57+
with self.assertRaises(RuntimeError):
58+
visitor('non_class_or_module', 'non_class_or_module_object', [])
59+
60+
def test_duplicates(self):
61+
visitor = doc_generator_visitor.DocGeneratorVisitor()
62+
visitor(
63+
'submodule.DocGeneratorVisitor',
64+
doc_generator_visitor.DocGeneratorVisitor,
65+
[('index', doc_generator_visitor.DocGeneratorVisitor.index),
66+
('index2', doc_generator_visitor.DocGeneratorVisitor.index)])
67+
visitor(
68+
'submodule2.DocGeneratorVisitor',
69+
doc_generator_visitor.DocGeneratorVisitor,
70+
[('index', doc_generator_visitor.DocGeneratorVisitor.index),
71+
('index2', doc_generator_visitor.DocGeneratorVisitor.index)])
72+
visitor(
73+
'DocGeneratorVisitor2',
74+
doc_generator_visitor.DocGeneratorVisitor,
75+
[('index', doc_generator_visitor.DocGeneratorVisitor.index),
76+
('index2', doc_generator_visitor.DocGeneratorVisitor.index)])
77+
78+
duplicate_of, duplicates = visitor.find_duplicates()
79+
80+
# The shorter path should be master, or if equal, the lexicographically
81+
# first will be.
82+
self.assertEqual(
83+
{'DocGeneratorVisitor2': sorted(['submodule.DocGeneratorVisitor',
84+
'submodule2.DocGeneratorVisitor',
85+
'DocGeneratorVisitor2']),
86+
'DocGeneratorVisitor2.index': sorted([
87+
'submodule.DocGeneratorVisitor.index',
88+
'submodule.DocGeneratorVisitor.index2',
89+
'submodule2.DocGeneratorVisitor.index',
90+
'submodule2.DocGeneratorVisitor.index2',
91+
'DocGeneratorVisitor2.index',
92+
'DocGeneratorVisitor2.index2'
93+
]),
94+
}, duplicates)
95+
self.assertEqual({
96+
'submodule.DocGeneratorVisitor': 'DocGeneratorVisitor2',
97+
'submodule.DocGeneratorVisitor.index': 'DocGeneratorVisitor2.index',
98+
'submodule.DocGeneratorVisitor.index2': 'DocGeneratorVisitor2.index',
99+
'submodule2.DocGeneratorVisitor': 'DocGeneratorVisitor2',
100+
'submodule2.DocGeneratorVisitor.index': 'DocGeneratorVisitor2.index',
101+
'submodule2.DocGeneratorVisitor.index2': 'DocGeneratorVisitor2.index',
102+
'DocGeneratorVisitor2.index2': 'DocGeneratorVisitor2.index'
103+
}, duplicate_of)
104+
105+
106+
if __name__ == '__main__':
107+
googletest.main()

0 commit comments

Comments
 (0)