Implement "stg refresh --edit" again
[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 link: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(save_template):
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 (save_template and 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 opts = [
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 if save_template:
159 opts.append(
160 opt('--save-template', action = 'callback', dest = 'save_template',
161 callback = templ_callback, metavar = 'FILE', type = 'string',
162 short = 'Save the message template to FILE and exit', long = """
163 Instead of running the command, just write the message
164 template to FILE, and exit. (If FILE is "-", write to
165 stdout.)
166
167 When driving StGit from another program, it is often
168 useful to first call a command with '--save-template',
169 then let the user edit the message, and then call the
170 same command with '--file'."""))
171 return opts
172
173 def diff_opts_option():
174 def diff_opts_callback(option, opt_str, value, parser):
175 if value:
176 parser.values.diff_flags.extend(value.split())
177 else:
178 parser.values.diff_flags = []
179 return [
180 opt('-O', '--diff-opts', dest = 'diff_flags',
181 default = (config.get('stgit.diff-opts') or '').split(),
182 action = 'callback', callback = diff_opts_callback,
183 type = 'string', metavar = 'OPTIONS',
184 short = 'Extra options to pass to "git diff"')]
185
186 def _person_opts(person, short):
187 """Sets options.<person> to a function that modifies a Person
188 according to the commandline options."""
189 def short_callback(option, opt_str, value, parser, field):
190 f = getattr(parser.values, person)
191 setattr(parser.values, person,
192 lambda p: getattr(f(p), 'set_' + field)(value))
193 def full_callback(option, opt_str, value, parser):
194 ne = utils.parse_name_email(value)
195 if not ne:
196 raise optparse.OptionValueError(
197 'Bad %s specification: %r' % (opt_str, value))
198 name, email = ne
199 short_callback(option, opt_str, name, parser, 'name')
200 short_callback(option, opt_str, email, parser, 'email')
201 return (
202 [opt('--%s' % person, metavar = '"NAME <EMAIL>"', type = 'string',
203 action = 'callback', callback = full_callback, dest = person,
204 default = lambda p: p, short = 'Set the %s details' % person)] +
205 [opt('--%s%s' % (short, f), metavar = f.upper(), type = 'string',
206 action = 'callback', callback = short_callback, dest = person,
207 callback_args = (f,), short = 'Set the %s %s' % (person, f))
208 for f in ['name', 'email', 'date']])
209
210 def author_options():
211 return _person_opts('author', 'auth')
212
213 def author_committer_options():
214 return _person_opts('author', 'auth') + _person_opts('committer', 'comm')