chiark / gitweb /
4cd9527ac08f0aff69f0ec603f0fcc76cf520863
[stgit] / stgit / argparse.py
1 """This module provides a layer on top of the standard library's
2 C{optparse} module, so that we can easily generate both interactive
3 help and asciidoc documentation (such as man pages)."""
4
5 import optparse, sys, textwrap
6 from stgit import utils
7 from stgit.config import config
8
9 def _splitlist(lst, split_on):
10     """Iterate over the sublists of lst that are separated by an element e
11     such that split_on(e) is true."""
12     current = []
13     for e in lst:
14         if split_on(e):
15             yield current
16             current = []
17         else:
18             current.append(e)
19     yield current
20
21 def _paragraphs(s):
22     """Split a string s into a list of paragraphs, each of which is a list
23     of lines."""
24     lines = [line.rstrip() for line in textwrap.dedent(s).strip().splitlines()]
25     return [p for p in _splitlist(lines, lambda line: not line.strip()) if p]
26
27 class opt(object):
28     """Represents a command-line flag."""
29     def __init__(self, *args, **kwargs):
30         self.args = args
31         self.kwargs = kwargs
32     def get_option(self):
33         kwargs = dict(self.kwargs)
34         kwargs['help'] = kwargs['short']
35         del kwargs['short']
36         if 'long' in kwargs:
37             del kwargs['long']
38         return optparse.make_option(*self.args, **kwargs)
39     def metavar(self):
40         o = self.get_option()
41         if not o.nargs:
42             return None
43         if o.metavar:
44             return o.metavar
45         for flag in self.args:
46             if flag.startswith('--'):
47                 return utils.strip_prefix('--', flag).upper()
48         raise Exception('Cannot determine metavar')
49     def write_asciidoc(self, f):
50         for flag in self.args:
51             f.write(flag)
52             m = self.metavar()
53             if m:
54                 f.write(' ' + m)
55             f.write('::\n')
56         paras = _paragraphs(self.kwargs.get('long', self.kwargs['short'] + '.'))
57         for line in paras[0]:
58             f.write(' '*8 + line + '\n')
59         for para in paras[1:]:
60             f.write('+\n')
61             for line in para:
62                 f.write(line + '\n')
63
64 def _cmd_name(cmd_mod):
65     return getattr(cmd_mod, 'name', cmd_mod.__name__.split('.')[-1])
66
67 def make_option_parser(cmd):
68     pad = ' '*len('Usage: ')
69     return optparse.OptionParser(
70         prog = 'stg %s' % _cmd_name(cmd),
71         usage = (('\n' + pad).join('%%prog %s' % u for u in cmd.usage) +
72                  '\n\n' + cmd.help),
73         option_list = [o.get_option() for o in cmd.options])
74
75 def _write_underlined(s, u, f):
76     f.write(s + '\n')
77     f.write(u*len(s) + '\n')
78
79 def write_asciidoc(cmd, f):
80     _write_underlined('stg-%s(1)' % _cmd_name(cmd), '=', f)
81     f.write('\n')
82     _write_underlined('NAME', '-', f)
83     f.write('stg-%s - %s\n\n' % (_cmd_name(cmd), cmd.help))
84     _write_underlined('SYNOPSIS', '-', f)
85     f.write('[verse]\n')
86     for u in cmd.usage:
87         f.write("'stg' %s %s\n" % (_cmd_name(cmd), u))
88     f.write('\n')
89     _write_underlined('DESCRIPTION', '-', f)
90     f.write('\n%s\n\n' % cmd.description.strip('\n'))
91     if cmd.options:
92         _write_underlined('OPTIONS', '-', f)
93         for o in cmd.options:
94             o.write_asciidoc(f)
95             f.write('\n')
96     _write_underlined('StGit', '-', f)
97     f.write('Part of the StGit suite - see stglink:stg[1]\n')
98
99 def sign_options():
100     def callback(option, opt_str, value, parser, sign_str):
101         if parser.values.sign_str not in [None, sign_str]:
102             raise optparse.OptionValueError(
103                 '--ack and --sign were both specified')
104         parser.values.sign_str = sign_str
105     return [
106         opt('--sign', action = 'callback', dest = 'sign_str',
107             callback = callback, callback_args = ('Signed-off-by',),
108             short = 'Add "Signed-off-by:" line', long = """
109             Add a "Signed-off-by:" to the end of the patch."""),
110         opt('--ack', action = 'callback', dest = 'sign_str',
111             callback = callback, callback_args = ('Acked-by',),
112             short = 'Add "Acked-by:" line', long = """
113             Add an "Acked-by:" line to the end of the patch.""")]
114
115 def message_options():
116     def no_dup(parser):
117         if parser.values.message != None:
118             raise optparse.OptionValueError(
119                 'Cannot give more than one --message or --file')
120     def no_combine(parser):
121         if (parser.values.message != None
122             and parser.values.save_template != None):
123             raise optparse.OptionValueError(
124                 'Cannot give both --message/--file and --save-template')
125     def msg_callback(option, opt_str, value, parser):
126         no_dup(parser)
127         parser.values.message = value
128         no_combine(parser)
129     def file_callback(option, opt_str, value, parser):
130         no_dup(parser)
131         if value == '-':
132             parser.values.message = sys.stdin.read()
133         else:
134             f = file(value)
135             parser.values.message = f.read()
136             f.close()
137         no_combine(parser)
138     def templ_callback(option, opt_str, value, parser):
139         if value == '-':
140             def w(s):
141                 sys.stdout.write(s)
142         else:
143             def w(s):
144                 f = file(value, 'w+')
145                 f.write(s)
146                 f.close()
147         parser.values.save_template = w
148         no_combine(parser)
149     return [
150         opt('-m', '--message', action = 'callback',
151             callback = msg_callback, dest = 'message', type = 'string',
152             short = 'Use MESSAGE instead of invoking the editor'),
153         opt('-f', '--file', action = 'callback', callback = file_callback,
154             dest = 'message', type = 'string',
155             short = 'Use FILE instead of invoking the editor', long = """
156             Use the contents of FILE instead of invoking the editor.
157             (If FILE is "-", write to stdout.)"""),
158         opt('--save-template', action = 'callback', dest = 'save_template',
159             callback = templ_callback, metavar = 'FILE', type = 'string',
160             short = 'Save the message template to FILE and exit', long = """
161             Instead of running the command, just write the message
162             template to FILE, and exit. (If FILE is "-", write to
163             stdout.)
164
165             When driving StGit from another program, it is often
166             useful to first call a command with '--save-template',
167             then let the user edit the message, and then call the same
168             command with '--file'.""")]
169
170 def diff_opts_option():
171     def diff_opts_callback(option, opt_str, value, parser):
172         if value:
173             parser.values.diff_flags.extend(value.split())
174         else:
175             parser.values.diff_flags = []
176     return [
177         opt('-O', '--diff-opts', dest = 'diff_flags',
178             default = (config.get('stgit.diff-opts') or '').split(),
179             action = 'callback', callback = diff_opts_callback,
180             type = 'string', metavar = 'OPTIONS',
181             short = 'Extra options to pass to "git diff"')]
182
183 def _person_opts(person, short):
184     """Sets options.<person> to a function that modifies a Person
185     according to the commandline options."""
186     def short_callback(option, opt_str, value, parser, field):
187         f = getattr(parser.values, person)
188         setattr(parser.values, person,
189                 lambda p: getattr(f(p), 'set_' + field)(value))
190     def full_callback(option, opt_str, value, parser):
191         ne = utils.parse_name_email(value)
192         if not ne:
193             raise optparse.OptionValueError(
194                 'Bad %s specification: %r' % (opt_str, value))
195         name, email = ne
196         short_callback(option, opt_str, name, parser, 'name')
197         short_callback(option, opt_str, email, parser, 'email')
198     return (
199         [opt('--%s' % person, metavar = '"NAME <EMAIL>"', type = 'string',
200              action = 'callback', callback = full_callback, dest = person,
201              default = lambda p: p, short = 'Set the %s details' % person)] +
202         [opt('--%s%s' % (short, f), metavar = f.upper(), type = 'string',
203              action = 'callback', callback = short_callback, dest = person,
204              callback_args = (f,), short = 'Set the %s %s' % (person, f))
205          for f in ['name', 'email', 'date']])
206
207 def author_committer_options():
208     return _person_opts('author', 'auth') + _person_opts('committer', 'comm')