Use README.md in setup.py long_description
[stgit.git] / stgit / argparse.py
blob3a3169f1bcf6edf9d44893fd1928d73769a54674
1 """Command line argument parsing for StGit subcommands.
3 This module provides a layer on top of the standard library's :mod:`optparse` to
4 facilitate generation of both interactive help and asciidoc documentation (such as man
5 pages).
7 """
9 import io
10 import optparse
11 import sys
12 import textwrap
14 from stgit import utils
15 from stgit.config import config
16 from stgit.lib.git import Date
17 from stgit.out import out
20 def _splitlist(lst, split_on):
21 """Split list using provided predicate."""
22 current = []
23 for e in lst:
24 if split_on(e):
25 yield current
26 current = []
27 else:
28 current.append(e)
29 yield current
32 def _paragraphs(s):
33 """Split a string s into a list of paragraphs, each of which is a list of lines."""
34 lines = [line.rstrip() for line in textwrap.dedent(s).strip().splitlines()]
35 return [p for p in _splitlist(lines, lambda line: not line.strip()) if p]
38 class opt:
39 """Represents a command-line flag."""
41 def __init__(self, *pargs, **kwargs):
42 self.pargs = pargs
43 self.kwargs = kwargs
45 def get_option(self):
46 kwargs = dict(self.kwargs)
47 kwargs['help'] = kwargs['short']
48 for k in ['short', 'long', 'args']:
49 kwargs.pop(k, None)
50 return optparse.make_option(*self.pargs, **kwargs)
52 def metavar(self):
53 o = self.get_option()
54 if not o.takes_value():
55 return None
56 if o.metavar:
57 return o.metavar
58 for flag in self.pargs:
59 if flag.startswith('--'):
60 return utils.strip_prefix('--', flag).upper()
61 raise Exception('Cannot determine metavar')
63 def write_asciidoc(self, f):
64 for flag in self.pargs:
65 f.write(flag)
66 m = self.metavar()
67 if m:
68 f.write(' ' + m)
69 f.write('::\n')
70 paras = _paragraphs(self.kwargs.get('long', self.kwargs['short'] + '.'))
71 for line in paras[0]:
72 f.write(' ' * 8 + line + '\n')
73 for para in paras[1:]:
74 f.write('+\n')
75 for line in para:
76 f.write(line + '\n')
78 @property
79 def flags(self):
80 return self.pargs
82 @property
83 def args(self):
84 if self.kwargs.get('action', None) in ['store_true', 'store_false']:
85 default = []
86 else:
87 default = ['files']
88 return self.kwargs.get('args', default)
91 def _cmd_name(cmd_mod):
92 return getattr(cmd_mod, 'name', cmd_mod.__name__.split('.')[-1])
95 def make_option_parser(cmd):
96 pad = ' ' * len('Usage: ')
97 return optparse.OptionParser(
98 prog='stg %s' % _cmd_name(cmd),
99 usage=(
100 ('\n' + pad).join('%%prog %s' % u for u in cmd.usage) + '\n\n' + cmd.help
102 option_list=[o.get_option() for o in cmd.options],
106 def _write_underlined(s, u, f):
107 f.write(s + '\n')
108 f.write(u * len(s) + '\n')
111 def write_asciidoc(cmd, f):
112 _write_underlined('stg-%s(1)' % _cmd_name(cmd), '=', f)
113 f.write('\n')
114 _write_underlined('NAME', '-', f)
115 f.write('stg-%s - %s\n\n' % (_cmd_name(cmd), cmd.help))
116 _write_underlined('SYNOPSIS', '-', f)
117 f.write('[verse]\n')
118 for u in cmd.usage:
119 f.write("'stg %s' %s\n" % (_cmd_name(cmd), u))
120 f.write('\n')
121 _write_underlined('DESCRIPTION', '-', f)
122 f.write('\n%s\n\n' % cmd.description.strip('\n'))
123 if cmd.options:
124 _write_underlined('OPTIONS', '-', f)
125 for o in cmd.options:
126 o.write_asciidoc(f)
127 f.write('\n')
128 _write_underlined('StGit', '-', f)
129 f.write('Part of the StGit suite - see linkman:stg[1]\n')
132 def sign_options():
133 def callback(option, opt_str, value, parser, sign_str):
134 if parser.values.sign_str not in [None, sign_str]:
135 raise optparse.OptionValueError(
136 'Cannot give more than one of --ack, --sign, --review'
138 parser.values.sign_str = sign_str
140 return [
141 opt(
142 '--sign',
143 action='callback',
144 dest='sign_str',
145 args=[],
146 callback=callback,
147 callback_args=('Signed-off-by',),
148 short='Add "Signed-off-by:" line',
149 long='Add a "Signed-off-by:" to the end of the patch.',
151 opt(
152 '--ack',
153 action='callback',
154 dest='sign_str',
155 args=[],
156 callback=callback,
157 callback_args=('Acked-by',),
158 short='Add "Acked-by:" line',
159 long='Add an "Acked-by:" line to the end of the patch.',
161 opt(
162 '--review',
163 action='callback',
164 dest='sign_str',
165 args=[],
166 callback=callback,
167 callback_args=('Reviewed-by',),
168 short='Add "Reviewed-by:" line',
169 long='Add a "Reviewed-by:" line to the end of the patch.',
174 def hook_options():
175 return [
176 opt(
177 '--no-verify',
178 action='store_true',
179 dest='no_verify',
180 default=False,
181 short='Disable commit-msg hook',
182 long="This option bypasses the commit-msg hook.",
187 def message_options(save_template):
188 def no_dup(parser):
189 if parser.values.message is not None:
190 raise optparse.OptionValueError(
191 'Cannot give more than one --message or --file'
194 def no_combine(parser):
195 if (
196 save_template
197 and parser.values.message is not None
198 and parser.values.save_template is not None
200 raise optparse.OptionValueError(
201 'Cannot give both --message/--file and --save-template'
204 def msg_callback(option, opt_str, value, parser):
205 no_dup(parser)
206 if value and not value.endswith('\n'):
207 value += '\n'
208 parser.values.message = value
209 no_combine(parser)
211 def file_callback(option, opt_str, value, parser):
212 no_dup(parser)
213 if value == '-':
214 parser.values.message = sys.stdin.read()
215 else:
216 with open(value) as f:
217 parser.values.message = f.read()
218 no_combine(parser)
220 def templ_callback(option, opt_str, value, parser):
221 if value == '-':
222 parser.values.save_template = out.stdout_bytes
223 else:
225 def write_file(s):
226 with io.open(value, 'wb') as f:
227 f.write(s)
229 parser.values.save_template = write_file
230 no_combine(parser)
232 opts = [
233 opt(
234 '-m',
235 '--message',
236 action='callback',
237 callback=msg_callback,
238 dest='message',
239 type='string',
240 short='Use MESSAGE instead of invoking the editor',
242 opt(
243 '-f',
244 '--file',
245 action='callback',
246 callback=file_callback,
247 dest='message',
248 type='string',
249 args=['files'],
250 metavar='FILE',
251 short='Use FILE instead of invoking the editor',
252 long="""
253 Use the contents of FILE instead of invoking the editor.
254 (If FILE is "-", write to stdout.)""",
257 if save_template:
258 opts.append(
259 opt(
260 '--save-template',
261 action='callback',
262 dest='save_template',
263 callback=templ_callback,
264 metavar='FILE',
265 type='string',
266 short='Save the message template to FILE and exit',
267 long="""
268 Instead of running the command, just write the message
269 template to FILE, and exit. (If FILE is "-", write to
270 stdout.)
272 When driving StGit from another program, it is often
273 useful to first call a command with '--save-template',
274 then let the user edit the message, and then call the
275 same command with '--file'.""",
278 return opts
281 def diff_opts_option():
282 def diff_opts_callback(option, opt_str, value, parser):
283 if value:
284 parser.values.diff_flags.extend(value.split())
285 else:
286 parser.values.diff_flags = []
288 return [
289 opt(
290 '-O',
291 '--diff-opts',
292 dest='diff_flags',
293 default=(config.get('stgit.diff-opts') or '').split(),
294 action='callback',
295 callback=diff_opts_callback,
296 type='string',
297 metavar='OPTIONS',
298 args=[strings('-M', '-C')],
299 short='Extra options to pass to "git diff"',
304 def author_options():
305 """Create command line options for setting author information.
307 The ``opts.author`` destination variable is a callback function that modifies a
308 :class:`Person` according to the these command line options.
312 def short_callback(option, opt_str, value, parser, field):
313 f = parser.values.author
314 if field == "date":
315 value = Date(value)
316 parser.values.author = lambda p: getattr(f(p), 'set_' + field)(value)
318 def full_callback(option, opt_str, value, parser):
319 ne = utils.parse_name_email(value)
320 if not ne:
321 raise optparse.OptionValueError(
322 'Bad %s specification: %r' % (opt_str, value)
324 name, email = ne
325 short_callback(option, opt_str, name, parser, 'name')
326 short_callback(option, opt_str, email, parser, 'email')
328 return [
329 opt(
330 '--author',
331 metavar='"NAME <EMAIL>"',
332 type='string',
333 action='callback',
334 callback=full_callback,
335 dest='author',
336 default=lambda p: p,
337 short='Set the author details',
339 ] + [
340 opt(
341 '--auth%s' % field,
342 metavar=field.upper(),
343 type='string',
344 action='callback',
345 callback=short_callback,
346 dest='author',
347 callback_args=(field,),
348 short='Set the author %s' % field,
350 for field in ['name', 'email', 'date']
354 def keep_option():
355 return [
356 opt(
357 '-k',
358 '--keep',
359 action='store_true',
360 short='Keep the local changes',
361 default=config.get('stgit.autokeep') == 'yes',
366 def merged_option():
367 return [
368 opt(
369 '-m',
370 '--merged',
371 action='store_true',
372 short='Check for patches merged upstream',
377 class strings(list):
378 def __init__(self, *args):
379 super().__init__(args)
382 class patch_range(list):
383 def __init__(self, *args):
384 super().__init__(args)