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)."""
5 import optparse
, sys
, textwrap
6 from stgit
import utils
7 from stgit
.config
import config
8 from stgit
.lib
import git
10 def _splitlist(lst
, split_on
):
11 """Iterate over the sublists of lst that are separated by an element e
12 such that split_on(e) is true."""
23 """Split a string s into a list of paragraphs, each of which is a list
25 lines
= [line
.rstrip() for line
in textwrap
.dedent(s
).strip().splitlines()]
26 return [p
for p
in _splitlist(lines
, lambda line
: not line
.strip()) if p
]
29 """Represents a command-line flag."""
30 def __init__(self
, *pargs
, **kwargs
):
34 kwargs
= dict(self
.kwargs
)
35 kwargs
['help'] = kwargs
['short']
36 for k
in ['short', 'long', 'args']:
38 return optparse
.make_option(*self
.pargs
, **kwargs
)
41 if not o
.takes_value():
45 for flag
in self
.pargs
:
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
.pargs
:
56 paras
= _paragraphs(self
.kwargs
.get('long', self
.kwargs
['short'] + '.'))
58 f
.write(' '*8 + line
+ '\n')
59 for para
in paras
[1:]:
68 if self
.kwargs
.get('action', None) in ['store_true', 'store_false']:
72 return self
.kwargs
.get('args', default
)
74 def _cmd_name(cmd_mod
):
75 return getattr(cmd_mod
, 'name', cmd_mod
.__name
__.split('.')[-1])
77 def make_option_parser(cmd
):
78 pad
= ' '*len('Usage: ')
79 return optparse
.OptionParser(
80 prog
= 'stg %s' % _cmd_name(cmd
),
81 usage
= (('\n' + pad
).join('%%prog %s' % u
for u
in cmd
.usage
) +
83 option_list
= [o
.get_option() for o
in cmd
.options
])
85 def _write_underlined(s
, u
, f
):
87 f
.write(u
*len(s
) + '\n')
89 def write_asciidoc(cmd
, f
):
90 _write_underlined('stg-%s(1)' % _cmd_name(cmd
), '=', f
)
92 _write_underlined('NAME', '-', f
)
93 f
.write('stg-%s - %s\n\n' % (_cmd_name(cmd
), cmd
.help))
94 _write_underlined('SYNOPSIS', '-', f
)
97 f
.write("'stg' %s %s\n" % (_cmd_name(cmd
), u
))
99 _write_underlined('DESCRIPTION', '-', f
)
100 f
.write('\n%s\n\n' % cmd
.description
.strip('\n'))
102 _write_underlined('OPTIONS', '-', f
)
103 for o
in cmd
.options
:
106 _write_underlined('StGit', '-', f
)
107 f
.write('Part of the StGit suite - see linkman:stg[1]\n')
110 def callback(option
, opt_str
, value
, parser
, sign_str
):
111 if parser
.values
.sign_str
not in [None, sign_str
]:
112 raise optparse
.OptionValueError(
113 'Cannot give more than one of --ack, --sign, --review')
114 parser
.values
.sign_str
= sign_str
116 opt('--sign', action
= 'callback', dest
= 'sign_str', args
= [],
117 callback
= callback
, callback_args
= ('Signed-off-by',),
118 short
= 'Add "Signed-off-by:" line', long = """
119 Add a "Signed-off-by:" to the end of the patch."""),
120 opt('--ack', action
= 'callback', dest
= 'sign_str', args
= [],
121 callback
= callback
, callback_args
= ('Acked-by',),
122 short
= 'Add "Acked-by:" line', long = """
123 Add an "Acked-by:" line to the end of the patch."""),
124 opt('--review', action
= 'callback', dest
= 'sign_str', args
= [],
125 callback
= callback
, callback_args
= ('Reviewed-by',),
126 short
= 'Add "Reviewed-by:" line', long = """
127 Add a "Reviewed-by:" line to the end of the patch.""")]
131 opt('--no-verify', action
= 'store_true', dest
= 'no_verify',
132 default
= False, short
= 'Disable commit-msg hook', long = """
133 This option bypasses the commit-msg hook."""),
136 def message_options(save_template
):
138 if parser
.values
.message
!= None:
139 raise optparse
.OptionValueError(
140 'Cannot give more than one --message or --file')
141 def no_combine(parser
):
142 if (save_template
and parser
.values
.message
!= None
143 and parser
.values
.save_template
!= None):
144 raise optparse
.OptionValueError(
145 'Cannot give both --message/--file and --save-template')
146 def msg_callback(option
, opt_str
, value
, parser
):
148 parser
.values
.message
= value
150 def file_callback(option
, opt_str
, value
, parser
):
153 parser
.values
.message
= sys
.stdin
.read()
156 parser
.values
.message
= f
.read()
159 def templ_callback(option
, opt_str
, value
, parser
):
165 f
= file(value
, 'w+')
168 parser
.values
.save_template
= w
171 opt('-m', '--message', action
= 'callback',
172 callback
= msg_callback
, dest
= 'message', type = 'string',
173 short
= 'Use MESSAGE instead of invoking the editor'),
174 opt('-f', '--file', action
= 'callback', callback
= file_callback
,
175 dest
= 'message', type = 'string', args
= [files
],
177 short
= 'Use FILE instead of invoking the editor', long = """
178 Use the contents of FILE instead of invoking the editor.
179 (If FILE is "-", write to stdout.)""")]
182 opt('--save-template', action
= 'callback', dest
= 'save_template',
183 callback
= templ_callback
, metavar
= 'FILE', type = 'string',
184 short
= 'Save the message template to FILE and exit', long = """
185 Instead of running the command, just write the message
186 template to FILE, and exit. (If FILE is "-", write to
189 When driving StGit from another program, it is often
190 useful to first call a command with '--save-template',
191 then let the user edit the message, and then call the
192 same command with '--file'."""))
195 def diff_opts_option():
196 def diff_opts_callback(option
, opt_str
, value
, parser
):
198 parser
.values
.diff_flags
.extend(value
.split())
200 parser
.values
.diff_flags
= []
202 opt('-O', '--diff-opts', dest
= 'diff_flags',
203 default
= (config
.get('stgit.diff-opts') or '').split(),
204 action
= 'callback', callback
= diff_opts_callback
,
205 type = 'string', metavar
= 'OPTIONS',
206 args
= [strings('-M', '-C')],
207 short
= 'Extra options to pass to "git diff"')]
209 def _person_opts(person
, short
):
210 """Sets options.<person> to a function that modifies a Person
211 according to the commandline options."""
212 def short_callback(option
, opt_str
, value
, parser
, field
):
213 f
= getattr(parser
.values
, person
)
215 value
= git
.Date(value
)
216 setattr(parser
.values
, person
,
217 lambda p
: getattr(f(p
), 'set_' + field
)(value
))
218 def full_callback(option
, opt_str
, value
, parser
):
219 ne
= utils
.parse_name_email(value
)
221 raise optparse
.OptionValueError(
222 'Bad %s specification: %r' % (opt_str
, value
))
224 short_callback(option
, opt_str
, name
, parser
, 'name')
225 short_callback(option
, opt_str
, email
, parser
, 'email')
227 [opt('--%s' % person
, metavar
= '"NAME <EMAIL>"', type = 'string',
228 action
= 'callback', callback
= full_callback
, dest
= person
,
229 default
= lambda p
: p
, short
= 'Set the %s details' % person
)] +
230 [opt('--%s%s' % (short
, f
), metavar
= f
.upper(), type = 'string',
231 action
= 'callback', callback
= short_callback
, dest
= person
,
232 callback_args
= (f
,), short
= 'Set the %s %s' % (person
, f
))
233 for f
in ['name', 'email', 'date']])
235 def author_options():
236 return _person_opts('author', 'auth')
239 return [opt('-k', '--keep', action
= 'store_true',
240 short
= 'Keep the local changes',
241 default
= config
.get('stgit.autokeep') == 'yes')]
244 return [opt('-m', '--merged', action
= 'store_true',
245 short
= 'Check for patches merged upstream')]
247 class CompgenBase(object):
248 def actions(self
, var
): return set()
249 def words(self
, var
): return set()
250 def command(self
, var
):
252 for act
in self
.actions(var
):
254 words
= self
.words(var
)
256 cmd
+= ['-W', '"%s"' % ' '.join(words
)]
257 cmd
+= ['--', '"%s"' % var
]
260 class CompgenJoin(CompgenBase
):
261 def __init__(self
, a
, b
):
262 assert isinstance(a
, CompgenBase
)
263 assert isinstance(b
, CompgenBase
)
266 def words(self
, var
): return self
.__a
.words(var
) | self
.__b
.words(var
)
267 def actions(self
, var
): return self
.__a
.actions(var
) | self
.__b
.actions(var
)
269 class Compgen(CompgenBase
):
270 def __init__(self
, words
= frozenset(), actions
= frozenset()):
271 self
.__words
= set(words
)
272 self
.__actions
= set(actions
)
273 def actions(self
, var
): return self
.__actions
274 def words(self
, var
): return self
.__words
276 def compjoin(compgens
):
279 comp
= CompgenJoin(comp
, c
)
282 all_branches
= Compgen(['$(_all_branches)'])
283 stg_branches
= Compgen(['$(_stg_branches)'])
284 applied_patches
= Compgen(['$(_applied_patches)'])
285 other_applied_patches
= Compgen(['$(_other_applied_patches)'])
286 unapplied_patches
= Compgen(['$(_unapplied_patches)'])
287 hidden_patches
= Compgen(['$(_hidden_patches)'])
288 commit
= Compgen(['$(_all_branches) $(_tags) $(_remotes)'])
289 conflicting_files
= Compgen(['$(_conflicting_files)'])
290 dirty_files
= Compgen(['$(_dirty_files)'])
291 unknown_files
= Compgen(['$(_unknown_files)'])
292 known_files
= Compgen(['$(_known_files)'])
293 repo
= Compgen(actions
= ['directory'])
294 dir = Compgen(actions
= ['directory'])
295 files
= Compgen(actions
= ['file'])
296 def strings(*ss
): return Compgen(ss
)
297 class patch_range(CompgenBase
):
298 def __init__(self
, *endpoints
):
299 self
.__endpoints
= endpoints
300 def words(self
, var
):
302 for e
in self
.__endpoints
:
303 assert not e
.actions(var
)
304 words |
= e
.words(var
)
305 return set(['$(_patch_range "%s" "%s")' % (' '.join(words
), var
)])