Exemple #1
0
def _beautify_multiline_field(content):
    content = dedent_docstring(content)
    lines = content.split('\n')
    title = ''
    if len(lines):
        title = lines[0]
    if len(lines) > 1:
        content = ''
        for l in lines[1:]:
            l = l.strip()
            content = '{}{}{}'.format(
                content, ' ' if len(content) and l != '.'
                and content[-1] != '\n' else '', l if l != '.' else '\n')
    return title, content
Exemple #2
0
def _beautify_multiline_field(content):
    content = dedent_docstring(content)
    lines = content.split('\n')
    title = ''
    if len(lines):
        title = lines[0]
    if len(lines) > 1:
        content = ''
        for l in lines[1:]:
            l = l.strip()
            content = '{}{}{}'.format(
                content,
                ' ' if len(content) and l != '.' and content[-1] != '\n' else '',
                l if l != '.' else '\n')
    return title, content
Exemple #3
0
def test_dedent():
    assert_false(dedent_docstring("one liner").endswith("\n"))
Exemple #4
0
def test_dedent():
    assert_false(dedent_docstring("one liner").endswith("\n"))
Exemple #5
0
def alter_interface_docs_for_cmdline(docs):
    """Apply modifications to interface docstrings for cmdline doc use."""
    # central place to alter the impression of docstrings,
    # like removing Python API specific sections, and argument markup
    if not docs:
        return docs
    import re
    import textwrap

    docs = dedent_docstring(docs)
    # clean cmdline sections
    docs = re.sub(r'\|\| PYTHON \>\>.*?\<\< PYTHON \|\|',
                  '',
                  docs,
                  flags=re.MULTILINE | re.DOTALL)
    # clean cmdline in-line bits
    docs = re.sub(r'\[PY:\s[^\[\]]*\sPY\]',
                  '',
                  docs,
                  flags=re.MULTILINE | re.DOTALL)
    docs = re.sub(r'\[CMD:\s([^\[\]]*)\sCMD\]',
                  lambda match: match.group(1),
                  docs,
                  flags=re.MULTILINE)
    docs = re.sub(r'\|\| CMDLINE \>\>(.*?)\<\< CMDLINE \|\|',
                  lambda match: match.group(1),
                  docs,
                  flags=re.MULTILINE | re.DOTALL)
    # remove :role:`...` RST markup for cmdline docs
    docs = re.sub(
        r':\S+:`[^`]*`[\\]*',
        lambda match: ':'.join(match.group(0).split(':')[2:]).strip('`\\'),
        docs,
        flags=re.MULTILINE | re.DOTALL)
    # make the handbook doc references more accessible
    # the URL is a redirect configured at readthedocs
    docs = re.sub(r'(handbook:[0-9]-[0-9]*)',
                  '\\1 (http://handbook.datalad.org/symbols)', docs)
    # remove None constraint. In general, `None` on the cmdline means don't
    # give option at all, but specifying `None` explicitly is practically
    # impossible
    docs = re.sub(r',\sor\svalue\smust\sbe\s`None`',
                  '',
                  docs,
                  flags=re.MULTILINE | re.DOTALL)
    # capitalize variables and remove backticks to uniformize with
    # argparse output
    docs = re.sub(
        r'([^`]+)`([a-zA-Z0-9_]+)`([^`]+)', lambda match:
        f'{match.group(1)}{match.group(2).upper()}{match.group(3)}', docs)
    # select only the cmdline alternative from argument specifications
    docs = re.sub(r'``([a-zA-Z0-9_,.]+)\|\|([a-zA-Z0-9-,.]+)``',
                  lambda match: f'``{match.group(2)}``', docs)
    # clean up sphinx API refs
    docs = re.sub(r'\~datalad\.api\.\S*',
                  lambda match: "`{0}`".format(match.group(0)[13:]), docs)
    # dedicated support for version markup
    docs = docs.replace('.. versionadded::', 'New in version')
    docs = docs.replace('.. versionchanged::', 'Changed in version')
    docs = docs.replace('.. deprecated::', 'Deprecated in version')
    # Remove RST paragraph markup
    docs = re.sub(r'^.. \S+::',
                  lambda match: match.group(0)[3:-2].upper(),
                  docs,
                  flags=re.MULTILINE)
    docs = re.sub(r'^([ ]*)\|\| REFLOW \>\>\n(.*?)\<\< REFLOW \|\|',
                  lambda match: textwrap.fill(
                      match.group(2), subsequent_indent=match.group(1)),
                  docs,
                  flags=re.MULTILINE | re.DOTALL)
    return docs
Exemple #6
0
    def __call__(plugin=None,
                 dataset=None,
                 showpluginhelp=False,
                 showplugininfo=False,
                 **kwargs):
        plugins = _get_plugins()
        if not plugin:
            max_name_len = max(len(k) for k in plugins.keys())
            for plname, plinfo in sorted(plugins.items(), key=lambda x: x[0]):
                spacer = ' ' * (max_name_len - len(plname))
                synopsis = None
                try:
                    with open(plinfo['file']) as plf:
                        for line in plf:
                            if line.startswith('"""'):
                                synopsis = line.strip().strip('"').strip()
                                break
                except Exception as e:
                    ui.message('{}{} [BROKEN] {}'.format(
                        plname, spacer, exc_str(e)))
                    continue
                if synopsis:
                    msg = '{}{} - {}'.format(plname, spacer, synopsis)
                else:
                    msg = '{}{} [no synopsis]'.format(plname, spacer)
                if showplugininfo:
                    msg = '{} ({})'.format(msg, plinfo['file'])
                ui.message(msg)
            return
        args = None
        if isinstance(plugin, (list, tuple)):
            args = plugin[1:]
            plugin = plugin[0]
        if plugin not in plugins:
            raise ValueError("unknown plugin '{}', available: {}".format(
                plugin, ','.join(plugins.keys())))
        user_supplied_args = set()
        if args:
            # we got some arguments in the plugin spec, parse them and add to
            # kwargs
            for arg in args:
                if isinstance(arg, tuple):
                    # came from python item-style
                    argname, argval = arg
                else:
                    parsed = argspec.match(arg)
                    if parsed is None:
                        raise ValueError(
                            "invalid plugin argument: '{}'".format(arg))
                    argname, argval = parsed.groups()
                if argname in kwargs:
                    # argument was seen at least once before -> make list
                    existing_val = kwargs[argname]
                    if not isinstance(existing_val, list):
                        existing_val = [existing_val]
                    existing_val.append(argval)
                    argval = existing_val
                kwargs[argname] = argval
                user_supplied_args.add(argname)
        plugin_call = _load_plugin(plugins[plugin]['file'])

        if showpluginhelp:
            # we don't need special docs for the cmdline, standard python ones
            # should be comprehensible enough
            ui.message(
                dedent_docstring(plugin_call.__doc__) if plugin_call.
                __doc__ else 'This plugin has no documentation')
            return

        #
        # argument preprocessing
        #
        # check the plugin signature and filter out all unsupported args
        plugin_args, _, _, arg_defaults = inspect.getargspec(plugin_call)
        supported_args = {k: v for k, v in kwargs.items() if k in plugin_args}
        excluded_args = user_supplied_args.difference(supported_args.keys())
        if excluded_args:
            lgr.warning(
                'ignoring plugin argument(s) %s, not supported by plugin',
                excluded_args)
        # always overwrite the dataset arg if one is needed
        if 'dataset' in plugin_args:
            supported_args['dataset'] = require_dataset(
                # use dedicated arg if given, also anything the came with the plugin args
                # or curdir as the last resort
                dataset if dataset else kwargs.get('dataset', curdir),
                # note 'dataset' arg is always first, if we have defaults for all args
                # we have a default for 'dataset' to -> it is optional
                check_installed=len(arg_defaults) != len(plugin_args),
                purpose='handover to plugin')

        # call as a generator
        for res in plugin_call(**supported_args):
            if not res:
                continue
            if dataset:
                # enforce standard regardless of what plugin did
                res['refds'] = getattr(dataset, 'path', dataset)
            elif 'refds' in res:
                # no base dataset, results must not have them either
                del res['refds']
            if 'logger' not in res:
                # make sure we have a logger
                res['logger'] = lgr
            yield res