Something went wrong. Try again.
lol
Something went wrong. Try again.
15 kB · 398 lines
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399From 0fd815b7cae40478f7d34c6003be7525b2ca2687 Mon Sep 17 00:00:00 2001From: renesat <self@renesat.me>Date: Sat, 12 Jul 2025 02:31:35 +0200Subject: [PATCH] update datalad buildsupport scrypts
--- _datalad_buildsupport/formatters.py | 18 ++- _datalad_buildsupport/setup.py | 227 +++++++++++++++++++++++----- 2 files changed, 200 insertions(+), 45 deletions(-)
diff --git a/_datalad_buildsupport/formatters.py b/_datalad_buildsupport/formatters.pyindex 5ac01de..fb21875 100644--- a/_datalad_buildsupport/formatters.py+++ b/_datalad_buildsupport/formatters.py@@ -7,7 +7,10 @@ import argparse import datetime+import os import re+import time+from textwrap import wrap class ManPageFormatter(argparse.HelpFormatter):@@ -24,7 +27,7 @@ def __init__(self, authors=None, version=None ):-+ from datalad import cfg super(ManPageFormatter, self).__init__( prog, indent_increment=indent_increment,@@ -33,7 +36,10 @@ def __init__(self, self._prog = prog self._section = 1- self._today = datetime.date.today().strftime('%Y\\-%m\\-%d')+ self._today = datetime.datetime.fromtimestamp(+ cfg.obtain('datalad.source.epoch'),+ datetime.timezone.utc+ ).strftime('%Y\\-%m\\-%d') self._ext_sections = ext_sections self._version = version @@ -75,7 +81,7 @@ def _mk_title(self, prog): def _mk_name(self, prog, desc): """- this method is in consitent with others ... it relies on+ this method is in consistent with others ... it relies on distribution """ desc = desc.splitlines()[0] if desc else 'it is in the name'@@ -195,7 +201,9 @@ def _mk_synopsis(self, parser): parser._mutually_exclusive_groups, '') usage = usage.replace('%s ' % self._prog, '')- usage = 'Synopsis\n--------\n::\n\n %s %s\n' \+ usage = '\n'.join(wrap(+ usage, break_on_hyphens=False, subsequent_indent=6*' '))+ usage = 'Synopsis\n--------\n::\n\n %s %s\n\n' \ % (self._markup(self._prog), usage) return usage @@ -251,7 +259,7 @@ def _mk_options(self, parser): def _format_action(self, action): # determine the required width and the entry label- action_header = self._format_action_invocation(action)+ action_header = self._format_action_invocation(action, doubledash='-\\-') if action.help: help_text = self._expand_help(action)diff --git a/_datalad_buildsupport/setup.py b/_datalad_buildsupport/setup.pyindex 27e0821..e3ba793 100644--- a/_datalad_buildsupport/setup.py+++ b/_datalad_buildsupport/setup.py@@ -8,19 +8,51 @@ import datetime import os--from os.path import (- dirname,- join as opj,+import platform+import sys+from os import (+ linesep,+ makedirs, )-from setuptools import Command, DistutilsOptionError-from setuptools.config import read_configuration--import versioneer+from os.path import dirname+from os.path import join as opj+from os.path import sep as pathsep+from os.path import splitext++import setuptools+from genericpath import exists+from packaging.version import Version+from setuptools import (+ Command,+ find_namespace_packages,+ findall,+ setup,+)+from setuptools.errors import OptionError from . import formatters as fmt +def _path_rel2file(*p):+ # dirname instead of joining with pardir so it works if+ # datalad_build_support/ is just symlinked into some extension+ # while developing+ return opj(dirname(dirname(__file__)), *p)+++def get_version(name):+ """Determine version via importlib_metadata++ Parameters+ ----------+ name: str+ Name of the folder (package) where from to read version.py+ """+ # delay import so we do not require it for a simple setup stage+ from importlib.metadata import version as importlib_version+ return importlib_version(name)++ class BuildManPage(Command): # The BuildManPage code was originally distributed # under the same License of Python@@ -29,33 +61,27 @@ class BuildManPage(Command): 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)'),+ ('manpath=', None, 'output path for manpages'),+ ('rstpath=', None, 'output path for RST files'), ('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+ self.parser = 'datalad.cli.parser:setup_parser' def finalize_options(self): if self.manpath is None:- raise DistutilsOptionError('\'manpath\' option is required')+ raise OptionError('\'manpath\' option is required') if self.rstpath is None:- raise DistutilsOptionError('\'rstpath\' option is required')+ raise OptionError('\'rstpath\' option is required') if self.parser is None:- raise DistutilsOptionError('\'parser\' option is required')+ raise OptionError('\'parser\' option is required')+ self.manpath = _path_rel2file(self.manpath)+ self.rstpath = _path_rel2file(self.rstpath) mod_name, func_name = self.parser.split(':') fromlist = mod_name.split('.') try:@@ -64,18 +90,10 @@ def finalize_options(self): ['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')+ help_ignore_extensions=True) 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()@@ -125,12 +143,9 @@ def run(self): #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']),+ appname, dist.get_author(), dist.get_author_email()), } for cls, opath, ext in ((fmt.ManPageFormatter, self.manpath, '1'),@@ -138,8 +153,6 @@ def run(self): 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 '',@@ -147,7 +160,7 @@ def run(self): format = cls( cmdname, ext_sections=sections,- version=versioneer.get_version())+ version=get_version(getattr(self, 'mod_name', appname))) formatted = format.format_man_page(p) with open(opj(opath, '{0}.{1}'.format( cmdname.replace(' ', '-'),@@ -156,6 +169,42 @@ def run(self): f.write(formatted) +class BuildRSTExamplesFromScripts(Command):+ description = 'Generate RST variants of example shell scripts.'++ user_options = [+ ('expath=', None, 'path to look for example scripts'),+ ('rstpath=', None, 'output path for RST files'),+ ]++ def initialize_options(self):+ self.expath = opj('docs', 'examples')+ self.rstpath = opj('docs', 'source', 'generated', 'examples')++ def finalize_options(self):+ if self.expath is None:+ raise OptionError('\'expath\' option is required')+ if self.rstpath is None:+ raise OptionError('\'rstpath\' option is required')+ self.expath = _path_rel2file(self.expath)+ self.rstpath = _path_rel2file(self.rstpath)+ self.announce('Converting example scripts')++ def run(self):+ opath = self.rstpath+ if not os.path.exists(opath):+ os.makedirs(opath)++ from glob import glob+ for example in glob(opj(self.expath, '*.sh')):+ exname = os.path.basename(example)[:-3]+ with open(opj(opath, '{0}.rst'.format(exname)), 'w') as out:+ fmt.cmdline_example_to_rst(+ open(example),+ out=out,+ ref='_example_{0}'.format(exname))++ class BuildConfigInfo(Command): description = 'Generate RST documentation for all config items.' @@ -168,7 +217,8 @@ def initialize_options(self): def finalize_options(self): if self.rstpath is None:- raise DistutilsOptionError('\'rstpath\' option is required')+ raise OptionError('\'rstpath\' option is required')+ self.rstpath = _path_rel2file(self.rstpath) self.announce('Generating configuration documentation') def run(self):@@ -176,8 +226,8 @@ def run(self): if not os.path.exists(opath): os.makedirs(opath) - from datalad.interface.common_cfg import definitions as cfgdefs from datalad.dochelpers import _indent+ from datalad.interface.common_cfg import definitions as cfgdefs categories = { 'global': {},@@ -218,3 +268,100 @@ def run(self): desc_tmpl += 'undocumented\n' v.update(docs) rst.write(_indent(desc_tmpl.format(**v), ' '))+++def get_long_description_from_README():+ """Read README.md, convert to .rst using pypandoc++ If pypandoc is not available or fails - just output original .md.++ Returns+ -------+ dict+ with keys long_description and possibly long_description_content_type+ for newer setuptools which support uploading of markdown as is.+ """+ # PyPI used to not render markdown. Workaround for a sane appearance+ # https://github.com/pypa/pypi-legacy/issues/148#issuecomment-227757822+ # is still in place for older setuptools++ README = opj(_path_rel2file('README.md'))++ ret = {}+ if Version(setuptools.__version__) >= Version('38.6.0'):+ # check than this+ ret['long_description'] = open(README).read()+ ret['long_description_content_type'] = 'text/markdown'+ return ret++ # Convert or fall-back+ try:+ import pypandoc+ return {'long_description': pypandoc.convert(README, 'rst')}+ except (ImportError, OSError) as exc:+ # attempting to install pandoc via brew on OSX currently hangs and+ # pypandoc imports but throws OSError demanding pandoc+ print(+ "WARNING: pypandoc failed to import or thrown an error while "+ "converting"+ " README.md to RST: %r .md version will be used as is" % exc+ )+ return {'long_description': open(README).read()}+++def findsome(subdir, extensions):+ """Find files under subdir having specified extensions++ Leading directory (datalad) gets stripped+ """+ return [+ f.split(pathsep, 1)[1] for f in findall(opj('datalad', subdir))+ if splitext(f)[-1].lstrip('.') in extensions+ ]+++def datalad_setup(name, **kwargs):+ """A helper for a typical invocation of setuptools.setup.++ If not provided in kwargs, following fields will be autoset to the defaults+ or obtained from the present on the file system files:++ - author+ - author_email+ - packages -- all found packages which start with `name`+ - long_description -- converted to .rst using pypandoc README.md+ - version -- parsed `__version__` within `name/version.py`++ Parameters+ ----------+ name: str+ Name of the Python package+ **kwargs:+ The rest of the keyword arguments passed to setuptools.setup as is+ """+ # Simple defaults+ for k, v in {+ 'author': "The DataLad Team and Contributors",+ 'author_email': "team@datalad.org"+ }.items():+ if kwargs.get(k) is None:+ kwargs[k] = v++ # More complex, requiring some function call++ # Only recentish versions of find_packages support include+ # packages = find_packages('.', include=['datalad*'])+ # so we will filter manually for maximal compatibility+ if kwargs.get('packages') is None:+ # Use find_namespace_packages() in order to include folders that+ # contain data files but no Python code+ kwargs['packages'] = [pkg for pkg in find_namespace_packages('.') if pkg.startswith(name)]+ if kwargs.get('long_description') is None:+ kwargs.update(get_long_description_from_README())++ cmdclass = kwargs.get('cmdclass', {})+ # Check if command needs some module specific handling+ for v in cmdclass.values():+ if hasattr(v, 'handle_module'):+ getattr(v, 'handle_module')(name, **kwargs)+ return setup(name=name, **kwargs)