Blob Blame History Raw
# -*- coding: utf-8 -*-
#
#  doc.py - doc commander module
#
#  Copyright (C) 2010 - Jesse van den Kieboom
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor,
#  Boston, MA 02110-1301, USA.

import commander.commands as commands
import commander.commands.completion
import commander.commands.result
import commander.commands.exceptions
import re

__commander_module__ = True

class Argument:
    def __init__(self, argtype, typename, name):
        self.type = argtype.strip()
        self.type_name = typename.strip()
        self.name = name.strip()

class Function:
    def __init__(self, text):
        self._parse(text)

    def _parse(self, text):
        self.valid = False

        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)

        m = parser.match(text)

        if not m:
            return

        self.valid = True

        self.return_type = m.group(1) and m.group(1).strip() != 'void' and m.group(1).strip()
        self.return_type_name = self.return_type and m.group(2).strip()

        parts = m.group(3).split('::')
        self.name = parts[-1]

        if len(parts) > 1:
            self.classname = '::'.join(parts[0:-1])
        else:
            self.classname = None

        self.constructor = self.name == self.classname
        self.destructor = self.name == '~%s' % (self.classname,)

        self.const = m.group(5) != None
        self.args = []

        argre = re.compile('(([a-z_:][a-z0-9_:<>]*)(?:\\s*(?:\\s*\\bconst\\b\\s*|[*&])\s*)*)\\s*([a-z_][a-z_0-9]*)$', re.I)

        for arg in m.group(4).split(','):
            arg = arg.strip()

            if arg == 'void' or arg == '':
                continue
            else:
                m2 = argre.match(arg.strip())

                if not m2:
                    self.valid = False
                    return

                arg = Argument(m2.group(1), m2.group(2), m2.group(3))

            self.args.append(arg)

class Documenter:
    def __init__(self, window, view, iter):
        self.window = window
        self.view = view
        self.iter = iter

        bus = self.window.get_message_bus()
        self.canplaceholder = bus.is_registered('/plugins/snippets', 'parse-and-activate')

        self.placeholder = 1
        self.text = ''

    def append(self, *args):
        for text in args:
            self.text += str(text)

        return self

    def append_placeholder(self, *args):
        if not self.canplaceholder:
            return self.append(*args)

        text = " ".join(map(lambda x: str(x), args))
        self.text += "${%d:%s}" % (self.placeholder, text)
        self.placeholder += 1

        return self

    def insert(self):
        if self.canplaceholder:
            bus = self.window.get_message_bus()
            bus.send('/plugins/snippets', 'parse-and-activate', snippet=self.text, iter=self.iter, view=self.view)

def _make_documenter(window, view):
    buf = view.get_buffer()

    bus = window.get_message_bus()
    canplaceholder = bus.lookup('/plugins/snippets', 'parse-and-activate') != None

    insert = buf.get_iter_at_mark(buf.get_insert())
    insert.set_line_offset(0)

    offset = insert.get_offset()

    end = insert.copy()

    # This is just something random
    if not end.forward_chars(500):
        end = buf.get_end_iter()

    text = insert.get_text(end)
    func = Function(text)

    if not func.valid:
        raise commander.commands.exceptions.Execute('Could not find function specification')

    doc = Documenter(window, view, insert)
    return doc, func

def gtk(window, view):
    """Generate gtk-doc documentation: doc.gtk

Generate a documentation template for the C or C++ function defined at the
cursor. The cursor needs to be on the first line of the function declaration
for it to work."""

    buf = view.get_buffer()
    lang = buf.get_language()

    if not lang or not lang.get_id() in ('c', 'chdr', 'cpp'):
        raise commander.commands.exceptions.Execute('Don\'t know about this language')

    doc, func = _make_documenter(window, view)

    # Generate docstring for this function
    doc.append("/**\n * ", func.name, ":\n")
    structp = re.compile('([A-Z]+[a-zA-Z]*)|struct\s+_([A-Z]+[a-zA-Z]*)')

    for arg in func.args:
        sm = structp.match(arg.type_name)
        doc.append(" * @", arg.name, ": ")

        if sm:
            doc.append_placeholder("a #%s" % (sm.group(1) or sm.group(2),))
        else:
            doc.append_placeholder("Description")

        doc.append(".\n")

    doc.append(" *\n * ").append_placeholder("Description").append(".\n")

    if func.return_type:
        sm = structp.match(func.return_type_name)
        doc.append(" *\n * Returns: ")

        if sm:
            doc.append_placeholder("a #%s" % (sm.group(1) or sm.group(2),))
        else:
            doc.append_placeholder("Description")

        doc.append(".\n")

    doc.append(" *\n **/\n")
    doc.insert()

def doxygen(window, view):
    """Generate doxygen documentation: doc.doxygen

Generate a documentation template for the function defined at the
cursor. The cursor needs to be on the first line of the function declaration
for it to work."""

    buf = view.get_buffer()

    if not buf.get_language().get_id() in ('c', 'chdr', 'cpp'):
        raise commander.commands.exceptions.Execute('Don\'t know about this language')

    doc, func = _make_documenter(window, view)

    # Generate docstring for this function
    doc.append("/** \\brief ").append_placeholder("Short description")

    if func.const:
        doc.append(" (const)")

    doc.append(".\n")

    for arg in func.args:
        doc.append(" * @param ", arg.name, " ").append_placeholder("Description").append("\n")

    doc.append(" *\n * ")

    if func.constructor:
        doc.append("Constructor.\n *\n * ")
    elif func.destructor:
        doc.append("Destructor.\n *\n * ")

    doc.append_placeholder("Detailed description").append(".\n")

    if func.return_type:
        doc.append(" *\n * @return: ")

        if func.return_type == 'bool':
            doc.append("true if ").append_placeholder("Description").append(", false otherwise")
        else:
            doc.append_placeholder("Description")

        doc.append("\n")

    doc.append(" *\n */\n")
    doc.insert()

# ex:ts=4:et