1# -*- coding: utf-8 -*-
2#
3#  doc.py - doc commander module
4#
5#  Copyright (C) 2010 - Jesse van den Kieboom
6#
7#  This program is free software; you can redistribute it and/or modify
8#  it under the terms of the GNU General Public License as published by
9#  the Free Software Foundation; either version 2 of the License, or
10#  (at your option) any later version.
11#
12#  This program is distributed in the hope that it will be useful,
13#  but WITHOUT ANY WARRANTY; without even the implied warranty of
14#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15#  GNU General Public License for more details.
16#
17#  You should have received a copy of the GNU General Public License
18#  along with this program; if not, write to the Free Software
19#  Foundation, Inc., 51 Franklin Street, Fifth Floor,
20#  Boston, MA 02110-1301, USA.
21
22import commander.commands as commands
23import commander.commands.completion
24import commander.commands.result
25import commander.commands.exceptions
26import re
27
28__commander_module__ = True
29
30class Argument:
31    def __init__(self, argtype, typename, name):
32        self.type = argtype.strip()
33        self.type_name = typename.strip()
34        self.name = name.strip()
35
36class Function:
37    def __init__(self, text):
38        self._parse(text)
39
40    def _parse(self, text):
41        self.valid = False
42
43        parser = re.compile('^\\s*(?:(?:\\b(?:static|inline)\\b)\\s+)?(([a-z_:][a-z0-9_:<>]*)(?:\\s*(?:\\b(?:const)\\b)\\s*)?\\s*[*&]*\\s+)?([a-z_][a-z0-9_:~]*)\\s*\\(([^)]*)\\)(\\s*const)?', re.I)
44
45        m = parser.match(text)
46
47        if not m:
48            return
49
50        self.valid = True
51
52        self.return_type = m.group(1) and m.group(1).strip() != 'void' and m.group(1).strip()
53        self.return_type_name = self.return_type and m.group(2).strip()
54
55        parts = m.group(3).split('::')
56        self.name = parts[-1]
57
58        if len(parts) > 1:
59            self.classname = '::'.join(parts[0:-1])
60        else:
61            self.classname = None
62
63        self.constructor = self.name == self.classname
64        self.destructor = self.name == '~%s' % (self.classname,)
65
66        self.const = m.group(5) != None
67        self.args = []
68
69        argre = re.compile('(([a-z_:][a-z0-9_:<>]*)(?:\\s*(?:\\s*\\bconst\\b\\s*|[*&])\s*)*)\\s*([a-z_][a-z_0-9]*)$', re.I)
70
71        for arg in m.group(4).split(','):
72            arg = arg.strip()
73
74            if arg == 'void' or arg == '':
75                continue
76            else:
77                m2 = argre.match(arg.strip())
78
79                if not m2:
80                    self.valid = False
81                    return
82
83                arg = Argument(m2.group(1), m2.group(2), m2.group(3))
84
85            self.args.append(arg)
86
87class Documenter:
88    def __init__(self, window, view, iter):
89        self.window = window
90        self.view = view
91        self.iter = iter
92
93        bus = self.window.get_message_bus()
94        self.canplaceholder = bus.is_registered('/plugins/snippets', 'parse-and-activate')
95
96        self.placeholder = 1
97        self.text = ''
98
99    def append(self, *args):
100        for text in args:
101            self.text += str(text)
102
103        return self
104
105    def append_placeholder(self, *args):
106        if not self.canplaceholder:
107            return self.append(*args)
108
109        text = " ".join(map(lambda x: str(x), args))
110        self.text += "${%d:%s}" % (self.placeholder, text)
111        self.placeholder += 1
112
113        return self
114
115    def insert(self):
116        if self.canplaceholder:
117            bus = self.window.get_message_bus()
118            bus.send('/plugins/snippets', 'parse-and-activate', snippet=self.text, iter=self.iter, view=self.view)
119
120def _make_documenter(window, view):
121    buf = view.get_buffer()
122
123    bus = window.get_message_bus()
124    canplaceholder = bus.lookup('/plugins/snippets', 'parse-and-activate') != None
125
126    insert = buf.get_iter_at_mark(buf.get_insert())
127    insert.set_line_offset(0)
128
129    offset = insert.get_offset()
130
131    end = insert.copy()
132
133    # This is just something random
134    if not end.forward_chars(500):
135        end = buf.get_end_iter()
136
137    text = insert.get_text(end)
138    func = Function(text)
139
140    if not func.valid:
141        raise commander.commands.exceptions.Execute('Could not find function specification')
142
143    doc = Documenter(window, view, insert)
144    return doc, func
145
146def gtk(window, view):
147    """Generate gtk-doc documentation: doc.gtk
148
149Generate a documentation template for the C or C++ function defined at the
150cursor. The cursor needs to be on the first line of the function declaration
151for it to work."""
152
153    buf = view.get_buffer()
154    lang = buf.get_language()
155
156    if not lang or not lang.get_id() in ('c', 'chdr', 'cpp'):
157        raise commander.commands.exceptions.Execute('Don\'t know about this language')
158
159    doc, func = _make_documenter(window, view)
160
161    # Generate docstring for this function
162    doc.append("/**\n * ", func.name, ":\n")
163    structp = re.compile('([A-Z]+[a-zA-Z]*)|struct\s+_([A-Z]+[a-zA-Z]*)')
164
165    for arg in func.args:
166        sm = structp.match(arg.type_name)
167        doc.append(" * @", arg.name, ": ")
168
169        if sm:
170            doc.append_placeholder("a #%s" % (sm.group(1) or sm.group(2),))
171        else:
172            doc.append_placeholder("Description")
173
174        doc.append(".\n")
175
176    doc.append(" *\n * ").append_placeholder("Description").append(".\n")
177
178    if func.return_type:
179        sm = structp.match(func.return_type_name)
180        doc.append(" *\n * Returns: ")
181
182        if sm:
183            doc.append_placeholder("a #%s" % (sm.group(1) or sm.group(2),))
184        else:
185            doc.append_placeholder("Description")
186
187        doc.append(".\n")
188
189    doc.append(" *\n **/\n")
190    doc.insert()
191
192def doxygen(window, view):
193    """Generate doxygen documentation: doc.doxygen
194
195Generate a documentation template for the function defined at the
196cursor. The cursor needs to be on the first line of the function declaration
197for it to work."""
198
199    buf = view.get_buffer()
200
201    if not buf.get_language().get_id() in ('c', 'chdr', 'cpp'):
202        raise commander.commands.exceptions.Execute('Don\'t know about this language')
203
204    doc, func = _make_documenter(window, view)
205
206    # Generate docstring for this function
207    doc.append("/** \\brief ").append_placeholder("Short description")
208
209    if func.const:
210        doc.append(" (const)")
211
212    doc.append(".\n")
213
214    for arg in func.args:
215        doc.append(" * @param ", arg.name, " ").append_placeholder("Description").append("\n")
216
217    doc.append(" *\n * ")
218
219    if func.constructor:
220        doc.append("Constructor.\n *\n * ")
221    elif func.destructor:
222        doc.append("Destructor.\n *\n * ")
223
224    doc.append_placeholder("Detailed description").append(".\n")
225
226    if func.return_type:
227        doc.append(" *\n * @return: ")
228
229        if func.return_type == 'bool':
230            doc.append("true if ").append_placeholder("Description").append(", false otherwise")
231        else:
232            doc.append_placeholder("Description")
233
234        doc.append("\n")
235
236    doc.append(" *\n */\n")
237    doc.insert()
238
239# ex:ts=4:et
240