# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## # # 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 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), ' '))