123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
- #
- # See COPYING file distributed along with the DataLad package for the
- # copyright and license terms.
- #
- # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
- import datetime
- import os
- from os.path import (
- dirname,
- join as opj,
- )
- from setuptools import Command, DistutilsOptionError
- from setuptools.config import read_configuration
- import versioneer
- from . import formatters as fmt
- class BuildManPage(Command):
- # The BuildManPage code was originally 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 = [
- ('manpath=', None,
- 'output path for manpages (relative paths are relative to the '
- 'datalad package)'),
- ('rstpath=', None,
- 'output path for RST files (relative paths are relative to the '
- 'datalad package)'),
- ('parser=', None, 'module path to an ArgumentParser instance'
- '(e.g. mymod:func, where func is a method or function which return'
- 'a dict with one or more arparse.ArgumentParser instances.'),
- ('cmdsuite=', None, 'module path to an extension command suite '
- '(e.g. mymod:command_suite) to limit the build to the contained '
- 'commands.'),
- ]
- def initialize_options(self):
- self.manpath = opj('build', 'man')
- self.rstpath = opj('docs', 'source', 'generated', 'man')
- self.parser = 'datalad.cmdline.main:setup_parser'
- self.cmdsuite = None
- def finalize_options(self):
- if self.manpath is None:
- raise DistutilsOptionError('\'manpath\' option is required')
- if self.rstpath is None:
- raise DistutilsOptionError('\'rstpath\' 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)(
- ['datalad'],
- formatter_class=fmt.ManPageFormatter,
- return_subparsers=True,
- # ignore extensions only for the main package to avoid pollution
- # with all extension commands that happen to be installed
- help_ignore_extensions=self.distribution.get_name() == 'datalad')
- except ImportError as err:
- raise err
- if self.cmdsuite:
- mod_name, suite_name = self.cmdsuite.split(':')
- mod = __import__(mod_name, fromlist=mod_name.split('.'))
- suite = getattr(mod, suite_name)
- self.cmdlist = [c[2] if len(c) > 2 else c[1].replace('_', '-').lower()
- for c in suite[1]]
- self.announce('Writing man page(s) to %s' % self.manpath)
- self._today = datetime.date.today()
- @classmethod
- def handle_module(cls, mod_name, **kwargs):
- """Module specific handling.
- This particular one does
- 1. Memorize (at class level) the module name of interest here
- 2. Check if 'datalad.extensions' are specified for the module,
- and then analyzes them to obtain command names it provides
- If cmdline commands are found, its entries are to be used instead of
- the ones in datalad's _parser.
- Parameters
- ----------
- **kwargs:
- all the kwargs which might be provided to setuptools.setup
- """
- cls.mod_name = mod_name
- exts = kwargs.get('entry_points', {}).get('datalad.extensions', [])
- for ext in exts:
- assert '=' in ext # should be label=module:obj
- ext_label, mod_obj = ext.split('=', 1)
- assert ':' in mod_obj # should be module:obj
- mod, obj = mod_obj.split(':', 1)
- assert mod_name == mod # AFAIK should be identical
- mod = __import__(mod_name)
- if hasattr(mod, obj):
- command_suite = getattr(mod, obj)
- assert len(command_suite) == 2 # as far as I see it
- if not hasattr(cls, 'cmdline_names'):
- cls.cmdline_names = []
- cls.cmdline_names += [
- cmd
- for _, _, cmd, _ in command_suite[1]
- ]
- def run(self):
- dist = self.distribution
- #homepage = dist.get_url()
- #appname = self._parser.prog
- appname = 'datalad'
- cfg = read_configuration(
- opj(dirname(dirname(__file__)), 'setup.cfg'))['metadata']
- sections = {
- 'Authors': """{0} is developed by {1} <{2}>.""".format(
- appname, cfg['author'], cfg['author_email']),
- }
- for cls, opath, ext in ((fmt.ManPageFormatter, self.manpath, '1'),
- (fmt.RSTManPageFormatter, self.rstpath, 'rst')):
- if not os.path.exists(opath):
- os.makedirs(opath)
- for cmdname in getattr(self, 'cmdline_names', list(self._parser)):
- if hasattr(self, 'cmdlist') and cmdname not in self.cmdlist:
- continue
- p = self._parser[cmdname]
- cmdname = "{0}{1}".format(
- 'datalad ' if cmdname != 'datalad' else '',
- cmdname)
- format = cls(
- cmdname,
- ext_sections=sections,
- version=versioneer.get_version())
- formatted = format.format_man_page(p)
- with open(opj(opath, '{0}.{1}'.format(
- cmdname.replace(' ', '-'),
- ext)),
- 'w') as f:
- f.write(formatted)
- class BuildConfigInfo(Command):
- description = 'Generate RST documentation for all config items.'
- user_options = [
- ('rstpath=', None, 'output path for RST file'),
- ]
- def initialize_options(self):
- self.rstpath = opj('docs', 'source', 'generated', 'cfginfo')
- def finalize_options(self):
- if self.rstpath is None:
- raise DistutilsOptionError('\'rstpath\' option is required')
- self.announce('Generating configuration documentation')
- def run(self):
- opath = self.rstpath
- if not os.path.exists(opath):
- os.makedirs(opath)
- from datalad.interface.common_cfg import definitions as cfgdefs
- from datalad.dochelpers import _indent
- categories = {
- 'global': {},
- 'local': {},
- 'dataset': {},
- 'misc': {}
- }
- for term, v in cfgdefs.items():
- categories[v.get('destination', 'misc')][term] = v
- for cat in categories:
- with open(opj(opath, '{}.rst.in'.format(cat)), 'w') as rst:
- rst.write('.. glossary::\n')
- for term, v in sorted(categories[cat].items(), key=lambda x: x[0]):
- rst.write(_indent(term, '\n '))
- qtype, docs = v.get('ui', (None, {}))
- desc_tmpl = '\n'
- if 'title' in docs:
- desc_tmpl += '{title}:\n'
- if 'text' in docs:
- desc_tmpl += '{text}\n'
- if 'default' in v:
- default = v['default']
- if hasattr(default, 'replace'):
- # protect against leaking specific home dirs
- v['default'] = default.replace(os.path.expanduser('~'), '~')
- desc_tmpl += 'Default: {default}\n'
- if 'type' in v:
- type_ = v['type']
- if hasattr(type_, 'long_description'):
- type_ = type_.long_description()
- else:
- type_ = type_.__name__
- desc_tmpl += '\n[{type}]\n'
- v['type'] = type_
- if desc_tmpl == '\n':
- # we need something to avoid joining terms
- desc_tmpl += 'undocumented\n'
- v.update(docs)
- rst.write(_indent(desc_tmpl.format(**v), ' '))
|