Completed
Pull Request — master (#2623)
by
unknown
01:51
created

ManPageFormatter   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 132
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 132
rs 10
c 0
b 0
f 0
wmc 23
1
import argparse
2
import datetime
3
from distutils.core import Command
4
from distutils.errors import DistutilsOptionError
5
6
7
class BuildManPage(Command):
8
    """
9
    Add a ``build_manpage`` command  to your setup.py.
10
    To use this Command class add a command to call this class::
11
12
        # For setuptools
13
        setup(
14
              entry_points={
15
                "distutils.commands": [
16
                    "build_manpage = coalib.misc.BuildManPage:BuildManPage"
17
                ]
18
              }
19
        )
20
21
        # For distutils
22
        from coalib.misc.BuildManPage import BuildManPage
23
        setup(
24
              cmdclass={'build_manpage': BuildManPage}
25
        )
26
27
    You can then use the following setup command to produce a man page::
28
29
        $ python setup.py build_manpage --output=coala.1 \
30
            --parser=coalib.parsing.DefaultArgParser:default_arg_parser
31
32
    If automatically want to build the man page every time you invoke
33
    your build, add to your ```setup.cfg``` the following::
34
35
        [build_manpage]
36
        output = <appname>.1
37
        parser = <path_to_your_parser>
38
    """
39
    user_options = [
40
        ('output=', 'O', 'output file'),
41
        ('parser=', None, 'module path to an ArgumentParser instance'
42
         '(e.g. mymod:func, where func is a method or function which return'
43
         'an arparse.ArgumentParser instance.'),
44
    ]
45
46
    def initialize_options(self):
47
        self.output = None
48
        self.parser = None
49
50
    def finalize_options(self):
51
        if self.output is None:
52
            raise DistutilsOptionError('\'output\' option is required')
53
        if self.parser is None:
54
            raise DistutilsOptionError('\'parser\' option is required')
55
        mod_name, func_name = self.parser.split(':')
56
        fromlist = mod_name.split('.')
57
        mod = __import__(mod_name, fromlist=fromlist)
58
        self._parser = (
59
            getattr(mod, func_name)(formatter_class=ManPageFormatter))
60
61
        self.announce('Writing man page %s' % self.output)
62
        self._today = datetime.date.today()
63
64
    def run(self):
65
        dist = self.distribution
66
        homepage = dist.get_url()
67
        maintainer = dist.get_maintainer()
68
        _license = dist.get_license()
69
        appname = self._parser.prog
70
71
        sections = {"see also": ("Online documentation: {}".format(homepage)),
72
                    "maintainer(s)": maintainer,
73
                    "license": _license}
74
75
        dist = self.distribution
76
        mpf = ManPageFormatter(appname,
77
                               desc=dist.get_description(),
78
                               long_desc=dist.get_long_description(),
79
                               ext_sections=sections,
80
                               parser=self._parser)
81
82
        formatted_man_page = mpf.format_man_page()
83
84
        with open(self.output, 'w') as man_file:
85
            man_file.write(formatted_man_page)
86
87
88
class ManPageFormatter(argparse.HelpFormatter):
89
90
    def __init__(self,
91
                 prog,
92
                 indent_increment=2,
93
                 max_help_position=24,
94
                 width=None,
95
                 desc=None,
96
                 long_desc=None,
97
                 ext_sections=None,
98
                 parser=None):
99
        argparse.HelpFormatter.__init__(self, prog)
100
101
        self._prog = prog
102
        self._section = 1
103
        self._today = datetime.date.today().strftime('%Y\\-%m\\-%d')
104
        self._desc = desc
105
        self._long_desc = long_desc
106
        self._ext_sections = ext_sections
107
        self._parser = parser
108
109
    def _format_action_invocation(self, action):
110
        if not action.option_strings:
111
            metavar, = self._metavar_formatter(action, action.dest)(1)
112
            return metavar
113
114
        else:
115
            # if the Optional doesn't take a value, format is:
116
            #    -s, --long
117
            if action.nargs == 0:
118
                parts = [ManPageFormatter._bold(action_str)
119
                         for action_str in action.option_strings]
120
121
            # if the Optional takes a value, format is:
122
            #    -s ARGS, --long ARGS
123
            else:
124
                default = ManPageFormatter._underline(action.dest.upper())
125
                args_string = self._format_args(action, default)
126
                parts = ['%s %s' % (self._bold(option_string), args_string)
127
                         for option_string in action.option_strings]
128
129
            return ', '.join(parts)
130
131
    @staticmethod
132
    def _markup(string):
133
        return string.replace('-', '\\-')
134
135
    @staticmethod
136
    def _add_format(string, front, back):
137
        if not string.strip().startswith(front):
138
            string = front + string
139
        if not string.strip().endswith(back):
140
            string = string + back
141
        return string
142
143
    @staticmethod
144
    def _underline(string):
145
        return ManPageFormatter._add_format(string, "\\fI", "\\fR")
146
147
    @staticmethod
148
    def _bold(string):
149
        return ManPageFormatter._add_format(string, "\\fB", "\\fR")
150
151
    def _mk_title(self):
152
        return '.TH {0} {1} {2}\n'.format(self._prog,
153
                                          self._section,
154
                                          self._today)
155
156
    def _mk_name(self):
157
        return '.SH NAME\n%s\n' % (self._parser.prog)
158
159
    def _mk_synopsis(self):
160
        self.add_usage(self._parser.usage,
161
                       self._parser._actions,
162
                       self._parser._mutually_exclusive_groups,
163
                       prefix='')
164
        usage = self._format_usage(None,
165
                                   self._parser._actions,
166
                                   self._parser._mutually_exclusive_groups,
167
                                   '')
168
169
        usage = usage.replace('%s ' % self._prog, '')
170
        usage = ('.SH SYNOPSIS\n \\fB%s\\fR %s\n'
171
                 % (ManPageFormatter._markup(self._prog), usage))
172
        return usage
173
174
    def _mk_description(self):
175
        if self._long_desc:
176
            long_desc = self._long_desc.replace('\n', '\n.br\n')
177
            return '.SH DESCRIPTION\n%s\n' % self._markup(long_desc)
178
        else:
179
            return ''
180
181
    def _mk_options(self):
182
        formatter = self._parser._get_formatter()
183
184
        # positionals, optionals and user-defined groups
185
        for action_group in self._parser._action_groups:
186
            formatter.start_section(None)
187
            formatter.add_text(None)
188
            formatter.add_arguments(action_group._group_actions)
189
            formatter.end_section()
190
191
        # epilog
192
        formatter.add_text(self._parser.epilog)
193
194
        # determine help from format above
195
        return '.SH OPTIONS\n' + formatter.format_help()
196
197
    def _mk_footer(self):
198
        sections = self._ext_sections
199
        if not hasattr(sections, '__iter__'):
200
            return ''
201
202
        footer = []
203
204
        for section in sorted(sections.keys()):
205
            part = ".SH {}\n {}".format(section.upper(), sections[section])
206
            footer.append(part)
207
208
        return '\n'.join(footer)
209
210
    def format_man_page(self):
211
        page = []
212
        page.append(self._mk_title())
213
        page.append(self._mk_name())
214
        page.append(self._mk_synopsis())
215
        page.append(self._mk_description())
216
        page.append(self._mk_options())
217
        page.append(self._mk_footer())
218
219
        return ''.join(page)
220