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
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."""
22 """Split a string s into a list of paragraphs, each of which is a list
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
]
28 """Represents a command-line flag."""
29 def __init__(self
, *pargs
, **kwargs
):
33 kwargs
= dict(self
.kwargs
)
34 kwargs
['help'] = kwargs
['short']
35 for k
in ['short', 'long', 'args']:
37 return optparse
.make_option(*self
.pargs
, **kwargs
)
40 if not o
.takes_value():
44 for flag
in self
.pargs
:
45 if flag
.startswith('--'):
46 return utils
.strip_prefix('--', flag
).upper()
47 raise Exception('Cannot determine metavar')
48 def write_asciidoc(self
, f
):
49 for flag
in self
.pargs
:
55 paras
= _paragraphs(self
.kwargs
.get('long', self
.kwargs
['short'] + '.'))
57 f
.write(' '*8 + line
+ '\n')
58 for para
in paras
[1:]:
67 if self
.kwargs
.get('action', None) in ['store_true', 'store_false']:
71 return self
.kwargs
.get('args', default
)
73 def _cmd_name(cmd_mod
):
74 return getattr(cmd_mod
, 'name', cmd_mod
.__name__
.split('.')[-1])
76 def make_option_parser(cmd
):
77 pad
= ' '*len('Usage: ')
78 return optparse
.OptionParser(
79 prog
= 'stg %s' %
_cmd_name(cmd
),
80 usage
= (('\n' + pad
).join('%%prog %s' % u for u in cmd
.usage
) +
82 option_list
= [o
.get_option() for o
in cmd
.options
])
84 def _write_underlined(s
, u
, f
):
86 f
.write(u
*len(s
) + '\n')
88 def write_asciidoc(cmd
, f
):
89 _write_underlined('stg-%s(1)' %
_cmd_name(cmd
), '=', f
)
91 _write_underlined('NAME', '-', f
)
92 f
.write('stg-%s - %s\n\n' %
(_cmd_name(cmd
), cmd
.help))
93 _write_underlined('SYNOPSIS', '-', f
)
96 f
.write("'stg' %s %s\n" %
(_cmd_name(cmd
), u
))
98 _write_underlined('DESCRIPTION', '-', f
)
99 f
.write('\n%s\n\n' % cmd
.description
.strip('\n'))
101 _write_underlined('OPTIONS', '-', f
)
102 for o
in cmd
.options
:
105 _write_underlined('StGit', '-', f
)
106 f
.write('Part of the StGit suite - see manlink:stg[1]\n')
109 def callback(option
, opt_str
, value
, parser
, sign_str
):
110 if parser
.values
.sign_str
not in [None, sign_str
]:
111 raise optparse
.OptionValueError(
112 '--ack and --sign were both specified')
113 parser
.values
.sign_str
= sign_str
115 opt('--sign', action
= 'callback', dest
= 'sign_str', args
= [],
116 callback
= callback
, callback_args
= ('Signed-off-by',),
117 short
= 'Add "Signed-off-by:" line', long = """
118 Add a "Signed-off-by:" to the end of the patch."""),
119 opt('--ack', action
= 'callback', dest
= 'sign_str', args
= [],
120 callback
= callback
, callback_args
= ('Acked-by',),
121 short
= 'Add "Acked-by:" line', long = """
122 Add an "Acked-by:" line to the end of the patch.""")]
124 def message_options(save_template
):
126 if parser
.values
.message
!= None:
127 raise optparse
.OptionValueError(
128 'Cannot give more than one --message or --file')
129 def no_combine(parser
):
130 if (save_template
and parser
.values
.message
!= None
131 and parser
.values
.save_template
!= None):
132 raise optparse
.OptionValueError(
133 'Cannot give both --message/--file and --save-template')
134 def msg_callback(option
, opt_str
, value
, parser
):
136 parser
.values
.message
= value
138 def file_callback(option
, opt_str
, value
, parser
):
141 parser
.values
.message
= sys
.stdin
.read()
144 parser
.values
.message
= f
.read()
147 def templ_callback(option
, opt_str
, value
, parser
):
153 f
= file(value
, 'w+')
156 parser
.values
.save_template
= w
159 opt('-m', '--message', action
= 'callback',
160 callback
= msg_callback
, dest
= 'message', type = 'string',
161 short
= 'Use MESSAGE instead of invoking the editor'),
162 opt('-f', '--file', action
= 'callback', callback
= file_callback
,
163 dest
= 'message', type = 'string', args
= [files
],
164 short
= 'Use FILE instead of invoking the editor', long = """
165 Use the contents of FILE instead of invoking the editor.
166 (If FILE is "-", write to stdout.)""")]
169 opt('--save-template', action
= 'callback', dest
= 'save_template',
170 callback
= templ_callback
, metavar
= 'FILE', type = 'string',
171 short
= 'Save the message template to FILE and exit', long = """
172 Instead of running the command, just write the message
173 template to FILE, and exit. (If FILE is "-", write to
176 When driving StGit from another program, it is often
177 useful to first call a command with '--save-template',
178 then let the user edit the message, and then call the
179 same command with '--file'."""))
182 def diff_opts_option():
183 def diff_opts_callback(option
, opt_str
, value
, parser
):
185 parser
.values
.diff_flags
.extend(value
.split())
187 parser
.values
.diff_flags
= []
189 opt('-O', '--diff-opts', dest
= 'diff_flags',
190 default
= (config
.get('stgit.diff-opts') or '').split(),
191 action
= 'callback', callback
= diff_opts_callback
,
192 type = 'string', metavar
= 'OPTIONS',
193 args
= [strings('-M', '-C')],
194 short
= 'Extra options to pass to "git diff"')]
196 def _person_opts(person
, short
):
197 """Sets options.<person> to a function that modifies a Person
198 according to the commandline options."""
199 def short_callback(option
, opt_str
, value
, parser
, field
):
200 f
= getattr(parser
.values
, person
)
201 setattr(parser
.values
, person
,
202 lambda p
: getattr(f(p
), 'set_' + field
)(value
))
203 def full_callback(option
, opt_str
, value
, parser
):
204 ne
= utils
.parse_name_email(value
)
206 raise optparse
.OptionValueError(
207 'Bad %s specification: %r' %
(opt_str
, value
))
209 short_callback(option
, opt_str
, name
, parser
, 'name')
210 short_callback(option
, opt_str
, email
, parser
, 'email')
212 [opt('--%s' % person
, metavar
= '"NAME <EMAIL>"', type = 'string',
213 action
= 'callback', callback
= full_callback
, dest
= person
,
214 default
= lambda p
: p
, short
= 'Set the %s details' % person
)] +
215 [opt('--%s%s' %
(short
, f
), metavar
= f
.upper(), type = 'string',
216 action
= 'callback', callback
= short_callback
, dest
= person
,
217 callback_args
= (f
,), short
= 'Set the %s %s' %
(person
, f
))
218 for f
in ['name', 'email', 'date']])
220 def author_options():
221 return _person_opts('author', 'auth')
223 class CompgenBase(object):
224 def actions(self
, var
): return set()
225 def words(self
, var
): return set()
226 def command(self
, var
):
228 for act
in self
.actions(var
):
230 words
= self
.words(var
)
232 cmd
+= ['-W', '"%s"' %
' '.join(words
)]
233 cmd
+= ['--', '"%s"' % var
]
236 class CompgenJoin(CompgenBase
):
237 def __init__(self
, a
, b
):
238 assert isinstance(a
, CompgenBase
)
239 assert isinstance(b
, CompgenBase
)
242 def words(self
, var
): return self
.__a
.words(var
) | self
.__b
.words(var
)
243 def actions(self
, var
): return self
.__a
.actions(var
) | self
.__b
.actions(var
)
245 class Compgen(CompgenBase
):
246 def __init__(self
, words
= frozenset(), actions
= frozenset()):
247 self
.__words
= set(words
)
248 self
.__actions
= set(actions
)
249 def actions(self
, var
): return self
.__actions
250 def words(self
, var
): return self
.__words
252 def compjoin(compgens
):
255 comp
= CompgenJoin(comp
, c
)
258 all_branches
= Compgen(['$(_all_branches)'])
259 stg_branches
= Compgen(['$(_stg_branches)'])
260 applied_patches
= Compgen(['$(_applied_patches)'])
261 other_applied_patches
= Compgen(['$(_other_applied_patches)'])
262 unapplied_patches
= Compgen(['$(_unapplied_patches)'])
263 hidden_patches
= Compgen(['$(_hidden_patches)'])
264 commit
= Compgen(['$(_all_branches) $(_tags) $(_remotes)'])
265 conflicting_files
= Compgen(['$(_conflicting_files)'])
266 dirty_files
= Compgen(['$(_dirty_files)'])
267 unknown_files
= Compgen(['$(_unknown_files)'])
268 known_files
= Compgen(['$(_known_files)'])
269 repo
= Compgen(actions
= ['directory'])
270 dir = Compgen(actions
= ['directory'])
271 files
= Compgen(actions
= ['file'])
272 def strings(*ss
): return Compgen(ss
)
273 class patch_range(CompgenBase
):
274 def __init__(self
, *endpoints
):
275 self
.__endpoints
= endpoints
276 def words(self
, var
):
278 for e
in self
.__endpoints
:
279 assert not e
.actions(var
)
280 words |
= e
.words(var
)
281 return set(['$(_patch_range "%s" "%s")' %
(' '.join(words
), var
)])