Files
nixpkgs/pkgs/development/python-modules/datalad-gooey/setuptools.patch
2025-09-13 10:12:22 +02:00

399 lines
14 KiB
Diff

From 0fd815b7cae40478f7d34c6003be7525b2ca2687 Mon Sep 17 00:00:00 2001
From: renesat <self@renesat.me>
Date: Sat, 12 Jul 2025 02:31:35 +0200
Subject: [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.py
index 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.py
index 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)