-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathsetup.py
220 lines (187 loc) · 8.32 KB
/
setup.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
# 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), ' '))