Skip to content

Instantly share code, notes, and snippets.

@sonictk
Created October 3, 2015 07:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sonictk/16c60bdd4c036c28b59e to your computer and use it in GitHub Desktop.
Save sonictk/16c60bdd4c036c28b59e to your computer and use it in GitHub Desktop.
Generate completion files for the Mari Python API
#!/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