Bladeren bron

update setup, add man page builder

Oz N Tiram 9 jaren geleden
bovenliggende
commit
651933995b
1 gewijzigde bestanden met toevoegingen van 281 en 0 verwijderingen
  1. 281 0
      setup.py

+ 281 - 0
setup.py

@@ -1,11 +1,289 @@
 #!/usr/bin/env python
 
+import argparse
+import datetime
+from distutils.core import Command
+from distutils.command.build import build
+
 from setuptools import setup
 from setuptools import find_packages
 
+build.sub_commands.append(('build_manpage', None))
+
+
+class BuildManPage(Command):
+
+    """
+    Add a `build_manpage` command  to your setup.py.
+    To use this Command class import the class to your setup.py,
+    and add a command to call this class::
+
+        from build_manpage import BuildManPage
+
+        ...
+        ...
+
+        setup(
+        ...
+        ...
+        cmdclass={
+            'build_manpage': BuildManPage,
+        )
+
+    You can then use the following setup command to produce a man page::
+
+        $ python setup.py build_manpage --output=prog.1
+            --parser=yourmodule:argparser
+
+    Alternatively, set the variable AUTO_BUILD to True, and just invoke::
+
+        $ python setup.py build
+
+    If automatically want to build the man page every time you invoke your build,
+    add to your ```setup.cfg``` the following::
+
+        [build_manpage]
+        output = <appname>.1
+        parser = <path_to_your_parser>
+
+    # The BuildManPage code is distributed
+    # under the same License of Python
+    # Copyright (c) 2014 Oz Nahum Tiram  <nahumoz@gmail.com>
+    """
+    description = 'Generate man page from an ArgumentParser instance.'
+
+    user_options = [
+        ('output=', 'O', 'output file'),
+        ('parser=', None, 'module path to an ArgumentParser instance'
+         '(e.g. mymod:func, where func is a method or function which return'
+         'an arparse.ArgumentParser instance.')]
+
+    def initialize_options(self):
+        self.output = None
+        self.parser = None
+
+    def finalize_options(self):
+        if self.output is None:
+            raise DistutilsOptionError('\'output\' option is required')
+        if self.parser is None:
+            raise DistutilsOptionError('\'parser\' option is required')
+        mod_name, func_name = self.parser.split(':')
+        fromlist = mod_name.split('.')
+        try:
+            mod = __import__(mod_name, fromlist=fromlist)
+            self._parser = getattr(mod, func_name)(
+                formatter_class=ManPageFormatter)
+
+        except ImportError as err:
+            raise err
+
+        self.announce('Writing man page %s' % self.output)
+        self._today = datetime.date.today()
+
+    def run(self):
+
+        dist = self.distribution
+        homepage = dist.get_url()
+        appname = self._parser.prog
+
+        sections = {'authors': ("Blogit is written and maintained "
+                                "by Oz N Tiram <nahumoz@gmail.com>."),
+                    'distribution': ("The latest version of {} may be "
+                                     "downloaded from {}".format(appname,
+                                                                 homepage))
+                    }
+
+        dist = self.distribution
+        mpf = ManPageFormatter(appname,
+                               desc=dist.get_description(),
+                               long_desc=dist.get_long_description(),
+                               ext_sections=sections)
+
+        m = mpf.format_man_page(self._parser)
+
+        with open(self.output, 'w') as f:
+            f.write(m)
+
+
+class ManPageFormatter(argparse.HelpFormatter):
+
+    """
+    Formatter class to create man pages.
+    This class relies only on the parser, and not distutils.
+    The following shows a scenario for usage::
+
+        from pwman import parser_options
+        from build_manpage import ManPageFormatter
+
+        # example usage ...
+
+        dist = distribution
+        mpf = ManPageFormatter(appname,
+                               desc=dist.get_description(),
+                               long_desc=dist.get_long_description(),
+                               ext_sections=sections)
+
+        # parser is an ArgumentParser instance
+        m = mpf.format_man_page(parsr)
+
+        with open(self.output, 'w') as f:
+            f.write(m)
+
+    The last line would print all the options and help infomation wrapped with
+    man page macros where needed.
+    """
+
+    def __init__(self,
+                 prog,
+                 indent_increment=2,
+                 max_help_position=24,
+                 width=None,
+                 section=1,
+                 desc=None,
+                 long_desc=None,
+                 ext_sections=None,
+                 authors=None,
+                 ):
+
+        super(ManPageFormatter, self).__init__(prog)
+
+        self._prog = prog
+        self._section = 1
+        self._today = datetime.date.today().strftime('%Y\\-%m\\-%d')
+        self._desc = desc
+        self._long_desc = long_desc
+        self._ext_sections = ext_sections
+
+    def _get_formatter(self, **kwargs):
+        return self.formatter_class(prog=self.prog, **kwargs)
+
+    def _markup(self, txt):
+        return txt.replace('-', '\\-')
+
+    def _underline(self, string):
+        return "\\fI\\s-1" + string + "\\s0\\fR"
+
+    def _bold(self, string):
+        if not string.strip().startswith('\\fB'):
+            string = '\\fB' + string
+        if not string.strip().endswith('\\fR'):
+            string = string + '\\fR'
+        return string
+
+    def _mk_synopsis(self, parser):
+        self.add_usage(parser.usage, parser._actions,
+                       parser._mutually_exclusive_groups, prefix='')
+        usage = self._format_usage(None, parser._actions,
+                                   parser._mutually_exclusive_groups, '')
+
+        usage = usage.replace('%s ' % self._prog, '')
+        usage = '.SH SYNOPSIS\n \\fB%s\\fR %s\n' % (self._markup(self._prog),
+                                                    usage)
+        return usage
+
+    def _mk_title(self, prog):
+        return '.TH {0} {1} {2}\n'.format(prog, self._section,
+                                          self._today)
+
+    def _make_name(self, parser):
+        """
+        this method is in consitent with others ... it relies on
+        distribution
+        """
+        return '.SH NAME\n%s \\- %s\n' % (parser.prog,
+                                          parser.description)
+
+    def _mk_description(self):
+        if self._long_desc:
+            long_desc = self._long_desc.replace('\n', '\n.br\n')
+            return '.SH DESCRIPTION\n%s\n' % self._markup(long_desc)
+        else:
+            return ''
+
+    def _mk_footer(self, sections):
+        if not hasattr(sections, '__iter__'):
+            return ''
+
+        footer = []
+        for section, value in sections.items():
+            part = ".SH {}\n {}".format(section.upper(), value)
+            footer.append(part)
+
+        return '\n'.join(footer)
+
+    def format_man_page(self, parser):
+        page = []
+        page.append(self._mk_title(self._prog))
+        page.append(self._mk_synopsis(parser))
+        page.append(self._mk_description())
+        page.append(self._mk_options(parser))
+        page.append(self._mk_footer(self._ext_sections))
+
+        return ''.join(page)
+
+    def _mk_options(self, parser):
+
+        formatter = parser._get_formatter()
+
+        # positionals, optionals and user-defined groups
+        for action_group in parser._action_groups:
+            formatter.start_section(None)
+            formatter.add_text(None)
+            formatter.add_arguments(action_group._group_actions)
+            formatter.end_section()
+
+        # epilog
+        formatter.add_text(parser.epilog)
+
+        # determine help from format above
+        return '.SH OPTIONS\n' + formatter.format_help()
+
+    def _format_action_invocation(self, action):
+        if not action.option_strings:
+            metavar, = self._metavar_formatter(action, action.dest)(1)
+            return metavar
+
+        else:
+            parts = []
+
+            # if the Optional doesn't take a value, format is:
+            #    -s, --long
+            if action.nargs == 0:
+                parts.extend([self._bold(action_str) for action_str in
+                              action.option_strings])
+
+            # if the Optional takes a value, format is:
+            #    -s ARGS, --long ARGS
+            else:
+                default = self._underline(action.dest.upper())
+                args_string = self._format_args(action, default)
+                for option_string in action.option_strings:
+                    parts.append('%s %s' % (self._bold(option_string),
+                                            args_string))
+
+            return ', '.join(parts)
+
+
+class ManPageCreator(object):
+
+    """
+    This class takes a little different approach. Instead of relying on
+    information from ArgumentParser, it relies on information retrieved
+    from distutils.
+    This class makes it easy for package maintainer to create man pages in
+    cases, that there is no ArgumentParser.
+    """
+
+    def _mk_name(self, distribution):
+        """
+        """
+        return '.SH NAME\n%s \\- %s\n' % (distribution.get_name(),
+                                          distribution.get_description())
+
 setup(name='blogit',
       version='0.1',
       license="GNU GPL",
+      url='http://github.com/oz123/blogit',
       packages=find_packages(exclude=['tests']),
       install_requires=['Jinja2', 'markdown2', 'tinydb', 'pygments'],
       tests_require=['pytest', 'beautifulsoup4'],
@@ -13,6 +291,9 @@ setup(name='blogit',
               'console_scripts': ['blogit = blogit.blogit:main']
           },
 
+      cmdclass={
+          'build_manpage': BuildManPage
+      },
       classifiers=['Environment :: Console',
                    'Intended Audience :: End Users/Desktop',
                    'Intended Audience :: Developers',