make_man.py 9.64 KB
Newer Older
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3

4
# Copyright(C) 2010-2018 Laurent Bachelier
5
#
6
# This file is part of weboob.
7
#
8
# weboob is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU Lesser General Public License as published by
10 11 12 13
# 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,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU Lesser General Public License for more details.
17
#
18
# You should have received a copy of the GNU Lesser General Public License
19
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
20

21
from __future__ import absolute_import, print_function
Romain Bignon's avatar
Romain Bignon committed
22 23 24 25 26 27 28 29

import imp
import inspect
import optparse
import os
import re
import sys
import time
30
from datetime import datetime
31
from textwrap import dedent
Romain Bignon's avatar
Romain Bignon committed
32

33
from weboob.tools.application.base import Application
34 35

BASE_PATH = os.path.join(os.path.dirname(__file__), os.pardir)
36
DEST_DIR = 'man'
37
COMP_PATH = 'tools/weboob_bash_completion'
38

39

40
class ManpageHelpFormatter(optparse.HelpFormatter):
41
    def __init__(self,
42 43 44 45 46
                 app,
                 indent_increment=0,
                 max_help_position=0,
                 width=80,
                 short_first=1):
47
        optparse.HelpFormatter.__init__(self, indent_increment, max_help_position, width, short_first)
48
        self.app = app
49 50 51 52

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

53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
    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):
68 69 70
        desc = u'.SH DESCRIPTION\n.LP\n\n%s\n' % description
        if hasattr(self.app, 'CAPS'):
            self.app.weboob.modules_loader.load_all()
71
            caps = self.app.CAPS if isinstance(self.app.CAPS, tuple) else (self.app.CAPS,)
72
            modules = []
73
            for name, module in self.app.weboob.modules_loader.loaded.items():
74
                if module.has_caps(*caps):
75 76
                    modules.append(u'* %s (%s)' % (name, module.description))
            if len(modules) > 0:
77
                desc += u'\n.SS Supported websites:\n'
78
                desc += u'\n.br\n'.join(sorted(modules))
79
        return desc
80 81 82

    def format_commands(self, commands):
        s = u''
83
        for section, cmds in commands.items():
84 85
            if len(cmds) == 0:
                continue
86
            s += '.SH %s COMMANDS\n' % section.upper()
Romain Bignon's avatar
Romain Bignon committed
87
            for cmd in sorted(cmds):
88 89 90 91 92 93 94 95 96 97 98 99 100
                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

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

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


def main():
108
    scripts_path = os.path.join(BASE_PATH, 'weboob', 'applications')
109
    files = os.listdir(scripts_path)
110
    completions = dict()
111 112 113

    for fname in files:
        fpath = os.path.join(scripts_path, fname)
114 115 116 117 118 119 120 121 122 123 124 125 126 127
        if os.path.isdir(fpath) and not fname.startswith('_'):
            try:
                fp, pathname, description = imp.find_module(fname, [scripts_path])
                module = imp.load_module(fname, fp, pathname, description)
            except OSError as e:
                print("Unable to load the %s application (%s)"
                      % (fname, e), file=sys.stderr)
            else:
                print("Loaded %s" % fname)
                # Find the applications we can handle
                for klass in module.__dict__.values():
                    if inspect.isclass(klass) and issubclass(klass, Application) and klass.VERSION:
                        completions[klass.APPNAME] = analyze_application(klass, klass.APPNAME)

128
    write_completions(completions)
129

130 131 132 133

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

134

135 136 137 138 139
# 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 = []
140 141


142 143
def analyze_application(app, script_name):
    application = app()
144
    applications.append(application)
145

146 147
    formatter = ManpageHelpFormatter(application)

148
    # patch the application
149 150
    application._parser.prog = "%s" % script_name
    application._parser.formatter = formatter
151 152 153 154 155
    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"\-")
156
    coding = r'.\" -*- coding: utf-8 -*-'
157
    comment = r'.\" This file was generated automatically by tools/make_man.sh.'
158 159
    header = '.TH %s 1 "%s" "%s %s"' % (script_name.upper(), time.strftime("%d %B %Y"),
                                        script_name, app.VERSION.replace('.', '\\&.'))
160
    name = ".SH NAME\n%s \- %s" % (script_name, application.SHORT_DESCRIPTION)
161
    condition = """.SH CONDITION
162
The \-c and \-\-condition is a flexible way to filter and get only interesting results. It supports conditions on numerical values, dates, durations and strings. Dates are given in YYYY\-MM\-DD or YYYY\-MM\-DD HH:MM format. Durations look like XhYmZs where X, Y and Z are integers. Any of them may be omitted. For instance, YmZs, XhZs or Ym are accepted.
163 164 165
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.
166
.SS The following operators are supported:
167 168
.TP
=
169
Test if object.field is equal to the value.
170 171
.TP
!=
172
Test if object.field is not equal to the value.
173 174
.TP
>
175
Test if object.field is greater than the value. If object.field is date, return true if value is before that object.field.
176 177
.TP
<
178
Test if object.field is less than the value. If object.field is date, return true if value is after that object.field.
179 180
.TP
|
181 182
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
183 184 185 186
.LP
You can make a expression combinations with the keywords \\fB" AND "\\fR, \\fB" OR "\\fR an \\fB" LIMIT "\\fR.
.LP
The \\fBLIMIT\\fR keyword can be used to limit the number of items upon which running the expression. \\fBLIMIT\\fR can only be placed at the end of the expression followed by the number of elements you want.
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
.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
204
.B boobank history account@backend \-\-condition 'date>2013\-12\-01 AND date<2013\-12\-09'
205 206
.fi
Get transactions betweens the 2th December and 8th December 2013.
207 208 209 210 211
.PP
.nf
.B boobank history account@backend \-\-condition 'date>2013\-12\-01  LIMIT 10'
.fi
Get transactions after the 2th December in the last 10 transactions
212
"""
213 214 215
    footer = """.SH COPYRIGHT
%s
.LP
216
For full copyright information see the COPYING file in the weboob package.
217 218 219
.LP
.RE
.SH FILES
220
 "~/.config/weboob/backends" """ % application.COPYRIGHT.replace('YEAR', '%d' % datetime.today().year)
221
    if len(app.CONFIG) > 0:
222
        footer += '\n\n "~/.config/weboob/%s"' % app.APPNAME
223

224
    # Skip internal applications.
Romain Bignon's avatar
Romain Bignon committed
225
    footer += "\n\n.SH SEE ALSO\nHome page: http://weboob.org/applications/%s" % application.APPNAME
226

227
    mantext = u"%s\n%s\n%s\n%s\n%s\n%s\n%s" % (coding, comment, header, name, helptext, condition, footer)
228
    with open(os.path.join(BASE_PATH, DEST_DIR, "%s.1" % script_name), 'w+') as manfile:
229
        for line in mantext.split('\n'):
230
            manfile.write('%s\n' % line.lstrip())
231
    print("wrote %s/%s.1" % (DEST_DIR, script_name))
232

233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
    return application._shell_completion_items()


def write_completions(completions):
    compscript = dedent('''
    # Weboob completion for Bash (automatically generated by tools/make_man.sh)
    #
    # vim: filetype=sh expandtab softtabstop=4 shiftwidth=4
    #
    # This file is part of weboob.
    #
    # This script can be distributed under the same license as the
    # weboob or bash packages.
    ''')
    for name, items in completions.items():
        compscript += dedent('''
        _weboob_{1}()
        {{
            local cur args

            COMPREPLY=()
            cur=${{COMP_WORDS[COMP_CWORD]}}
            args="{2}"

            COMPREPLY=( $(compgen -o default -W "${{args}}" -- "$cur" ) )
        }}
        complete -F _weboob_{1} {0}
        ''').format(name, name.replace('-', '_'), ' '.join(items))
    with open(os.path.join(BASE_PATH, COMP_PATH), 'w') as f:
        f.write(compscript)


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