SphinxTemplate   A
last analyzed

Complexity

Total Complexity 7

Size/Duplication

Total Lines 44
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 44
rs 10
wmc 7

4 Methods

Rating   Name   Duplication   Size   Complexity  
A method() 0 13 1
A render_method_template() 0 17 4
A get_docstring_for_return() 0 3 1
A get_docstring_for_arguments() 0 3 1
1
import ast
2
import sys
3
4
5
class BaseFlavor:
6
    """
7
    Basic description for class {name}
8
    """
9
10
    def method(self):
11
        """
12
        Basic description for method {name}
13
        """
14
        pass
15
16
    @classmethod
17
    def lstrip(cls, string):
18
        """Helper method to remove left padding from docstrings"""
19
        return '\n'.join((line.lstrip(' '*4) for line in string.split('\n')))
20
21
    @classmethod
22
    def with_quotes(cls, string):
23
        """Return the rendered template between quotes"""
24
        return ''.join(('"""', string, '"""'))
25
26
    @classmethod
27
    def render_class_template(cls, node):
28
        """Renders the class docstring"""
29
        base = cls.lstrip(cls.__doc__)
30
        return cls.with_quotes(base.format(name=node.name))
31
32
    @classmethod
33
    def render_method_template(cls, node):
34
        """Renders the method docstring"""
35
        base = cls.lstrip(cls.method.__doc__)
36
        return cls.with_quotes(base.format(name=node.name))
37
38
39
class SphinxTemplate(BaseFlavor):
40
    """
41
    Basic description for class {name}
42
    """
43
44
    def method(self):
45
        """Brief summary for method {name}
46
47
        The first line is brief explanation, which may be completed with
48
        a longer one.
49
50
        - **parameters**, **types**, **return** and **return types**::
51
52
        {parameters}
53
        :return: return description
54
        :rtype: The return type description
55
        """
56
        pass
57
58
    @classmethod
59
    def get_docstring_for_arguments(cls, node):
60
        pass
61
62
    @classmethod
63
    def get_docstring_for_return(cls, node):
64
        pass
65
66
    @classmethod
67
    def render_method_template(cls, node):
68
        base = cls.lstrip(cls.method.__doc__)
69
        arguments = []
70
        for arg in node.args.args:
71
            name = arg.arg
72
            if name not in ('self', 'cls'):
73
                annotation = arg.annotation.id if arg.annotation else ''
74
                arguments.append(
75
                    ':param %s: Description for %s' % (name, name))
76
                arguments.append(':type %s: %s' % (name, annotation))
77
78
        rendered = base.format(
79
            name=node.name,
80
            parameters='\n'.join(arguments))
81
82
        return cls.with_quotes(rendered)
83
84
85
FLAVORS = {
86
    'default': BaseFlavor,
87
    'sphinx': SphinxTemplate,
88
}
89
90
91
def get_ast_tree(module):
92
    raw_file = open(module).read()
93
94
    tree = ast.parse(raw_file)
95
96
    return tree
97
98
99
def iter_tree(item):
100
    yield item
101
    if 'body' in item._fields:
102
        for child in item.body:
103
            # Python 3.3+: yield from iter_tree(child)
104
            for grandchild in iter_tree(child):
105
                yield grandchild
106
107
108
# TODO allow find by symbol type so we can do Class.method
109
def find_symbol_in_tree(tree, symbol):
110
    for item in iter_tree(tree):
111
        name = getattr(item, 'name', None)
112
        if name == symbol:
113
            return item
114
115
116
def render_template_for_symbol(node, flavor):
117
    if isinstance(node, ast.FunctionDef):
118
        return FLAVORS[flavor].render_method_template(node)
119
120
    if isinstance(node, ast.ClassDef):
121
        return FLAVORS[flavor].render_class_template(node)
122
123
124
def get_docstring_for_symbol(tree, symbol, flavor):
125
    node = find_symbol_in_tree(tree, symbol)
126
    template = render_template_for_symbol(node, flavor)
127
128
    return template
129
130
131
def cli():
132
    try:
133
        module_path = sys.argv[1]
134
        symbol = sys.argv[2]
135
        flavor = 'sphinx'
136
137
        if all((len(sys.argv) >= 4, sys.argv[3] in FLAVORS.keys())):
138
            flavor = sys.argv[3]
139
140
    except IndexError:
141
        print('Usage: <path to module> <symbol> <flavor>')
142
        sys.exit(1)
143
144
    result = get_docstring_for_symbol(
145
        get_ast_tree(module_path),
146
        symbol,
147
        flavor)
148
149
    if not result:
150
        print('Symbol %s not found in %s' % (symbol, module_path))
151
        sys.exit(2)
152
153
    print(result)
154
155
156
if __name__ == '__main__':
157
    cli()
158