summaryrefslogtreecommitdiff
path: root/docs/sphinx/gnuradio_sphinx.py
blob: e8ca867f8c7b87fd93a74694365d58497bc272ee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
"""
Customizations of sphinx for gnuradio use.
"""

from sphinx.ext.autodoc import py_ext_sig_re
from sphinx.ext.autodoc import ClassDocumenter, FunctionDocumenter, members_option
from sphinx.ext.autodoc import bool_option, members_set_option, identity
from sphinx.ext.autodoc import ALL

# A dictionary of the number of lines to delete from the beginning of docstrings
lines_to_delete = {}

def setup(sp):
    # Fix line-breaks in signature.
    sp.connect('autodoc-process-signature', fix_signature)
    sp.connect('autodoc-process-docstring', remove_lines)
    # Add node to autodocument signal-processing blocks.
    sp.add_autodocumenter(OldBlockDocumenter)
    sp.add_autodocumenter(BlockDocumenter)
    sp.add_autodocumenter(PyBlockDocumenter)

def remove_lines(app, what, name, obj, options, lines):
    del_lines = lines_to_delete.get(name, 0)
    # Don't delete any lines if this is called again.
    lines_to_delete[name] = 0
    lines[:] = lines[del_lines:]

def fix_signature(app, what, name, obj, options, signature, return_annotation):
    """
    SWIG produces signature at the top of docstrings of the form
    'blah(int arg1, float arg2) -> return_type'
    and if the string is long it breaks it over multiple lines.
    
    Sphinx gets confused if it is broken over multiple lines.
    fix_signature and remove_lines get around this problem.
    """
    if return_annotation is not None:
        return

    if hasattr(obj, '__doc__'):
        docs = obj.__doc__
    else:
        docs = None
    if not docs:
        return None
    doclines = docs.split('\n')
    del_lines = remove_linebreaks_in_signature(doclines)
    # match first line of docstring against signature RE
    match = py_ext_sig_re.match(doclines[0])
    if not match:
        return None
    exmod, path, base, args, retann = match.groups()
    # ok, now jump over remaining empty lines and set the remaining
    # lines as the new doclines
    i = 1
    while i < len(doclines) and not doclines[i].strip():
        i += 1
    lines_to_delete[name] = i - 1 + del_lines
    # format args
    signature = "({0})".format(args)
    return signature, retann

def remove_linebreaks_in_signature(lines):
    alllines = '\n'.join(lines)
    alllines = alllines.lstrip()
    bits = alllines.split('->')
    if len(bits) == 1:
        return 0
    after = '->'.join(bits[1:])
    after_lines = after.split('\n')
    ending = None
    remainder = []
    for line in after_lines:
        if line and ending is None:
            ending = line
        elif ending is not None:
            remainder.append(line)
    first_line = ' '.join([a.strip() for a in bits[0].split('\n') if a.strip()]) + ' -> ' + ending.strip()
    match = py_ext_sig_re.match(first_line)
    # If it is a signature, make the change to lines.
    if match:
        new_lines = [first_line] + remainder
        lines[:] = new_lines
        return len(bits[0].split('\n'))
    else:
        return 0

# These methods are not displayed in the documentation of blocks to
# avoid redundancy.
common_block_members =[
    'check_topology',
    'detail',
    'history',
    'input_signature',
    'name',
    'nitems_read',
    'nitems_written',
    'nthreads',
    'output_multiple',
    'output_signature',
    'relative_rate',
    'set_detail',
    'set_nthreads',
    'start',
    'stop',
    'thisown',
    'to_basic_block',
    'unique_id',
    'make',
    ]

class OldBlockDocumenter(FunctionDocumenter):
    """
    Specialized Documenter subclass for gnuradio blocks.

    It merges together the documentation for the generator function (e.g. gr.head)
    with the wrapped sptr (e.g. gr.gr_head_sptr) to keep the documentation
    tidier.
    """
    objtype = 'oldblock'
    directivetype = 'function'
    # Don't want to use this for generic functions for give low priority.
    priority = -10 
    
    def __init__(self, *args, **kwargs):
        super(OldBlockDocumenter, self).__init__(*args, **kwargs)
        # Get class name
        bits = self.name.split('.')
        if len(bits) != 3 or bits[0] != 'gnuradio':
            raise ValueError("expected name to be of form gnuradio.x.y but it is {0}".format(self.name)) 
        sptr_name = 'gnuradio.{0}.{0}_{1}_sptr'.format(bits[1], bits[2])
        # Create a Class Documenter to create documentation for the classes members.
        self.classdoccer = ClassDocumenter(self.directive, sptr_name, indent=self.content_indent)
        self.classdoccer.doc_as_attr = False
        self.classdoccer.real_modname = self.classdoccer.get_real_modname()
        self.classdoccer.options.members = ALL
        self.classdoccer.options.exclude_members = common_block_members
        self.classdoccer.parse_name()
        self.classdoccer.import_object()

    def document_members(self, *args, **kwargs):
        return self.classdoccer.document_members(*args, **kwargs)

class BlockDocumenter(FunctionDocumenter):
    """
    Specialized Documenter subclass for new style gnuradio blocks.

    It merges together the documentation for the generator function (e.g. wavelet.squash_ff)
    with the wrapped sptr (e.g. wavelet.squash_ff_sptr) to keep the documentation
    tidier.
    """
    objtype = 'block'
    directivetype = 'function'
    # Don't want to use this for generic functions for give low priority.
    priority = -10 
    
    def __init__(self, *args, **kwargs):
        super(BlockDocumenter, self).__init__(*args, **kwargs)
        # Get class name
        sptr_name = self.name + '_sptr'
        # Create a Class Documenter to create documentation for the classes members.
        self.classdoccer = ClassDocumenter(self.directive, sptr_name, indent=self.content_indent)
        self.classdoccer.doc_as_attr = False
        self.classdoccer.real_modname = self.classdoccer.get_real_modname()
        self.classdoccer.options.members = ALL
        self.classdoccer.options.exclude_members = common_block_members
        self.classdoccer.parse_name()
        self.classdoccer.import_object()

    def document_members(self, *args, **kwargs):
        return self.classdoccer.document_members(*args, **kwargs)

class PyBlockDocumenter(ClassDocumenter):
    """
    Specialized Documenter subclass for hierarchical python gnuradio blocks.
    """
    objtype = 'pyblock'
    directivetype = 'class'
    
    def __init__(self, *args, **kwargs):
        super(PyBlockDocumenter, self).__init__(*args, **kwargs)
        self.options.members = ALL
        self.options.exclude_members = common_block_members