1""" A cmake extension for Sphinx
2
3tailored for the Dune project.
4This is used during `make doc` to build the
5build system documentation.
6"""
7
8from docutils import nodes
9from docutils.parsers.rst import Directive
10from itertools import chain
11
12class CMakeParamNode(nodes.Element):
13    pass
14
15class CMakeBriefNode(nodes.Element):
16    pass
17
18class CMakeFunction(Directive):
19    # We do require the name to be an argument
20    required_arguments = 1
21    optional_arguments = 0
22    final_argument_whitespace = False
23    has_content = True
24
25    def run(self):
26        env = self.state.document.settings.env
27
28        # Parse the content of the directive recursively
29        node = nodes.Element()
30        node.document = self.state.document
31        self.state.nested_parse(self.content, self.content_offset, node)
32
33        brief_nodes = []
34        output_nodes = []
35        positional_params = []
36        required_params = {}
37        optional_params = {}
38
39        for child in node:
40            if isinstance(child, CMakeParamNode):
41                if child["positional"]:
42                    positional_params.append(child)
43                elif child["required"]:
44                    required_params[child["name"]] = child
45                else:
46                    optional_params[child["name"]] = child
47            elif isinstance(child, CMakeBriefNode):
48                par = nodes.paragraph()
49                self.state.nested_parse(child['content'], self.content_offset, par)
50                brief_nodes.append(par)
51            else:
52                output_nodes.append(child)
53
54        def render_required(paramnode):
55            if paramnode["multi"]:
56                sl.append(" "*5 + paramnode['name'] + ' ' + paramnode['argname'] + '1 [' + paramnode['argname'] + '2 ...]\n')
57            if paramnode["single"]:
58                sl.append(" "*5 + paramnode['name'] + ' ' + paramnode['argname'] + '\n')
59            if paramnode["option"]:
60                sl.append(" "*5 + paramnode['name'] + '\n')
61            if paramnode["special"]:
62                sl.append(" "*5 + paramnode['argname'] + '\n')
63
64        def render_optional(paramnode):
65            if paramnode["multi"]:
66                sl.append(' '*4 + '[' + paramnode['name'] + ' ' + paramnode['argname'] + '1 [' + paramnode['argname'] + '2 ...]' + ']\n')
67            if paramnode["single"]:
68                sl.append(" "*4 + '['+ paramnode['name'] + ' ' + paramnode['argname'] + ']\n')
69            if paramnode["option"]:
70                sl.append(" "*4 + '['+ paramnode['name'] + ']\n')
71            if paramnode["special"]:
72                sl.append(" "*4 + '['+ paramnode['argname'] + ']\n')
73
74        # Build the content of the box
75        sl = [self.arguments[0] + '(\n']
76
77        for paramnode in positional_params:
78            if paramnode["required"]:
79                render_required(paramnode)
80            else:
81                render_optional(paramnode)
82
83        for rp, paramnode in required_params.items():
84            render_required(paramnode)
85        for op, paramnode in optional_params.items():
86            render_optional(paramnode)
87
88        sl.append(")\n")
89        lb = nodes.literal_block(''.join(sl), ''.join(sl))
90        brief_nodes.append(lb)
91
92        dl = nodes.definition_list()
93        for paramnode in chain(positional_params, required_params.values(), optional_params.values()):
94            dli = nodes.definition_list_item()
95            dl += dli
96
97            dlit = nodes.term(text=paramnode["name"])
98            dli += dlit
99
100            dlic = nodes.definition()
101            dli += dlic
102            self.state.nested_parse(paramnode['content'], self.content_offset, dlic)
103
104        # add the parameter list to the output
105        brief_nodes.append(dl)
106
107        return brief_nodes + output_nodes
108
109class CMakeBrief(Directive):
110    required_arguments = 0
111    optional_arguments = 0
112    final_argument_whitespace = False
113    has_content = True
114
115    def run(self):
116        node = CMakeBriefNode()
117        node['content'] = self.content
118        return [node]
119
120class CMakeParam(Directive):
121    # We do require the name to be an argument
122    required_arguments = 1
123    optional_arguments = 0
124    final_argument_whitespace = False
125    option_spec = {'argname' : lambda s: s,
126                   'multi': lambda s: True,
127                   'option': lambda s: True,
128                   'positional' : lambda s: True,
129                   'required': lambda s: True,
130                   'single': lambda s: True,
131                   'special': lambda s: True
132                   }
133    has_content = True
134
135    def run(self):
136        node = CMakeParamNode()
137        # set defaults:
138        node['name'] = self.arguments[0]
139        node['single'] = self.options.get('single', False)
140        node['multi'] = self.options.get('multi', False)
141        node['option'] = self.options.get('option', False)
142        node['special'] = self.options.get('special', False)
143        node['positional'] = self.options.get('positional', False)
144        node['required'] = self.options.get('required', False)
145        node['argname'] = self.options.get('argname', self.arguments[0].lower() if self.arguments[0].lower()[-1:] != 's' else self.arguments[0].lower()[:-1])
146        node['content'] = self.content
147        if node['positional']:
148            node['argname'] = ''
149        return [node]
150
151
152class CMakeVariable(Directive):
153    # We do require the name to be an argument
154    required_arguments = 1
155    optional_arguments = 0
156    final_argument_whitespace = False
157    option_spec = {'argname' : lambda s: s,
158                   'multi': lambda s: True,
159                   'option': lambda s: True,
160                   'positional' : lambda s: True,
161                   'required': lambda s: True,
162                   'single': lambda s: True
163                   }
164    has_content = True
165
166    def run(self):
167        node = nodes.paragraph()
168        self.state.nested_parse(self.content, self.content_offset, node)
169        return [node]
170
171class CMakeModule(Directive):
172    required_arguments = 0
173    optional_arguments = 0
174    final_argument_whitespace = False
175    has_content = True
176
177    def run(self):
178        node = nodes.paragraph()
179        self.state.nested_parse(self.content, self.content_offset, node)
180        return [node]
181
182def setup(app):
183    app.add_node(CMakeBriefNode)
184    app.add_node(CMakeParamNode)
185    app.add_directive('cmake_module', CMakeModule)
186    app.add_directive('cmake_brief', CMakeBrief)
187    app.add_directive('cmake_function', CMakeFunction)
188    app.add_directive('cmake_param', CMakeParam)
189    app.add_directive('cmake_variable', CMakeVariable)
190
191    return {'version': '0.1'}
192