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 module for converting parsed doc content into markdown pages. 16 17 The adjacent `parser` module creates `PageInfo` objects, containing all data 18 necessary to document an element of the TensorFlow API. 19 20 This module contains one public function, which handels the conversion of these 21 `PageInfo` objects into a markdown string: 22 23 md_page = build_md_page(page_info) 24 """ 25 26 from __future__ import absolute_import 27 from __future__ import division 28 from __future__ import print_function 29 30 import itertools 31 import textwrap 32 33 34 def build_md_page(page_info): 35 """Given a PageInfo object, return markdown for the page. 36 37 Args: 38 page_info: must be a `parser.FunctionPageInfo`, `parser.ClassPageInfo`, or 39 `parser.ModulePageInfo` 40 41 Returns: 42 Markdown for the page 43 44 Raises: 45 ValueError: if `page_info` is an instance of an unrecognized class 46 """ 47 if page_info.for_function(): 48 return _build_function_page(page_info) 49 50 if page_info.for_class(): 51 return _build_class_page(page_info) 52 53 if page_info.for_module(): 54 return _build_module_page(page_info) 55 56 raise ValueError('Unknown Page Info Type: %s' % type(page_info)) 57 58 59 def _build_function_page(page_info): 60 """Given a FunctionPageInfo object Return the page as an md string.""" 61 parts = [_Metadata(page_info.full_name).build_html()] 62 parts.append('# %s\n\n' % page_info.full_name) 63 64 if len(page_info.aliases) > 1: 65 parts.append('### Aliases:\n\n') 66 parts.extend('* `%s`\n' % name for name in page_info.aliases) 67 parts.append('\n') 68 69 if page_info.signature is not None: 70 parts.append(_build_signature(page_info)) 71 72 if page_info.defined_in: 73 parts.append('\n\n') 74 parts.append(str(page_info.defined_in)) 75 76 parts.append(page_info.guides) 77 parts.append(page_info.doc.docstring) 78 parts.append(_build_function_details(page_info.doc.function_details)) 79 parts.append(_build_compatibility(page_info.doc.compatibility)) 80 81 return ''.join(parts) 82 83 84 def _build_class_page(page_info): 85 """Given a ClassPageInfo object Return the page as an md string.""" 86 meta_data = _Metadata(page_info.full_name) 87 for item in itertools.chain( 88 page_info.classes, 89 page_info.properties, 90 page_info.methods, 91 page_info.other_members): 92 meta_data.append(item) 93 94 parts = [meta_data.build_html()] 95 96 parts.append('# {page_info.full_name}\n\n'.format(page_info=page_info)) 97 98 parts.append('## Class `%s`\n\n' % page_info.full_name.split('.')[-1]) 99 if page_info.bases: 100 parts.append('Inherits From: ') 101 102 link_template = '[`{short_name}`]({url})' 103 parts.append(', '.join( 104 link_template.format(**base.__dict__) for base in page_info.bases)) 105 106 parts.append('\n\n') 107 108 if len(page_info.aliases) > 1: 109 parts.append('### Aliases:\n\n') 110 parts.extend('* Class `%s`\n' % name for name in page_info.aliases) 111 parts.append('\n') 112 113 if page_info.defined_in is not None: 114 parts.append('\n\n') 115 parts.append(str(page_info.defined_in)) 116 117 parts.append(page_info.guides) 118 parts.append(page_info.doc.docstring) 119 parts.append(_build_function_details(page_info.doc.function_details)) 120 parts.append(_build_compatibility(page_info.doc.compatibility)) 121 122 parts.append('\n\n') 123 124 if page_info.classes: 125 parts.append('## Child Classes\n') 126 127 link_template = ('[`class {class_info.short_name}`]' 128 '({class_info.url})\n\n') 129 class_links = sorted( 130 link_template.format(class_info=class_info) 131 for class_info in page_info.classes) 132 133 parts.extend(class_links) 134 135 if page_info.properties: 136 parts.append('## Properties\n\n') 137 for prop_info in sorted(page_info.properties): 138 h3 = '<h3 id="{short_name}"><code>{short_name}</code></h3>\n\n' 139 parts.append(h3.format(short_name=prop_info.short_name)) 140 141 parts.append(prop_info.doc.docstring) 142 parts.append(_build_function_details(prop_info.doc.function_details)) 143 parts.append(_build_compatibility(prop_info.doc.compatibility)) 144 145 parts.append('\n\n') 146 147 parts.append('\n\n') 148 149 if page_info.methods: 150 parts.append('## Methods\n\n') 151 # Sort the methods list, but make sure constructors come first. 152 constructors = ['__init__', '__new__'] 153 inits = [method for method in page_info.methods 154 if method.short_name in constructors] 155 others = [method for method in page_info.methods 156 if method.short_name not in constructors] 157 158 for method_info in sorted(inits) + sorted(others): 159 h3 = ('<h3 id="{short_name}">' 160 '<code>{short_name}</code>' 161 '</h3>\n\n') 162 parts.append(h3.format(**method_info.__dict__)) 163 164 if method_info.signature is not None: 165 parts.append(_build_signature(method_info, use_full_name=False)) 166 167 parts.append(method_info.doc.docstring) 168 parts.append(_build_function_details(method_info.doc.function_details)) 169 parts.append(_build_compatibility(method_info.doc.compatibility)) 170 parts.append('\n\n') 171 parts.append('\n\n') 172 173 if page_info.other_members: 174 parts.append('## Class Members\n\n') 175 176 # TODO(markdaoust): Document the value of the members, 177 # at least for basic types. 178 179 h3 = '<h3 id="{short_name}"><code>{short_name}</code></h3>\n\n' 180 others_member_headings = (h3.format(short_name=info.short_name) 181 for info in sorted(page_info.other_members)) 182 parts.extend(others_member_headings) 183 184 return ''.join(parts) 185 186 187 def _build_module_page(page_info): 188 """Given a ClassPageInfo object Return the page as an md string.""" 189 meta_data = _Metadata(page_info.full_name) 190 191 # Objects with their own pages are not added to the matadata list for the 192 # module, as the only thing on the module page is a link to the object's page. 193 for item in page_info.other_members: 194 meta_data.append(item) 195 196 parts = [meta_data.build_html()] 197 198 parts.append( 199 '# Module: {full_name}\n\n'.format(full_name=page_info.full_name)) 200 201 if len(page_info.aliases) > 1: 202 parts.append('### Aliases:\n\n') 203 parts.extend('* Module `%s`\n' % name for name in page_info.aliases) 204 parts.append('\n') 205 206 if page_info.defined_in is not None: 207 parts.append('\n\n') 208 parts.append(str(page_info.defined_in)) 209 210 parts.append(page_info.doc.docstring) 211 parts.append(_build_compatibility(page_info.doc.compatibility)) 212 213 parts.append('\n\n') 214 215 if page_info.modules: 216 parts.append('## Modules\n\n') 217 template = '[`{short_name}`]({url}) module' 218 219 for item in page_info.modules: 220 parts.append(template.format(**item.__dict__)) 221 222 if item.doc.brief: 223 parts.append(': ' + item.doc.brief) 224 225 parts.append('\n\n') 226 227 if page_info.classes: 228 parts.append('## Classes\n\n') 229 template = '[`class {short_name}`]({url})' 230 231 for item in page_info.classes: 232 parts.append(template.format(**item.__dict__)) 233 234 if item.doc.brief: 235 parts.append(': ' + item.doc.brief) 236 237 parts.append('\n\n') 238 239 if page_info.functions: 240 parts.append('## Functions\n\n') 241 template = '[`{short_name}(...)`]({url})' 242 243 for item in page_info.functions: 244 parts.append(template.format(**item.__dict__)) 245 246 if item.doc.brief: 247 parts.append(': ' + item.doc.brief) 248 249 parts.append('\n\n') 250 251 if page_info.other_members: 252 # TODO(markdaoust): Document the value of the members, 253 # at least for basic types. 254 parts.append('## Other Members\n\n') 255 256 for item in page_info.other_members: 257 parts.append('`{short_name}`\n\n'.format(**item.__dict__)) 258 259 return ''.join(parts) 260 261 262 def _build_signature(obj_info, use_full_name=True): 263 """Returns a md code block showing the function signature.""" 264 # Special case tf.range, since it has an optional first argument 265 if obj_info.full_name == 'tf.range': 266 return ( 267 '``` python\n' 268 "tf.range(limit, delta=1, dtype=None, name='range')\n" 269 "tf.range(start, limit, delta=1, dtype=None, name='range')\n" 270 '```\n\n') 271 272 parts = ['``` python'] 273 parts.extend(['@' + dec for dec in obj_info.decorators]) 274 signature_template = '{name}({sig})' 275 276 if not obj_info.signature: 277 sig = '' 278 elif len(obj_info.signature) == 1: 279 sig = obj_info.signature[0] 280 else: 281 sig = ',\n'.join(' %s' % sig_item for sig_item in obj_info.signature) 282 sig = '\n'+sig+'\n' 283 284 if use_full_name: 285 obj_name = obj_info.full_name 286 else: 287 obj_name = obj_info.short_name 288 parts.append(signature_template.format(name=obj_name, sig=sig)) 289 parts.append('```\n\n') 290 291 return '\n'.join(parts) 292 293 294 def _build_compatibility(compatibility): 295 """Return the compatibility section as an md string.""" 296 parts = [] 297 sorted_keys = sorted(compatibility.keys()) 298 for key in sorted_keys: 299 300 value = compatibility[key] 301 # Dedent so that it does not trigger markdown code formatting. 302 value = textwrap.dedent(value) 303 parts.append('\n\n#### %s Compatibility\n%s\n' % (key.title(), value)) 304 305 return ''.join(parts) 306 307 308 def _build_function_details(function_details): 309 """Return the function details section as an md string.""" 310 parts = [] 311 for detail in function_details: 312 sub = [] 313 sub.append('#### ' + detail.keyword + ':\n\n') 314 sub.append(textwrap.dedent(detail.header)) 315 for key, value in detail.items: 316 sub.append('* <b>`%s`</b>: %s' % (key, value)) 317 parts.append(''.join(sub)) 318 319 return '\n'.join(parts) 320 321 322 class _Metadata(object): 323 """A class for building a page's Metadata block. 324 325 Attributes: 326 name: The name of the page being described by the Metadata block. 327 """ 328 329 def __init__(self, name): 330 """Create a Metadata builder. 331 332 Args: 333 name: The name of the page being described by the Metadata block. 334 """ 335 self.name = name 336 self._content = [] 337 338 def append(self, item): 339 """Add an item from the page to the Metadata block. 340 341 Args: 342 item: The parsed page section to add. 343 """ 344 self._content.append(item.short_name) 345 346 def build_html(self): 347 """Return the Metadata block as an Html string.""" 348 schema = 'http://developers.google.com/ReferenceObject' 349 parts = ['<div itemscope itemtype="%s">' % schema] 350 351 parts.append('<meta itemprop="name" content="%s" />' % self.name) 352 for item in self._content: 353 parts.append('<meta itemprop="property" content="%s"/>' % item) 354 355 parts.extend(['</div>', '', '']) 356 357 return '\n'.join(parts) 358