carla/PythonAPI/docs/doc_gen.py

353 lines
13 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Computer Vision Center (CVC) at the Universitat Autonoma de
# Barcelona (UAB).
#
# This work is licensed under the terms of the MIT license.
# For a copy, see <https://opensource.org/licenses/MIT>.
import os
import yaml
def join(elem, separator = ''):
return separator.join(elem)
class MarkdownFile:
def __init__(self):
self._data = ""
self._list_depth = 0
self.endl = ' \n'
def data(self):
return self._data
def list_push(self, buf=''):
if buf:
self.text(join([
' ' * self._list_depth if self._list_depth != 0 else '', '- ', buf]))
self._list_depth = (self._list_depth + 1)
def list_pushn(self, buf):
self.list_push(join([buf, self.endl]))
def list_pop(self):
self._list_depth = max(self._list_depth - 1, 0)
def list_popn(self):
self.list_pop()
self._data = ''.join([self._data, '\n'])
def list_depth(self):
if self._data.strip()[-1:] != '\n' or self._list_depth == 0:
return ''
return ''.join([' ' * self._list_depth])
def separator(self):
self._data = ''.join([self._data, '\n---\n'])
def new_line(self):
self._data = ''.join([self._data, self.endl])
def text(self, buf):
self._data = ''.join([self._data, buf])
def textn(self, buf):
self._data = ''.join([self._data, self.list_depth(), buf, self.endl])
def title(self, strongness, buf):
self._data = ''.join([
self._data, '\n', self.list_depth(), '#' * strongness, ' ', buf, '\n'])
def code_block(self, buf, language=''):
return ''.join(['```', language, '\n', self.list_depth(), buf, '\n', self.list_depth(), '```\n'])
def prettify_doc(self, doc):
doc = doc.strip()
doc += '' if doc[-1:] == '.' else '.'
# doc = doc.replace('\n', '\n' + (' ' * self._list_depth) if self._list_depth != 0 else '\n')
return doc
def italic(buf):
return ''.join(['_', buf, '_'])
def bold(buf):
return ''.join(['**', buf, '**'])
def code(buf):
return ''.join(['`', buf, '`'])
def brackets(buf):
return ''.join(['[', buf, ']'])
def parentheses(buf):
return ''.join(['(', buf, ')'])
def small(buf):
return ''.join(['<sub><sup>', buf, '</sup></sub>'])
def sub(buf):
return ''.join(['<sub>', buf, '</sub>'])
def html_key(buf):
return ''.join(['<a name="', buf, '"></a>'])
def color(col, buf):
return ''.join(['<font color="', col, '">', buf, '</font>'])
def valid_dic_val(dic, value):
return value in dic and dic[value]
class YamlFile:
"""Yaml file class"""
def __init__(self, path):
self._path = path
with open(path) as yaml_file:
self.data = yaml.safe_load(yaml_file)
self.validate()
def validate(self):
# print('Validating ' + str(self._path.replace('\\', '/').split('/')[-1:][0]))
if self.data is None:
print('\n[ERROR] This file has no data:')
print(self._path)
exit(0)
for module in self.data:
if 'module_name' in module and module['module_name'] is None:
print('\n[ERROR] module_name is empty in:')
print(self._path)
exit(0)
if 'classes' in module:
for cl in module['classes']:
if 'class_name' in cl and cl['class_name'] is None:
print('\n[ERROR] class_name is empty in:')
print(self._path)
exit(0)
if 'instance_variables' in cl and cl['instance_variables']:
for iv in cl['instance_variables']:
if 'var_name' in iv and iv['var_name'] is None:
print('\n[ERROR] var_name is empty in:')
print(self._path)
exit(0)
if 'methods' in cl and cl['methods']:
for met in cl['methods']:
if 'def_name' in met and met['def_name'] is None:
print('\n[ERROR] def_name is empty in:')
print(self._path)
exit(0)
def get_modules(self):
return [module for module in self.data]
def gen_stub_method_def(method):
"""Return python def as it should be written in stub files"""
param = ''
method_name = method['def_name']
for p in method['params']:
p_type = ''.join([': ', str(p['type'])]) if 'type' in p else ''
default = ''.join([' = ', str(p['default'])]) if 'default' in p else ''
param = ''.join([param, p['param_name'], p_type, default, ', '])
param = param[:-2] # delete the last ', '
return_type = ''.join([' -> ', method['return']]) if 'return' in method else ''
return ''.join([method_name, parentheses(param), return_type])
def gen_doc_method_def(method, indx=False):
"""Return python def as it should be written in docs"""
param = ''
method_name = method['def_name']
# to correclty render methods like __init__ in md
if method_name[0] == '_':
method_name = '\\' + method_name
if indx:
method_name = bold(method_name)
else:
method_name = bold(color('#64BA2E', method_name))
for p in method['params']:
default = ''.join(['=', str(p['default'])]) if 'default' in p else ''
if indx:
param = ''.join([param, bold(p['param_name']), default, ', '])
else:
param = ''.join([param, '<font color="#2980B9">', bold(p['param_name']), default, '</font>', ', '])
param = param[:-2] # delete the last ', '
return ''.join([method_name, parentheses(param)])
def gen_inst_var_indx(inst_var, class_key):
inst_var_name = inst_var['var_name']
inst_var_key = '.'.join([class_key, inst_var_name])
return ''.join([
brackets(bold(inst_var_name)),
parentheses(inst_var_key), ' ',
sub(italic('Instance variable'))])
def gen_method_indx(method, class_key):
method_name = method['def_name']
method_key = '.'.join([class_key, method_name])
method_def = gen_doc_method_def(method, True)
return ''.join([
brackets(method_def),
parentheses(method_key), ' ',
sub(italic('Method'))])
def add_doc_method_param(md, param):
param_name = param['param_name']
param_type = ''
param_doc = ''
if valid_dic_val(param, 'type'):
param_type = param['type']
if valid_dic_val(param, 'doc'):
param_doc = md.prettify_doc(param['doc'])
param_type = '' if not param_type else parentheses(italic(param_type))
md.list_push(code(param_name))
if param_type:
md.text(' ' + param_type)
if param_doc:
md.textn(' ' + param_doc)
else:
md.new_line()
md.list_pop()
def add_doc_method(md, method, class_key):
method_name = method['def_name']
method_key = '.'.join([class_key, method_name])
method_def = gen_doc_method_def(method)
md.list_pushn(''.join([html_key(method_key), method_def]))
# method doc
if 'doc' in method and method['doc'] is not '':
md.textn(md.prettify_doc(method['doc']))
# ignore if only have the parameter self
if valid_dic_val(method, 'params') and \
len(method['params']) != 1 and \
method['params'][0] != 'self':
md.list_push(bold('Parameters:') + '\n')
for param in method['params']:
if param['param_name'] == 'self':
continue
add_doc_method_param(md, param)
md.list_pop()
# raises error doc
if valid_dic_val(method, 'raises'):
md.list_pushn(bold('Raises:') + ' ' + method['raises'])
md.list_pop()
md.list_pop()
def add_doc_inst_var(md, inst_var, class_key):
var_name = inst_var['var_name']
var_key = '.'.join([class_key, var_name])
md.list_pushn(html_key(var_key) + bold(var_name))
if valid_dic_val(inst_var, 'doc'):
md.textn(md.prettify_doc(inst_var['doc']))
md.list_pop()
def dump_keys(d, lvl=0):
for k, v in d.iteritems():
print '%s%s' % (lvl * ' ', k)
if type(v) == dict:
dump_keys(v, lvl+1)
class Documentation:
"""Main documentation class"""
def __init__(self, path):
self._path = path
self._files = [f for f in os.listdir(path) if f.endswith('.yml')]
self._yamls = list()
for yaml_file in self._files:
self._yamls.append(YamlFile(os.path.join(path, yaml_file)))
# merge modules
self.master_dict = dict()
for yaml_file in self._yamls:
for module in yaml_file.get_modules():
module_name = module['module_name']
if module_name not in self.master_dict:
self.master_dict[module_name] = module
elif valid_dic_val(module, 'classes'):
for new_module in module['classes']:
# Create the 'classes' key if does not exist already
if not valid_dic_val(self.master_dict[module_name], 'classes'):
self.master_dict[module_name]['classes'] = []
self.master_dict[module_name]['classes'].append(new_module)
def gen_overview(self):
"""Generates a referenced index for markdown file"""
md = MarkdownFile()
md.title(3, 'Overview')
for module_name in self.master_dict:
module = self.master_dict[module_name]
module_key = '#' + module_name
md.list_pushn(
brackets(bold(module_key[1:])) +
parentheses(module_key) + ' ' +
sub(italic('Module')))
# Generate class overview (if any)
if 'classes' in module and module['classes']:
for cl in module['classes']:
class_name = cl['class_name']
class_key = '.'.join([module_key, class_name])
md.list_pushn(
brackets(bold(class_name)) +
parentheses(class_key) + ' ' +
sub(italic('Class')))
# Generate class instance variables overview (if any)
if 'instance_variables' in cl and cl['instance_variables']:
for inst_var in cl['instance_variables']:
md.list_push(gen_inst_var_indx(inst_var, class_key))
md.list_popn()
# Generate class methods overview (if any)
if 'methods' in cl and cl['methods']:
for method in cl['methods']:
md.list_push(gen_method_indx(method, class_key))
md.list_popn()
md.list_pop()
md.list_pop()
return md.data()
def gen_body(self):
"""Generates the documentaion body"""
md = MarkdownFile()
for module_name in self.master_dict:
module = self.master_dict[module_name]
module_key = module_name
# Generate class doc (if any)
if valid_dic_val(module, 'classes'):
for cl in module['classes']:
class_name = cl['class_name']
class_key = '.'.join([module_key, class_name])
md.title(2,
html_key(class_key) +
class_name + ' ' +
small(italic('Class')))
# Generate instance variable doc (if any)
if valid_dic_val(cl, 'instance_variables'):
md.title(3, 'Instance Variables')
for inst_var in cl['instance_variables']:
add_doc_inst_var(md, inst_var, class_key)
# Generate method doc (if any)
if valid_dic_val(cl, 'methods'):
md.title(3, 'Methods')
for method in cl['methods']:
add_doc_method(md, method, class_key)
md.separator()
return md.data().strip()
def gen_markdown(self):
"""Generates the whole markdown file"""
return '\n'.join([self.gen_overview(), self.gen_body()]).strip()
def main():
"""Main function"""
script_path = os.path.dirname(os.path.abspath(__file__))
docs = Documentation(script_path)
with open(os.path.join(script_path, '../../Docs/python_api.md'), 'w') as md_file:
md_file.write(docs.gen_markdown())
if __name__ == "__main__":
main()