Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Added docgen command #60

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions lib/commands/docgen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# (c) Copyright 2017-2018 OLX

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

from command import Command
from .bases import CommandRepositoryBase
import load_python
import os

class Command_docgen(Command, CommandRepositoryBase):
"""
Generate the documentation of all classes supported by rubiks on your local rubiks folder.
"""

_documentation = """
This document is automatically generated using the command `docgen` and describe all the object and types that can be used inside Rubiks to configure your cluster

It is possible to generate this documentation locally running inside your rubiks repo `rubiks docgen`.
"""

def populate_args(self, parser):
pass

def run(self, args):
self.get_repository(can_fail=True)
objs = load_python.PythonBaseFile.get_kube_objs()
types = load_python.PythonBaseFile.get_kube_types()
formats = load_python.PythonBaseFile.get_kube_vartypes()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I particularly like the name "formats" for vartypes.


r = self.get_repository()

doc = '\n'.join([line.strip() for line in self._documentation.split('\n')])

md = ''
header = '<p align="center"><img src="logos/rubiks-logo-horizontal.png" title="Rubiks Logo"></p>\n\n'
header += '# Rubiks Object Index\n{}\n\n'.format(doc)
header += '# Table of contents\n\n'

header += '- [Formats](#formats)\n'
md += '\n# Formats\n\n'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This structure makes sense if you add options (which I think you should) for which sections you should generate.

And as mentioned, I think "formats" is an awful name for this.

for fname in sorted(formats.keys()):
header += ' - [{}](#{})\n'.format(fname, fname.lower())
md += '## {}\n\n'.format(fname)
md += '{}\n\n'.format(formats[fname].get_description())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

particularly because of this!


header += '- [Types](#types)\n'
md += '\n# Types\n\n'
for tname in sorted(types.keys()):
header += ' - [{}](#{})\n'.format(tname, tname.lower())
md += '## {}\n\n'.format(tname)
md += '{}\n\n'.format(types[tname].get_description())


header += '- [Objects](#objects)\n'
md += '\n# Objects\n\n'
for oname in sorted(objs.keys()):
header += ' - [{}](#{})\n'.format(oname, oname.lower())
md += objs[oname].get_help().render_markdown()

with open(os.path.join(r.basepath, 'docs/rubiks.class.md'), 'w') as f:
f.write(header.encode('utf-8'))
f.write(md.encode('utf-8'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, please fix your editor so that the file ends in a newline and we don't get spurious diffs down the line.

84 changes: 84 additions & 0 deletions lib/kube_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,89 @@ def render_terminal(self):

return txt

def _get_markdown_link(self, objects):
if isinstance(objects, list):
return ['[{}](#{})'.format(classname, classname.lower()) for classname in objects]
else:
return '[{}](#{})'.format(objects, objects.lower())

def _docstring_formatter(self):
return '\n'.join([line.strip() for line in self.class_doc.split('\n')])

def _decorate_obj_links(self, display, links):
for link in links:
display = display.replace(link, self._get_markdown_link(link))

return display

def render_markdown(self):
md_render = lambda x: '[{}](#{})'.format(x, x.lower())
# Title generation based on link
if self.class_doc_link is not None:
txt = '## [{}]({})\n'.format(self.class_name, self.class_doc_link)
else:
txt = '## {}\n'.format(self.class_name)

# Add class doc string if exists
if self.class_doc is not None:
txt += '\n{}\n'.format(self._docstring_formatter())

# Parents
if len(self.class_superclasses) != 0:
txt += '### Parents: \n'
for obj in self._get_markdown_link(self.class_superclasses):
txt += '- {}\n'.format(obj)

# Children
if len(self.class_subclasses) != 0:
txt += '### Children: \n'
for obj in self._get_markdown_link(self.class_subclasses):
txt += '- {}\n'.format(obj)

# Parent types
if len(self.class_parent_types) != 0:
txt += '### Parent types: \n'
for obj in self._get_markdown_link(sorted(self.class_parent_types.keys())):
txt += '- {}\n'.format(obj)

# Metadata
if self.class_has_metadata:
txt += '### Metadata\n'
txt += 'Name | Format\n'
txt += '---- | ------\n'
txt += 'annotations | {}\n'.format(Map(String, String).name(render=md_render))
txt += 'labels | {}\n'.format(Map(String, String).name(render=md_render))

# Properties table
if len(self.class_types.keys()) == 0:
return txt

txt += '### Properties:\n\n'
txt += 'Name | Type | Identifier | Type Transformation | Aliases\n'
txt += '---- | ---- | ---------- | ------------------- | -------\n'
if self.class_identifier is not None:
txt += '{} | {} | True | - | - \n'.format(self.class_identifier, self._get_markdown_link(self.class_types[self.class_identifier].name()))

for p in sorted(self.class_types.keys()):
if p == self.class_identifier:
continue

# Prepare Type transformation and remove special character that could ruin visualization in markdown
xf_data = self.class_xf_detail[p] if p in self.class_xf_detail else '-'
xf_data = xf_data.replace('<', '&lt;').replace('>', '&gt;')

is_mapped = ', '.join(self._get_markdown_link(self.class_mapping[p])) if p in self.class_mapping else '-'

original_type = self.class_types[p].original_type()
display_type = self.class_types[p].name(render=md_render).replace('<', '&lt;').replace('>', '&gt;')

if original_type is not None:
if isinstance(original_type, list):
original_type = original_type[0]
elif isinstance(original_type, dict):
original_type = original_type['value']


txt += '{} | {} | False | {} | {} \n'.format(p, display_type, xf_data, is_mapped)

return txt
6 changes: 6 additions & 0 deletions lib/kube_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ def construct_arg(cls, arg):
ret = Object(ret.__class__)

return ret

@classmethod
def get_description(cls):
if cls.__doc__ is not None and cls.__doc__ is not '':
return '\n'.join([line.strip() for line in cls.__doc__.split('\n')])
return 'TODO: Description is still missing from the class docstring.\nStay tuned to have more hint about this variable.'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this text - after all, Boolean/Integer etc should speak for themselves. I think this should be:

return cls.__name__


def original_type(self):
if self.wrapper:
Expand Down
23 changes: 23 additions & 0 deletions lib/load_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from util import mkdir_p

import kube_objs
import kube_types
import kube_vartypes


Expand Down Expand Up @@ -209,6 +210,7 @@ def gen_output(self):
class PythonBaseFile(object):
_kube_objs = None
_kube_vartypes = None
_kube_type = None
compile_in_init = True
default_export_objects = False
can_cluster_context = True
Expand Down Expand Up @@ -242,6 +244,26 @@ def get_kube_vartypes(cls):
pass
return cls._kube_vartypes

@classmethod
def get_kube_types(cls):
if cls._kube_type is None:
cls._kube_type = {}

for k in kube_types.__dict__:
if isinstance(kube_types.__dict__[k], type) and k not in ('KubeType', 'SurgeCheck'):
try:
if hasattr(kube_types.__dict__[k], 'wrapper'):
if kube_types.__dict__[k].wrapper:
if isinstance(kube_types.__dict__[k](kube_types.String), kube_types.KubeType):
cls._kube_type[k] = kube_types.__dict__[k]
else:
if isinstance(kube_types.__dict__[k](), kube_types.KubeType):
cls._kube_type[k] = kube_types.__dict__[k]
except:
pass

return cls._kube_type

def __init__(self, collection, path):
if path.basename == '' or path.basename.lower().strip('0123456789abcdefghijklmnopqrstuvwxyz_') != '':
raise UserError(ValueError(
Expand Down Expand Up @@ -515,6 +537,7 @@ def cluster_info(c):

ret.update(self.__class__.get_kube_objs())
ret.update(self.__class__.get_kube_vartypes())
ret.update(self.__class__.get_kube_types())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed!? Why do you think you need, say "Integer" or "String" in the DSL?


self.reserved_names = tuple(ret.keys())

Expand Down
7 changes: 7 additions & 0 deletions lib/var_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,10 @@ def __str__(self):
if self.renderer is None:
return self._internal_render()
return self.renderer(self._internal_render())

@classmethod
def get_description(cls):
if cls.__doc__ is not None and cls.__doc__ is not '':
return '\n'.join([line.strip() for line in cls.__doc__.split('\n')])
return 'TODO: Description is still missing from the class docstring.\nStay tuned to have more hint about this variable.'

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again

return cls.__name__