Created
October 3, 2015 07:15
-
-
Save sonictk/16c60bdd4c036c28b59e to your computer and use it in GitHub Desktop.
Generate completion files for the Mari Python API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: UTF-8 -*- | |
""" | |
Module generate_mari_autocompletions: This module allows for automatically | |
generating completions files for the Mari Python API. | |
""" | |
import shutil | |
import os | |
import sys | |
import pydoc | |
import inspect | |
import traceback | |
import mari | |
import mari.current as current | |
import mari.system as system | |
import mari.utils as utils | |
def get_docstring(item): | |
""" | |
This function returns the full docstring for the given object. | |
:param item: ``object`` that should have a ``__doc__`` attribute. | |
:return: ``str`` of full docstring. | |
""" | |
try: docstring = pydoc.plain(pydoc.render_doc(item)) | |
except ImportError: | |
docstring = '' | |
return docstring | |
def get_node_formatted_output( | |
node, | |
module, | |
output_prefix='', | |
recurse=True, | |
ignore_objects=None, | |
print_comments=False, | |
ignore_imports=True, | |
currently_recursing=False, | |
): | |
""" | |
This function inspects the given ``node`` and generates the formatted | |
autocompletion definition for it. | |
:param ignore_imports: ``bool`` indicating if imports are not to have | |
autocomplete data generated for. | |
:param print_comments: ``bool`` indicating if exceptions during | |
auto-completion generation should be output in the form of comments. | |
:param ignore_objects: ``list`` of object names to be ignored for | |
completion generation. | |
:param recurse: ``bool`` indicating if node members should be searched | |
recursively for output. | |
:param output_prefix: ``str`` prefix to add to the node defintion. | |
:param node: ``tuple`` containing ``str`` name and ``type`` of object to | |
generate completion data for. | |
:return: ``str`` containing formatted definition. | |
""" | |
if node[0] in ignore_objects: | |
return | |
output = '' | |
output += output_prefix | |
docstring = node[1].__doc__ | |
if not docstring: | |
docstring = inspect.getdoc(node[1]) | |
# formatted_docstring = inspect.cleandoc(docstring) | |
# Format the docstring to have correct indentation | |
formatted_docstring = repr(docstring) | |
formatted_docstring = '\t'+\ | |
formatted_docstring.\ | |
replace('\\n', '\\n\\t').\ | |
replace('\'', '').\ | |
replace('\"', '').\ | |
decode('string-escape') | |
if inspect.ismodule(node[1]): | |
if ignore_imports: | |
return | |
else: | |
if print_comments: | |
output += '\r# MODULE OBJECT: {0}\r'.format(node[0]) | |
output += '\rimport {0}\r'.format(node[0]) | |
# Variables | |
elif isinstance(node[1], int): | |
if print_comments: | |
output += '\r# INTEGER OBJECT: {0}\r'.format(node[0]) | |
output += '\r\t{0} = {1}\r'.format(node[0], node[1]) | |
elif inspect.ismethod(node[1]): | |
if print_comments: | |
output += '\r# METHOD OBJECT: {0}\r'.format(node[0]) | |
output += '\r\tdef {0}(self, *args, **kwargs):'\ | |
'\r\t\t\"\"\"\r\t\t{1}\r\t\t\"\"\"'\ | |
'\r\t\tpass\r\r'\ | |
.format(node[0], formatted_docstring) | |
elif inspect.isfunction(node[1]): | |
if print_comments: | |
output += '\r# FUNCTION: {0} belonging to module {1}\r'\ | |
.format(node[0], module) | |
node_members = inspect.getmembers(node[1]) | |
classmethod_def = '' | |
classmethod_prefix = '' | |
# Check if class instance method | |
if currently_recursing: | |
classmethod_prefix = '\t' | |
for child_node in node_members: | |
if child_node[0] == '__module__' or child_node[0] == '__self__': | |
if node[1].__module__ == module.__name__: | |
classmethod_def = 'self, ' | |
break | |
output += \ | |
'{0}def {1}({2}*args, **kwargs):'\ | |
'\r\t\t\"\"\"\r\t\t{3}\r\t\t\"\"\"'\ | |
'\r\t\tpass\r\r'\ | |
.format(classmethod_prefix, node[0], classmethod_def, formatted_docstring) | |
elif inspect.isgeneratorfunction(node[1]) or inspect.isgenerator(node[1]): | |
if print_comments: | |
output += '\r# GENERATOR OBJECT: {0}\r'.format(node[0]) | |
output += \ | |
'\r\tdef {0}(*args, **kwargs):'\ | |
'\r\t\t\"\"\"\r\t\t{1}\r\t\t\"\"\"'\ | |
'\r\t\tpass\r\r'\ | |
.format(node[0], formatted_docstring) | |
elif inspect.isclass(node[1]): | |
if print_comments: | |
output += '\r# CLASS OBJECT: {0}\r'.format(node[0]) | |
# Find the next class inherited in the MRO and use that for | |
# the auto-completion entry | |
base_class = inspect.getmro(node[1]) | |
if len(base_class) > 1: | |
try: base_class_name = base_class[1].__name__ | |
except SystemError: | |
# Default to first base class | |
base_class_name = base_class[-1].__name__ | |
else: | |
base_class_name = 'object' | |
# Get all class members | |
class_members = inspect.getmembers(node[1]) | |
try: | |
output += \ | |
'\rclass {0}({1}):'\ | |
'\r\t\"\"\"\r{2}\r\t\"\"\"\r'\ | |
.format(str(node[0]), base_class_name, formatted_docstring) | |
except SystemError: | |
sys.stderr.write('### Failed to generate output for: {0}!!!\r{1}\r' | |
.format(node, traceback.print_exc())) | |
if recurse: | |
# Now append all class members output as well | |
for class_member in class_members: | |
class_member_output = get_node_formatted_output( | |
node=class_member, | |
module=module, | |
output_prefix=output_prefix, | |
recurse=False, | |
ignore_objects=ignore_objects, | |
print_comments=print_comments, | |
ignore_imports=ignore_imports, | |
currently_recursing=True | |
) | |
if class_member_output: | |
output += class_member_output | |
# End class definition | |
output += '\r\tpass\r' | |
elif inspect.istraceback(node[1]): | |
if print_comments: | |
output += '\r# TRACEBACK OBJECT: {0}\r'.format(node[0]) | |
elif inspect.isframe(node[1]): | |
if print_comments: | |
output += '\r# FRAME OBJECT: {0}\r'.format(node[0]) | |
elif inspect.iscode(node[1]): | |
if print_comments: | |
output += '\r# CODE OBJECT: {0}\r'.format(node[0]) | |
elif inspect.isbuiltin(node[1]): | |
if print_comments: | |
output += '\r# BUILTIN OBJECT: {0}\r'.format(node[0]) | |
elif inspect.isroutine(node[1]): | |
if print_comments: | |
output += '\r# ROUTINE OBJECT: {0}\r'.format(node[0]) | |
# output += \ | |
# '\rdef {0}(*args, **kwargs):'\ | |
# '\r\t\t\"\"\"\r\t\t{1}\r\t\t\"\"\"'\ | |
# '\r\tpass\r\r'\ | |
# .format(node[0], formatted_docstring) | |
elif inspect.isabstract(node[1]): | |
if print_comments: | |
output += '\r# ABSTRACT OBJECT: {0}\r'.format(node[0]) | |
elif inspect.ismethoddescriptor(node[1]): | |
if print_comments: | |
output += '\r# METHOD DESCRIPTOR OBJECT: {0}\r'.format(node[0]) | |
elif inspect.isdatadescriptor(node[1]): | |
if print_comments: | |
output += '\r# DATA DESCRIPTOR OBJECT: {0}\r'.format(node[0]) | |
elif inspect.isgetsetdescriptor(node[1]): | |
if print_comments: | |
output += '\r# GET/SET DESCRIPTOR OBJECT: {0}\r'.format(node[0]) | |
elif inspect.ismemberdescriptor(node[1]): | |
if print_comments: | |
output += '\r# MEMBER DESCRIPTOR OBJECT: {0}\r'.format(node[0]) | |
else: | |
if print_comments: | |
output += '\r# COULD NOT INSPECT: {0} {1} belonging to: {2}\r'\ | |
.format(node[0], str(node[1]), module) | |
node_members = inspect.getmembers(node[1]) | |
classmethod_def = '' | |
classmethod_prefix = '' | |
# Check if class instance method | |
if currently_recursing: | |
classmethod_prefix = '\t' | |
for child_node in node_members: | |
if child_node[0] == '__module__' or child_node[0] == '__self__': | |
if node[1].__module__ or node[1].__self__: | |
classmethod_def = 'self, ' | |
classmethod_prefix = '\t' | |
break | |
output += \ | |
'{0}def {1}({2}*args, **kwargs):'\ | |
'\r\t\t\"\"\"\r\t\t{3}\r\t\t\"\"\"'\ | |
'\r\t\tpass\r\r'\ | |
.format(classmethod_prefix, node[0], classmethod_def, formatted_docstring) | |
return output | |
def get_output_from_objects(nodes, module): | |
""" | |
This function returns formatted output from the objects given. | |
:param module: | |
:param nodes: ``list`` of ``object``s to format data from. | |
:return: ``str`` formatted docstring and definition output. | |
""" | |
builtins_list = [ | |
'_api_doc', | |
'__builtins__', | |
'__class__', | |
'__call__', | |
'__repr__', | |
'__str__', | |
'__delattr__', | |
'__doc__', | |
'__dict__', | |
'__format__', | |
'__file__', | |
'__getattribute__', | |
'__hash__', | |
'__module__', | |
'__name__', | |
'__new__', | |
'__package__', | |
'__path__', | |
'__reduce__', | |
'__reduce_ex__', | |
'__setattr__', | |
'__sizeof__', | |
'__subclasshook__', | |
'__weakref__', | |
'_built_in_modules', | |
'_docs_path', | |
'actions', | |
'app', | |
'canvases', | |
'clock', | |
'colors', | |
'current', | |
'customScripts', | |
'ddi', | |
'event', | |
'examples', | |
'geo', | |
'gl_render', | |
'history', | |
'images', | |
'lights', | |
'menus', | |
'palettes', | |
'particle', | |
'patch_links', | |
'prefs', | |
'projection', | |
'projectors', | |
'projects', | |
'resources', | |
'shelves', | |
'system', | |
'tools', | |
'utils' | |
] | |
output = '' | |
for node in nodes: | |
if node[0] in builtins_list: | |
continue | |
node_output = get_node_formatted_output( | |
node=node, | |
module=module, | |
ignore_objects=builtins_list, | |
print_comments=False | |
) | |
if node_output: | |
output += node_output | |
# Change tabs to spaces | |
output = output.replace('\t', ' ') | |
return output | |
def generate_completion_file( | |
module, | |
file_name=None, | |
boilerplate_headers=None, | |
output_file_path=None | |
): | |
""" | |
This function writes the completion file to the specified | |
output file path for the given modules. | |
:param module: ``object`` that is Python module to generate completion data for. | |
:param file_name: ``str`` that will be the file name written to for output. | |
:param boilerplate_headers: ``str`` containing any header data that is to | |
be appended to the beginning of the completion file. | |
:param output_file_path: ``str`` that is the file path to write the final | |
completion file to. | |
:return: ``None`` | |
""" | |
members = inspect.getmembers(module) | |
if not file_name: | |
file_name = module.__name__ | |
# Format boilerplate imports that go in the output | |
if boilerplate_headers: | |
output = boilerplate_headers | |
else: | |
output = '' | |
output += get_output_from_objects(members, module) | |
if not output_file_path: | |
output_file_path = os.path.join( | |
os.path.dirname( | |
os.path.dirname(os.path.abspath(__file__)) | |
), | |
'extras', | |
'completions', | |
'py', | |
'mari', | |
file_name + '.py' | |
) | |
# Clear autocompletions directory first | |
# shutil.rmtree(os.path.dirname(output_file_path)) | |
if not os.path.isdir(os.path.dirname(output_file_path)): | |
os.makedirs(os.path.dirname(output_file_path)) | |
with open(output_file_path, 'w') as file_handle: | |
file_handle.write(output) | |
sys.stdout.write('Generated autocompletions file: {0} successfully!\n' | |
.format(output_file_path)) | |
def generate_autocompletions(boilerplate=''): | |
""" | |
This function, when run, generates the completion files. | |
:return: ``None`` | |
""" | |
# Generate completion files | |
try: | |
generate_completion_file(mari, 'mari') | |
generate_completion_file(mari.actions, 'actions') | |
generate_completion_file(mari.app, 'app') | |
generate_completion_file(mari.canvases, 'canvases') | |
generate_completion_file(mari.clock, 'clock') | |
generate_completion_file(mari.colors, 'colors') | |
generate_completion_file(current, 'current') | |
generate_completion_file(mari.customScripts, 'customScripts') | |
generate_completion_file(mari.ddi, 'ddi') | |
generate_completion_file(mari.event, 'event') | |
generate_completion_file(mari.examples, 'examples') | |
generate_completion_file(mari.geo, 'geo') | |
generate_completion_file(mari.gl_render, 'gl_render') | |
generate_completion_file(mari.history, 'history') | |
generate_completion_file(mari.images, 'images') | |
generate_completion_file(mari.lights, 'lights') | |
generate_completion_file(mari.menus, 'menus') | |
generate_completion_file(mari.palettes, 'palettes') | |
generate_completion_file(mari.particle, 'particle') | |
generate_completion_file(mari.patch_links, 'patch_links') | |
generate_completion_file(mari.prefs, 'prefs') | |
generate_completion_file(mari.projection, 'projection') | |
generate_completion_file(mari.projectors, 'projectors') | |
generate_completion_file(mari.projects, 'projects') | |
generate_completion_file(mari.resources, 'resources') | |
generate_completion_file(mari.shelves, 'shelves') | |
generate_completion_file(system, 'system') | |
generate_completion_file(mari.tools, 'tools') | |
generate_completion_file(utils, 'utils') | |
except Exception: | |
sys.stderr.write('### Failed to generate all completion data!!!\n{0}' | |
.format(traceback.print_exc())) | |
raise RuntimeError | |
sys.stdout.write('Successfully generated all completion file data!\n') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment