Skip to content
make_man.py 8.46 KiB
Newer Older
#!/usr/bin/env python
# -*- coding: utf-8 -*-

Romain Bignon's avatar
Romain Bignon committed
# Copyright(C) 2010-2011 Laurent Bachelier
Romain Bignon's avatar
Romain Bignon committed
# This file is part of weboob.
Romain Bignon's avatar
Romain Bignon committed
# weboob is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# weboob is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Romain Bignon's avatar
Romain Bignon committed
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
Romain Bignon's avatar
Romain Bignon committed
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
import sys
import os
import tempfile
import imp
Florent's avatar
Florent committed
import inspect
import optparse
import re
import time

from weboob.tools.application.base import Application

BASE_PATH = os.path.join(os.path.dirname(__file__), os.pardir)
DEST_DIR = 'man'
class ManpageHelpFormatter(optparse.HelpFormatter):
    def __init__(self,
                 app,
                 indent_increment=0,
                 max_help_position=0,
                 width=80,
                 short_first=1):
        optparse.HelpFormatter.__init__(self, indent_increment, max_help_position, width, short_first)
        self.app = app

    def format_heading(self, heading):
        return ".SH %s\n" % heading.upper()

    def format_usage(self, usage):
        txt = ''
        for line in usage.split('\n'):
            line = line.lstrip().split(' ', 1)
            if len(txt) > 0:
                txt += '.br\n'
            txt += '.B %s\n' % line[0]

            arg_re = re.compile(r'([\[\s])([\w_]+)')
            args = re.sub(arg_re, r"\1\\fI\2\\fR", line[1])
            txt += args
            txt += '\n'
        return '.SH SYNOPSIS\n%s' % txt

    def format_description(self, description):
        desc = u'.SH DESCRIPTION\n.LP\n\n%s\n' % description
        if hasattr(self.app, 'CAPS'):
            self.app.weboob.modules_loader.load_all()
            backends = []
            for name, backend in self.app.weboob.modules_loader.loaded.iteritems():
                if backend.has_caps(self.app.CAPS):
                    backends.append(u'* %s (%s)' % (name, backend.description))
            if len(backends) > 0:
                desc += u'\n.SS Supported websites:\n'
                desc += u'\n.br\n'.join(sorted(backends))

    def format_commands(self, commands):
        s = u''
        for section, cmds in commands.iteritems():
            if len(cmds) == 0:
                continue
            s += '.SH %s COMMANDS\n' % section.upper()
Romain Bignon's avatar
Romain Bignon committed
            for cmd in sorted(cmds):
                s += '.TP\n'
                h = cmd.split('\n')
                if ' ' in h[0]:
                    cmdname, args = h[0].split(' ', 1)
                    arg_re = re.compile(r'([A-Z_]+)')
                    args = re.sub(arg_re, r"\\fI\1\\fR", args)

                    s += '\\fB%s\\fR %s' % (cmdname, args)
                else:
                    s += '\\fB%s\\fR' % h[0]
                s += '%s\n' % '\n.br\n'.join(h[1:])
        return s

    def format_option_strings(self, option):
        opts = optparse.HelpFormatter.format_option_strings(self, option).split(", ")

        return ".TP\n" + ", ".join("\\fB%s\\fR" % opt for opt in opts)


def main():
    scripts_path = os.path.join(BASE_PATH, "scripts")
    files = os.listdir(scripts_path)

    # Create a fake "scripts" modules to import the scripts into
    sys.modules["scripts"] = imp.new_module("scripts")

    for fname in files:
        fpath = os.path.join(scripts_path, fname)
        if os.path.isfile(fpath) and os.access(fpath, os.X_OK):
            with open(fpath) as f:
                # Python will likely want create a compiled file, we provide a place
                tmpdir = os.path.join(tempfile.gettempdir(), "weboob", "make_man")
                if not os.path.isdir(tmpdir):
                    os.makedirs(tmpdir)
                tmpfile = os.path.join(tmpdir, fname)

                desc = ("", "U", imp.PY_SOURCE)
                try:
                    script = imp.load_module("scripts.%s" % fname, f, tmpfile, desc)
                except ImportError as e:
                    print >>sys.stderr, "Unable to load the %s script (%s)" \
                        % (fname, e)
                else:
                    print "Loaded %s" % fname
                    # Find the applications we can handle
                    for klass in script.__dict__.itervalues():
                        if inspect.isclass(klass) and issubclass(klass, Application):
                            analyze_application(klass, fname)
                finally:
                    # Cleanup compiled files if needed
                    if (os.path.isfile(tmpfile + "c")):
                        os.unlink(tmpfile + "c")


def format_title(title):
    return re.sub(r'^(.+):$', r'.SH \1\n.TP', title.group().upper())

# XXX useful because the PyQt QApplication destructor crashes sometimes. By
# keeping every applications until program end, it prevents to stop before
# every manpages have been generated. If it crashes at exit, it's not a
# really a problem.
applications = []
def analyze_application(app, script_name):
    application = app()
    formatter = ManpageHelpFormatter(application)

    # patch the application
    application._parser.prog = "%s" % script_name
    application._parser.formatter = formatter
    helptext = application._parser.format_help(formatter)

    cmd_re = re.compile(r'^.+ Commands:$', re.MULTILINE)
    helptext = re.sub(cmd_re, format_title, helptext)
    helptext = helptext.replace("-", r"\-")
    coding = r'.\" -*- coding: utf-8 -*-'
    comment = r'.\" This file was generated automatically by tools/make_man.sh.'
    header = '.TH %s 1 "%s" "%s %s"' % (script_name.upper(), time.strftime("%d %B %Y"),
                                        script_name, app.VERSION.replace('.', '\\&.'))
    name = ".SH NAME\n%s \- %s" % (script_name, application.SHORT_DESCRIPTION)
Florent's avatar
Florent committed
    condition = """.SH CONDITION
Florent's avatar
Florent committed
The \-c and \-\-condition is a flexible way to sort and get only interesting results. It supports conditions on numerical values, dates, and strings. Dates are given in YYYY\-MM\-DD format.
Florent's avatar
Florent committed
The syntax of one expression is "\\fBfield operator value\\fR". The field to test is always the left member of the expression.
.LP
The field is a member of the objects returned by the command. For example, a bank account has "balance", "coming" or "label" fields.
.SS The following operators are supported:
Test if object.field is equal to the value.
Test if object.field is not equal to the value.
Test if object.field is greater than the value. If object.field is date, return true if value is before that object.field.
Test if object.field is less than the value. If object.field is date, return true if value is after that object.field.
This operator is available only for string fields. It works like the Unix standard \\fBgrep\\fR command, and returns True if the pattern specified in the value is in object.field.
.SS Expression combination
You can make a expression combinations with the keywords \\fB" AND "\\fR and \\fB" OR "\\fR.
Florent's avatar
Florent committed

.SS Examples:
.nf
.B boobank ls \-\-condition 'label=Livret A'
.fi
Display only the "Livret A" account.
.PP
.nf
.B boobank ls \-\-condition 'balance>10000'
.fi
Display accounts with a lot of money.
.PP
.nf
.B boobank history account@backend \-\-condition 'label|rewe'
.fi
Get transactions containing "rewe".
.PP
.nf
Florent's avatar
Florent committed
.B boobank history account@backend \-\-condition 'date>2013\-12\-01 AND date<2013\-12\-09'
Florent's avatar
Florent committed
.fi
Get transactions betweens the 2th December and 8th December 2013.
"""
    footer = """.SH COPYRIGHT
%s
.LP
For full COPYRIGHT see COPYING file with weboob package.
.LP
.RE
.SH FILES
Florent's avatar
Florent committed
 "~/.config/weboob/backends" """ % application.COPYRIGHT
        footer += '\n\n "~/.config/weboob/%s"' % app.APPNAME
    # Skip internal applications.
Romain Bignon's avatar
Romain Bignon committed
    footer += "\n\n.SH SEE ALSO\nHome page: http://weboob.org/applications/%s" % application.APPNAME
    mantext = u"%s\n%s\n%s\n%s\n%s\n%s\n%s" % (coding, comment, header, name, helptext, condition, footer)
    with open(os.path.join(BASE_PATH, DEST_DIR, "%s.1" % script_name), 'w+') as manfile:
            manfile.write('%s\n' % line.lstrip().encode('utf-8'))
    print "wrote %s/%s.1" % (DEST_DIR, script_name)

if __name__ == '__main__':
    sys.exit(main())