Codebase list datalad / 1b8c14d
Merge pull request #503 from datalad/docs Rendering of manpages and examples in documentation Yaroslav Halchenko authored 7 years ago GitHub committed 7 years ago
13 changed file(s) with 645 addition(s) and 40 deletion(s). Raw diff Collapse all Expand all
4949
5050 install:
5151 # Install standalone build of git-annex for the recent enough version
52 - travis_retry sudo apt-get install git-annex-standalone zip help2man
52 - travis_retry sudo apt-get install git-annex-standalone zip
5353 # For Python3 compatibility needs never-released master
5454 - pip install https://github.com/niltonvolpato/python-progressbar/archive/master.zip
5555 - git config --global user.email "test@travis.land"
6969 - if [ ! -z "$DATALAD_TESTS_NONETWORK" ]; then sudo route add -net 0.0.0.0 netmask 0.0.0.0 dev lo; fi
7070 - DATALAD_LOGLEVEL=INFO $NOSE_WRAPPER `which nosetests` -s -v --with-doctest --with-cov --cover-package datalad --logging-level=INFO
7171 - if [ ! -z "$DATALAD_TESTS_NONETWORK" ]; then sudo route del -net 0.0.0.0 netmask 0.0.0.0 dev lo; fi
72 # Verify that we can render manpages
73 - make manpages
7472 # Generate documentation and run doctests
7573 - PYTHONPATH=$PWD/.. make -C docs html doctest
7674
3131 code-analysis:
3232 flake8 $(MODULE) | grep -v __init__ | grep -v external
3333 pylint -E -i y $(MODULE)/ # -d E1103,E0611,E1101
34
35 manpages: bin
36 mkdir -p build/man
37 # main manpage
38 DATALAD_HELP2MAN=1 PYTHONPATH=. help2man --no-discard-stderr \
39 --help-option="--help-np" -N -n "data management and sharing tool" \
40 "bin/datalad" > build/man/datalad.1 ; \
41 # figure out all relevant interface files, fuck yeah Python
42 for api in $$(PYTHONPATH=. python -c "from datalad.interface.base import get_interface_groups, get_cmdline_command_name; print(' '.join([' '.join([intf[0] + ':' + get_cmdline_command_name(intf) for intf in group[2]]) for group in get_interface_groups()]))"); do \
43 mod="$$(echo $${api} | cut -d ':' -f 1)" ; \
44 cmd="$$(echo $${api} | cut -d ':' -f 2)" ; \
45 summary="$$(grep -A 1 'class.*(.*Interface.*)' $$(python -c "import inspect; import $${mod} as mod; print(inspect.getsourcefile(mod))") | grep -v ':' | grep -v '^--' | sed -e 's/"//g' -e 's/^[ \t]*//;s/[ \t.]*$$//' | tr 'A-Z' 'a-z')" ; \
46 DATALAD_HELP2MAN=1 PYTHONPATH=. help2man --no-discard-stderr \
47 --help-option="--help-np" -N -n "$$summary" \
48 "bin/datalad $${cmd}" > build/man/datalad-$${cmd}.1 ; \
49 sed -i -e "4 s/^datalad /datalad $${cmd} /" build/man/datalad-$${cmd}.1 ; \
50 done
51
7070 helpstr)[0]
7171 # usage is on the same line
7272 helpstr = re.sub(r'^usage:', 'Usage:', helpstr)
73 if option_string == '--help-np':
74 usagestr = re.split(r'\n\n[A-Z]+', helpstr, maxsplit=1)[0]
75 usage_length = len(usagestr)
76 usagestr = re.subn(r'\s+', ' ', usagestr.replace('\n', ' '))[0]
77 helpstr = '%s\n%s' % (usagestr, helpstr[usage_length:])
78
79 if os.environ.get('DATALAD_HELP2MAN'):
80 # Convert 1-line command descriptions to remove leading -
81 helpstr = re.sub('\n\s*-\s*([-a-z0-9]*):\s*?([^\n]*)', r"\n'\1':\n \2\n", helpstr)
82 else:
83 # Those *s intended for man formatting do not contribute to readability in regular text mode
84 helpstr = helpstr.replace('*', '')
8573
8674 print(helpstr)
8775 sys.exit(0)
4949 """
5050
5151
52 def setup_parser():
52 def setup_parser(
53 formatter_class=argparse.RawDescriptionHelpFormatter,
54 return_subparsers=False):
5355 # Delay since can be a heavy import
5456 from ..interface.base import dedent_docstring, get_interface_groups, \
5557 get_cmdline_command_name, alter_interface_docs_for_cmdline
5658 # setup cmdline args parser
59 parts = {}
5760 # main parser
5861 parser = argparse.ArgumentParser(
5962 fromfile_prefix_chars='@',
6366 repositories as a backend. datalad command line tool allows to manipulate
6467 (obtain, create, update, publish, etc.) datasets and their collections."""),
6568 epilog='"Control Your Data"',
66 formatter_class=argparse.RawDescriptionHelpFormatter,
69 formatter_class=formatter_class,
6770 add_help=False)
6871 # common options
6972 helpers.parser_add_common_opt(parser, 'help')
120123 if hasattr(_intf, 'parser_args'):
121124 parser_args = _intf.parser_args
122125 else:
123 parser_args = dict(formatter_class=argparse.RawDescriptionHelpFormatter)
126 parser_args = dict(formatter_class=formatter_class)
124127 # use class description, if no explicit description is available
125128 parser_args['description'] = alter_interface_docs_for_cmdline(
126129 _intf.__doc__)
151154 sdescr = getattr(_intf, 'short_description',
152155 parser_args['description'].split('\n')[0])
153156 cmd_short_descriptions.append((cmd_name, sdescr))
157 parts[cmd_name] = subparser
154158 grp_short_descriptions.append(cmd_short_descriptions)
155159
156160 # create command summary
179183 available via command-specific --help, i.e.:
180184 datalad <command> --help"""),
181185 75, initial_indent='', subsequent_indent=''))
182 return parser
186 parts['datalad'] = parser
187 if return_subparsers:
188 return parts
189 else:
190 return parser
183191
184192
185193 # yoh: arn't used
0 # emacs: -*- mode: python-mode; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-
1 # ex: set sts=4 ts=4 sw=4 noet:
2 # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
3 #
4 # See COPYING file distributed along with the datalad package for the
5 # copyright and license terms.
6 #
7 # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
8 """"""
9
10 from six.moves import StringIO as SIO
11 import formatters as fmt
12 from datalad.cmdline.main import setup_parser
13 from .utils import assert_equal, ok_, assert_raises, assert_in, ok_startswith
14
15 demo_example = """
16 #!/bin/sh
17
18 set -e
19 set -u
20
21 # BOILERPLATE
22
23 HOME=IS_MY_CASTLE
24
25 #% EXAMPLE START
26
27 #% A simple start (on the command line)
28 #% ====================================
29
30 #% Lorem ipsum
31
32 datalad install http://the.world.com
33
34 #% Epilog -- with multiline rubish sdoifpwjefw
35 #% vsdokvpsokdvpsdkv spdokvpskdvpoksd
36 #% pfdsvja329u0fjpdsv sdpf9p93qk
37
38 datalad imagine --too \\
39 --much \\
40 --too say \\
41 yes=no
42
43 #% The result is not comprehensible.
44
45 #% EXAMPLE END
46
47 # define shunit test cases below, or just anything desired
48 """
49
50
51 def test_cmdline_example_to_rst():
52 # don't puke on nothing
53 out = fmt.cmdline_example_to_rst(SIO(''))
54 out.seek(0)
55 ok_startswith(out.read(), '.. AUTO-GENERATED')
56 out = fmt.cmdline_example_to_rst(SIO(''), ref='dummy')
57 out.seek(0)
58 assert_in('.. dummy:', out.read())
59 # full scale test
60 out = fmt.cmdline_example_to_rst(
61 SIO(demo_example), ref='mydemo')
62 out.seek(0)
63 assert_in('.. code-block:: sh', out.read())
64
65 def test_parser_access():
66 parsers = setup_parser(return_subparsers=True)
67 # we have a bunch
68 ok_(len(parsers) > 3)
69 assert_in('install', parsers.keys())
70
71
72 def test_manpage_formatter():
73 addonsections = {'mytest': "uniquedummystring"}
74
75 parsers = setup_parser(return_subparsers=True)
76 for p in parsers:
77 mp = fmt.ManPageFormatter(
78 p, ext_sections=addonsections).format_man_page(parsers[p])
79 for section in ('SYNOPSIS', 'DESCRIPTION', 'OPTIONS', 'MYTEST'):
80 assert_in('.SH {0}'.format(section), mp)
81 assert_in('uniquedummystring', mp)
82
83
84 def test_rstmanpage_formatter():
85 parsers = setup_parser(return_subparsers=True)
86 for p in parsers:
87 mp = fmt.RSTManPageFormatter(p).format_man_page(parsers[p])
88 for section in ('Synopsis', 'Description', 'Options'):
89 assert_in('\n{0}'.format(section), mp)
90 assert_in('{0}\n{1}'.format(p, '=' * len(p)), mp)
0 #!/bin/sh
1
2 set -e
3
4 # BOILERPLATE
5
6 BOBS_HOME=$(readlink -f $(mktemp -d datalad_demo.XXXX))
7 ALICES_HOME=$(readlink -f $(mktemp -d datalad_demo.XXXX))
8
9 #% EXAMPLE START
10
11 #% Build atop of 3rd-party data
12 #% ============================
13 #%
14 #% This example shows how datalad can be used to obtain a 3rd-party dataset and
15 #% use it as input for an analysis. Moreover, it demonstrates how two local
16 #% collaborators can contribute to this analysis, each using their own copy
17 #% of the dataset, but at the same time, being able to easily share their results
18 #% back and forth.
19
20 HOME=$BOBS_HOME
21
22 cd
23 pwd
24 git config --global --add user.name Bob
25 git config --global --add user.email bob@example.com
26
27 # will become: datalad create myanalysis --description "my phd in a day"
28 datalad install myanalysis
29
30
31 cd myanalysis
32
33 datalad install --source https://github.com/psychoinformatics-de/studyforrest-data-structural.git src/forrest_structural
34
35 mkdir code
36
37 # just test data, could be
38 #datalad install src/forrest_structural/sub-*/anat/sub-*_T1w.nii.gz
39 echo "datalad install src/forrest_structural/sub-01/anat/sub-01_T1w.nii.gz" > code/get_required_data.sh
40 echo "nib-ls src/forrest_structural/sub-*/anat/sub-*_T1w.nii.gz > result.txt" > code/run_analysis.sh
41
42 datalad install --recursive yes --add-data-to-git code
43
44 # will become: datalad make-memory-engram
45 git commit -m "Initial analysis setup"
46
47 bash code/get_required_data.sh
48 bash code/run_analysis.sh
49
50 datalad install result.txt
51
52 # will become: datalad make-memory-engram
53 git commit -m "First analysis results"
54
55
56 # 1. use case: lab colleague wants to work in the same analysis, on the same machine/cluster
57 HOME=$ALICES_HOME
58 cd
59 git config --global --add user.name Alice
60 git config --global --add user.email alice@example.com
61
62 #% we are the colleague now!
63 # TODO: needs to get --description to avoid confusion
64 datalad install --source $BOBS_HOME/myanalysis bobs_analysis
65
66 cd bobs_analysis
67 datalad install --recursive yes .
68 # pulls the data from the local source!
69 bash -x code/get_required_data.sh
70
71 datalad install result.txt
72 #cat result.txt
73
74 echo "file -L src/forrest_structural/sub-*/anat/sub-*_T1w.nii.gz > result.txt" > code/run_analysis.sh
75
76 bash code/run_analysis.sh ||true
77
78 git annex unlock
79 bash code/run_analysis.sh
80 git commit -a -m "Alice always helps"
81
82
83 HOME=$BOBS_HOME
84 cd ~/myanalysis
85 datalad add-sibling alice $ALICES_HOME/bobs_analysis
86
87 # datalad update failes:
88 #% datalad update alice
89 #2016-06-09 13:59:52,338 [INFO ] Updating handle '/tmp/datalad_demo.PU2F/myanalysis' ... (update.py:125)
90 #2016-06-09 13:59:52,391 [ERROR ] Failed to run ['git', '-c', 'receive.autogc=0', '-c', 'gc.auto=0', 'config', '--get', 'branch.master.remote'] under '/tmp/datalad_demo.PU2F/myanalysis'. Exit code=1. out= err= (cmd.py:295)
91 #
92 git pull alice master
93 git fetch alice
94 git annex merge
95
96 datalad install result.txt
97
98 #% EXAMPLE END
99
100 testEquality() {
101 assertEquals 1 1
102 }
103
104 [ -n "$DATALAD_RUN_CMDLINE_TESTS" ] && . shunit2 || true
0 .. -*- mode: rst -*-
1 .. vi: set ft=rst sts=4 ts=4 sw=4 et tw=79:
2
3 .. _chap_cmdline:
4
5 **********************
6 Command line reference
7 **********************
8
9 .. toctree::
10 :maxdepth: 1
11
12 generated/man/datalad
13 generated/man/datalad-install
1010 .. toctree::
1111 :maxdepth: 2
1212
13 cmdline
14 usecases/index
1315 crawler/index
14 usecases/index
1516 modref
1617
1718 >>> import datalad
44 .. toctree::
55 :maxdepth: 1
66
7 derived_data
7 ../generated/examples/3rdparty_analysis_workflow
0 # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
1 #
2 # See COPYING file distributed along with the DataLad package for the
3 # copyright and license terms.
4 #
5 # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
6
7 import argparse
8 import datetime
9
10
11 class ManPageFormatter(argparse.HelpFormatter):
12 # This code was originally distributed
13 # under the same License of Python
14 # Copyright (c) 2014 Oz Nahum Tiram <nahumoz@gmail.com>
15 def __init__(self,
16 prog,
17 indent_increment=2,
18 max_help_position=24,
19 width=None,
20 section=1,
21 ext_sections=None,
22 authors=None,
23 ):
24
25 super(ManPageFormatter, self).__init__(prog)
26
27 self._prog = prog
28 self._section = 1
29 self._today = datetime.date.today().strftime('%Y\\-%m\\-%d')
30 self._ext_sections = ext_sections
31
32 def _get_formatter(self, **kwargs):
33 return self.formatter_class(prog=self.prog, **kwargs)
34
35 def _markup(self, txt):
36 return txt.replace('-', '\\-')
37
38 def _underline(self, string):
39 return "\\fI\\s-1" + string + "\\s0\\fR"
40
41 def _bold(self, string):
42 if not string.strip().startswith('\\fB'):
43 string = '\\fB' + string
44 if not string.strip().endswith('\\fR'):
45 string = string + '\\fR'
46 return string
47
48 def _mk_synopsis(self, parser):
49 self.add_usage(parser.usage, parser._actions,
50 parser._mutually_exclusive_groups, prefix='')
51 usage = self._format_usage(None, parser._actions,
52 parser._mutually_exclusive_groups, '')
53
54 usage = usage.replace('%s ' % self._prog, '')
55 usage = '.SH SYNOPSIS\n \\fB%s\\fR %s\n' % (self._markup(self._prog),
56 usage)
57 return usage
58
59 def _mk_title(self, prog):
60 return '.TH {0} {1} {2}\n'.format(prog, self._section,
61 self._today)
62
63 def _make_name(self, parser):
64 """
65 this method is in consitent with others ... it relies on
66 distribution
67 """
68 return '.SH NAME\n%s \\- %s\n' % (parser.prog,
69 parser.description)
70
71 def _mk_description(self, parser):
72 desc = parser.description
73 if not desc:
74 return ''
75 desc = desc.replace('\n', '\n.br\n')
76 return '.SH DESCRIPTION\n%s\n' % self._markup(desc)
77
78 def _mk_footer(self, sections):
79 if not hasattr(sections, '__iter__'):
80 return ''
81
82 footer = []
83 for section, value in sections.items():
84 part = ".SH {}\n {}".format(section.upper(), value)
85 footer.append(part)
86
87 return '\n'.join(footer)
88
89 def format_man_page(self, parser):
90 page = []
91 page.append(self._mk_title(self._prog))
92 page.append(self._mk_synopsis(parser))
93 page.append(self._mk_description(parser))
94 page.append(self._mk_options(parser))
95 page.append(self._mk_footer(self._ext_sections))
96
97 return ''.join(page)
98
99 def _mk_options(self, parser):
100
101 formatter = parser._get_formatter()
102
103 # positionals, optionals and user-defined groups
104 for action_group in parser._action_groups:
105 formatter.start_section(None)
106 formatter.add_text(None)
107 formatter.add_arguments(action_group._group_actions)
108 formatter.end_section()
109
110 # epilog
111 formatter.add_text(parser.epilog)
112
113 # determine help from format above
114 return '.SH OPTIONS\n' + formatter.format_help()
115
116 def _format_action_invocation(self, action):
117 if not action.option_strings:
118 metavar, = self._metavar_formatter(action, action.dest)(1)
119 return metavar
120
121 else:
122 parts = []
123
124 # if the Optional doesn't take a value, format is:
125 # -s, --long
126 if action.nargs == 0:
127 parts.extend([self._bold(action_str) for action_str in
128 action.option_strings])
129
130 # if the Optional takes a value, format is:
131 # -s ARGS, --long ARGS
132 else:
133 default = self._underline(action.dest.upper())
134 args_string = self._format_args(action, default)
135 for option_string in action.option_strings:
136 parts.append('%s %s' % (self._bold(option_string),
137 args_string))
138
139 return ', '.join(parts)
140
141
142 class RSTManPageFormatter(ManPageFormatter):
143 def _get_formatter(self, **kwargs):
144 return self.formatter_class(prog=self.prog, **kwargs)
145
146 def _markup(self, txt):
147 # put general tune-ups here
148 return txt
149
150 def _underline(self, string):
151 return "*{0}*".format(string)
152
153 def _bold(self, string):
154 return "**{0}**".format(string)
155
156 def _mk_synopsis(self, parser):
157 self.add_usage(parser.usage, parser._actions,
158 parser._mutually_exclusive_groups, prefix='')
159 usage = self._format_usage(None, parser._actions,
160 parser._mutually_exclusive_groups, '')
161
162 usage = usage.replace('%s ' % self._prog, '')
163 usage = 'Synopsis\n--------\n::\n\n %s %s\n' \
164 % (self._markup(self._prog), usage)
165 return usage
166
167 def _mk_title(self, prog):
168 title = "{0}".format(prog)
169 title += '\n{0}\n\n'.format('=' * len(title))
170 return title
171
172 def _make_name(self, parser):
173 return ''
174
175 def _mk_description(self, parser):
176 desc = parser.description
177 if not desc:
178 return ''
179 return 'Description\n-----------\n%s\n' % self._markup(desc)
180
181 def _mk_footer(self, sections):
182 if not hasattr(sections, '__iter__'):
183 return ''
184
185 footer = []
186 for section, value in sections.items():
187 part = "\n{0}\n{1}\n{2}\n".format(
188 section,
189 '-' * len(section),
190 value)
191 footer.append(part)
192
193 return '\n'.join(footer)
194
195 def _mk_options(self, parser):
196
197 # this non-obvious maneuver is really necessary!
198 formatter = self.__class__(self._prog)
199
200 # positionals, optionals and user-defined groups
201 for action_group in parser._action_groups:
202 formatter.start_section(None)
203 formatter.add_text(None)
204 formatter.add_arguments(action_group._group_actions)
205 formatter.end_section()
206
207 # epilog
208 formatter.add_text(parser.epilog)
209
210 # determine help from format above
211 option_sec = formatter.format_help()
212
213 return '\n\nOptions\n-------\n{0}'.format(option_sec)
214
215 def _format_action(self, action):
216 # determine the required width and the entry label
217 action_header = self._format_action_invocation(action)
218
219 if action.help:
220 help_text = self._expand_help(action)
221 help_lines = self._split_lines(help_text, 80)
222 help = ' '.join(help_lines)
223 else:
224 help = ''
225
226 # return a single string
227 return '{0}\n{1}\n{2}\n\n'.format(
228 action_header,
229
230 '~' * len(action_header),
231 help)
232
233
234 def cmdline_example_to_rst(src, out=None, ref=None):
235 if out is None:
236 from six.moves import StringIO
237 out = StringIO()
238
239 # place header
240 out.write('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n')
241 if ref:
242 # place cross-ref target
243 out.write('.. {0}:\n\n'.format(ref))
244
245 # parser status vars
246 inexample = False
247 incodeblock = False
248
249 for line in src:
250 if line.startswith('#% EXAMPLE START'):
251 inexample = True
252 continue
253 if line.startswith('#% EXAMPLE END'):
254 break
255 if not inexample:
256 continue
257 if line.startswith('#%'):
258 incodeblock = False
259 out.write(line[3:])
260 continue
261 if incodeblock:
262 out.write(' %s' % line)
263 continue
264 # normal line
265 else:
266 if len(line.strip()):
267 incodeblock = True
268 out.write('\n.. code-block:: sh\n\n %s' % line)
269
270 return out
0 [build_manpage]
1 manpath = build/man
2 rstpath = docs/source/generated/man
3 parser = datalad.cmdline.main:setup_parser
4 [build_examples]
5 expath = docs/examples
6 rstpath = docs/source/generated/examples
1111 from os.path import sep as pathsep, join as opj, dirname
1212
1313 from setuptools import setup, find_packages
14
15 # manpage build imports
16 from distutils.command.build_py import build_py
17 from setup_support import BuildManPage, BuildRSTExamplesFromScripts
1418
1519 # This might entail lots of imports which might not yet be available
1620 # so let's do ad-hoc parsing of the version.py
6569 }
6670 requires['full'] = sum(list(requires.values()), [])
6771
72
73 # configure additional command for custom build steps
74 class DataladBuild(build_py):
75 def run(self):
76 self.run_command('build_manpage')
77 self.run_command('build_examples')
78 build_py.run(self)
79
80 cmdclass = {
81 'build_manpage': BuildManPage,
82 'build_examples': BuildRSTExamplesFromScripts,
83 'build_py': DataladBuild
84 }
85
6886 setup(
6987 name="datalad",
70 author="DataLad Team and Contributors",
88 author="The DataLad Team and Contributors",
7189 author_email="team@datalad.org",
7290 version=version,
7391 description="data distribution geared toward scientific datasets",
8199 'git-annex-remote-datalad=datalad.customremotes.datalad:main',
82100 ],
83101 },
102 cmdclass=cmdclass,
84103 package_data={
85104 'datalad': [
86105 'resources/git_ssh.sh',
0 # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
1 #
2 # See COPYING file distributed along with the DataLad package for the
3 # copyright and license terms.
4 #
5 # ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
6
7
8 import os
9 from os.path import join as opj
10
11 from distutils.core import Command
12 from distutils.errors import DistutilsOptionError
13 import datetime
14 import formatters as fmt
15
16
17 class BuildManPage(Command):
18 # The BuildManPage code was originally distributed
19 # under the same License of Python
20 # Copyright (c) 2014 Oz Nahum Tiram <nahumoz@gmail.com>
21
22 description = 'Generate man page from an ArgumentParser instance.'
23
24 user_options = [
25 ('manpath=', None, 'output path for manpages'),
26 ('rstpath=', None, 'output path for RST files'),
27 ('parser=', None, 'module path to an ArgumentParser instance'
28 '(e.g. mymod:func, where func is a method or function which return'
29 'a dict with one or more arparse.ArgumentParser instances.'),
30 ]
31
32 def initialize_options(self):
33 self.manpath = None
34 self.rstpath = None
35 self.parser = None
36
37 def finalize_options(self):
38 if self.manpath is None:
39 raise DistutilsOptionError('\'manpath\' option is required')
40 if self.rstpath is None:
41 raise DistutilsOptionError('\'rstpath\' option is required')
42 if self.parser is None:
43 raise DistutilsOptionError('\'parser\' option is required')
44 mod_name, func_name = self.parser.split(':')
45 fromlist = mod_name.split('.')
46 try:
47 mod = __import__(mod_name, fromlist=fromlist)
48 self._parser = getattr(mod, func_name)(
49 formatter_class=fmt.ManPageFormatter,
50 return_subparsers=True)
51
52 except ImportError as err:
53 raise err
54
55 self.announce('Writing man page(s) to %s' % self.manpath)
56 self._today = datetime.date.today()
57
58 def run(self):
59
60 dist = self.distribution
61 #homepage = dist.get_url()
62 #appname = self._parser.prog
63 appname = 'datalad'
64
65 sections = {
66 'Authors': """{0} is developed by {1} <{2}>.""".format(
67 appname, dist.get_author(), dist.get_author_email()),
68 }
69
70 dist = self.distribution
71 for cls, opath, ext in ((fmt.ManPageFormatter, self.manpath, '1'),
72 (fmt.RSTManPageFormatter, self.rstpath, 'rst')):
73 if not os.path.exists(opath):
74 os.makedirs(opath)
75 for cmdname in self._parser:
76 p = self._parser[cmdname]
77 cmdname = "{0}{1}".format(
78 'datalad-' if cmdname != 'datalad' else '',
79 cmdname)
80 format = cls(cmdname, ext_sections=sections)
81 formatted = format.format_man_page(p)
82 with open(opj(opath, '{0}.{1}'.format(
83 cmdname,
84 ext)),
85 'w') as f:
86 f.write(formatted)
87
88
89 class BuildRSTExamplesFromScripts(Command):
90 description = 'Generate RST variants of example shell scripts.'
91
92 user_options = [
93 ('expath=', None, 'path to look for example scripts'),
94 ('rstpath=', None, 'output path for RST files'),
95 ]
96
97 def initialize_options(self):
98 self.expath = None
99 self.rstpath = None
100
101 def finalize_options(self):
102 if self.expath is None:
103 raise DistutilsOptionError('\'expath\' option is required')
104 if self.rstpath is None:
105 raise DistutilsOptionError('\'rstpath\' option is required')
106 self.announce('Converting exanmple scripts')
107
108 def run(self):
109 opath = self.rstpath
110 if not os.path.exists(opath):
111 os.makedirs(opath)
112
113 from glob import glob
114 for example in glob(opj(self.expath, '*.sh')):
115 exname = os.path.basename(example)[:-3]
116 with open(opj(opath, '{0}.rst'.format(exname)), 'w') as out:
117 fmt.cmdline_example_to_rst(
118 open(example),
119 out=out,
120 ref='_example_{0}'.format(exname))