Refactor message printing
[stgit] / stgit / main.py
1 """Basic quilt-like functionality
2 """
3
4 __copyright__ = """
5 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License version 2 as
9 published by the Free Software Foundation.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 """
20
21 import sys, os
22 from optparse import OptionParser
23
24 import stgit.commands
25 from stgit.utils import out
26
27 #
28 # The commands map
29 #
30 class Commands(dict):
31 """Commands class. It performs on-demand module loading
32 """
33 def canonical_cmd(self, key):
34 """Return the canonical name for a possibly-shortenned
35 command name.
36 """
37 candidates = [cmd for cmd in self.keys() if cmd.startswith(key)]
38
39 if not candidates:
40 out.error('Unknown command: %s' % key,
41 'Try "%s help" for a list of supported commands' % prog)
42 sys.exit(1)
43 elif len(candidates) > 1:
44 out.error('Ambiguous command: %s' % key,
45 'Candidates are: %s' % ', '.join(candidates))
46 sys.exit(1)
47
48 return candidates[0]
49
50 def __getitem__(self, key):
51 """Return the command python module name based.
52 """
53 global prog
54
55 cmd_mod = self.get(key) or self.get(self.canonical_cmd(key))
56
57 __import__('stgit.commands.' + cmd_mod)
58 return getattr(stgit.commands, cmd_mod)
59
60 commands = Commands({
61 'add': 'add',
62 'applied': 'applied',
63 'assimilate': 'assimilate',
64 'branch': 'branch',
65 'delete': 'delete',
66 'diff': 'diff',
67 'clean': 'clean',
68 'clone': 'clone',
69 'commit': 'commit',
70 'cp': 'copy',
71 'export': 'export',
72 'files': 'files',
73 'float': 'float',
74 'fold': 'fold',
75 'goto': 'goto',
76 'hide': 'hide',
77 'id': 'id',
78 'import': 'imprt',
79 'init': 'init',
80 'log': 'log',
81 'mail': 'mail',
82 'new': 'new',
83 'patches': 'patches',
84 'pick': 'pick',
85 'pop': 'pop',
86 'pull': 'pull',
87 'push': 'push',
88 'rebase': 'rebase',
89 'refresh': 'refresh',
90 'rename': 'rename',
91 'resolved': 'resolved',
92 'rm': 'rm',
93 'series': 'series',
94 'show': 'show',
95 'sink': 'sink',
96 'status': 'status',
97 'sync': 'sync',
98 'top': 'top',
99 'unapplied': 'unapplied',
100 'uncommit': 'uncommit',
101 'unhide': 'unhide'
102 })
103
104 # classification: repository, stack, patch, working copy
105 repocommands = (
106 'clone',
107 'id',
108 )
109 stackcommands = (
110 'applied',
111 'assimilate',
112 'branch',
113 'clean',
114 'commit',
115 'float',
116 'goto',
117 'hide',
118 'init',
119 'patches',
120 'pop',
121 'pull',
122 'push',
123 'rebase',
124 'series',
125 'sink',
126 'top',
127 'unapplied',
128 'uncommit',
129 'unhide',
130 )
131 patchcommands = (
132 'delete',
133 'export',
134 'files',
135 'fold',
136 'import',
137 'log',
138 'mail',
139 'new',
140 'pick',
141 'refresh',
142 'rename',
143 'show',
144 'sync',
145 )
146 wccommands = (
147 'add',
148 'cp',
149 'diff',
150 'resolved',
151 'rm',
152 'status',
153 )
154
155 def _print_helpstring(cmd):
156 print ' ' + cmd + ' ' * (12 - len(cmd)) + commands[cmd].help
157
158 def print_help():
159 print 'usage: %s <command> [options]' % os.path.basename(sys.argv[0])
160 print
161 print 'Generic commands:'
162 print ' help print the detailed command usage'
163 print ' version display version information'
164 print ' copyright display copyright information'
165 # unclassified commands if any
166 cmds = commands.keys()
167 cmds.sort()
168 for cmd in cmds:
169 if not cmd in repocommands and not cmd in stackcommands \
170 and not cmd in patchcommands and not cmd in wccommands:
171 _print_helpstring(cmd)
172 print
173
174 print 'Repository commands:'
175 for cmd in repocommands:
176 _print_helpstring(cmd)
177 print
178
179 print 'Stack commands:'
180 for cmd in stackcommands:
181 _print_helpstring(cmd)
182 print
183
184 print 'Patch commands:'
185 for cmd in patchcommands:
186 _print_helpstring(cmd)
187 print
188
189 print 'Working-copy commands:'
190 for cmd in wccommands:
191 _print_helpstring(cmd)
192
193 #
194 # The main function (command dispatcher)
195 #
196 def main():
197 """The main function
198 """
199 global prog
200
201 prog = os.path.basename(sys.argv[0])
202
203 if len(sys.argv) < 2:
204 print >> sys.stderr, 'usage: %s <command>' % prog
205 print >> sys.stderr, \
206 ' Try "%s --help" for a list of supported commands' % prog
207 sys.exit(1)
208
209 cmd = sys.argv[1]
210
211 if cmd in ['-h', '--help']:
212 if len(sys.argv) >= 3:
213 cmd = commands.canonical_cmd(sys.argv[2])
214 sys.argv[2] = '--help'
215 else:
216 print_help()
217 sys.exit(0)
218 if cmd == 'help':
219 if len(sys.argv) == 3 and not sys.argv[2] in ['-h', '--help']:
220 cmd = commands.canonical_cmd(sys.argv[2])
221 if not cmd in commands:
222 out.error('%s help: "%s" command unknown' % (prog, cmd))
223 sys.exit(1)
224
225 sys.argv[0] += ' %s' % cmd
226 command = commands[cmd]
227 parser = OptionParser(usage = command.usage,
228 option_list = command.options)
229 from pydoc import pager
230 pager(parser.format_help())
231 else:
232 print_help()
233 sys.exit(0)
234 if cmd in ['-v', '--version', 'version']:
235 from stgit.version import version
236 print 'Stacked GIT %s' % version
237 os.system('git --version')
238 print 'Python version %s' % sys.version
239 sys.exit(0)
240 if cmd in ['copyright']:
241 print __copyright__
242 sys.exit(0)
243
244 # re-build the command line arguments
245 sys.argv[0] += ' %s' % commands.canonical_cmd(cmd)
246 del(sys.argv[1])
247
248 command = commands[cmd]
249 usage = command.usage.split('\n')[0].strip()
250 parser = OptionParser(usage = usage, option_list = command.options)
251 options, args = parser.parse_args()
252
253 # These modules are only used from this point onwards and do not
254 # need to be imported earlier
255 from stgit.config import config_setup
256 from ConfigParser import ParsingError, NoSectionError
257 from stgit.stack import Series, StackException
258 from stgit.git import GitException
259 from stgit.commands.common import CmdException
260 from stgit.gitmergeonefile import GitMergeException
261 from stgit.utils import EditorException
262
263 try:
264 debug_level = int(os.environ['STGIT_DEBUG_LEVEL'])
265 except KeyError:
266 debug_level = 0
267 except ValueError:
268 out.error('Invalid STGIT_DEBUG_LEVEL environment variable')
269 sys.exit(1)
270
271 try:
272 config_setup()
273
274 # 'clone' doesn't expect an already initialised GIT tree. A Series
275 # object will be created after the GIT tree is cloned
276 if cmd != 'clone':
277 if hasattr(options, 'branch') and options.branch:
278 command.crt_series = Series(options.branch)
279 else:
280 command.crt_series = Series()
281 stgit.commands.common.crt_series = command.crt_series
282
283 command.func(parser, options, args)
284 except (IOError, ParsingError, NoSectionError, CmdException,
285 StackException, GitException, GitMergeException,
286 EditorException), err:
287 print >> sys.stderr, '%s %s: %s' % (prog, cmd, err)
288 if debug_level:
289 raise
290 else:
291 sys.exit(2)
292 except KeyboardInterrupt:
293 sys.exit(1)
294
295 sys.exit(0)