/stdhome

To get this branch, use:
bzr branch http://bzr.ed.am/stdhome
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
1
# program.py
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
2
#
3
# Copyright (C) 2013 Tim Marston <tim@edm.am>
4
#
5
# This file is part of stdhome (hereafter referred to as "this program").
6
# See http://ed.am/dev/stdhome for more information.
7
#
8
# This program is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU General Public License as published by
10
# the Free Software Foundation, either version 3 of the License, or
11
# (at your option) any later version.
12
#
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
# GNU General Public License for more details.
17
#
18
# You should have received a copy of the GNU General Public License
19
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
22
import os, sys, getopt
23
import the
24
25
26
class Program:
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
27
28
29
	def __init__( self, version ):
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
30
		self.name = os.path.basename( sys.argv[ 0 ] )
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
31
		self.version = version
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
32
33
34
	def die( self, error_message ):
35
		prefix = self.name + ( ' ' + the.command if the.command else '' )
36
		print >> sys.stderr, '%s: %s' % ( prefix, error_message )
37
		exit( 1 )
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
38
39
40
	def print_usage( self, error_message ):
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
41
		suffix = ' ' + the.command if the.command else ''
42
		self.die( error_message + \
43
				  "\nTry '%s%s --help' for more information." % \
44
				  ( self.name, suffix ) )
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
45
46
47
	def print_help( self ):
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
48
		print "Usage: " + self.name + " COMMAND [OPTION]..."
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
49
		print
50
		#      01234567890123456789012345678901234567890123456789012345678901234567890123456789
51
		print "Manage your home directories, across multiple computers, similar to how you"
52
		print "would software under version control."
53
		print
54
		print "Global options (for all commands):"
55
		print "     --help     display help and exit"
56
		print "     --version  output version information and exit"
57
		print
58
		print "Commands:"
59
		print "  init          initialise a local copy of your repositories"
60
		print "  update        update files in your home directory"
61
		print "  resolve       try to finish an update (that had conflicts)"
62
		print "  add           add local files/changes to the repository"
63
		print "  remove        remove a local file from the repository"
64
		print "  revert        undo changes made to a local file"
65
		print "  stage-add     stage local files/changes"
66
		print "  stage-remove  stage the removal of files"
67
		print "  stage-revert  revert staged changes"
68
		print "  stage-status  show status of staging area"
69
		print "  stage-commit  commit staged changes to repository"
70
		print
71
		print "For help about a particular command (including the additional options that the"
72
		print "command accepts) try typing:"
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
73
		print "  $ " + self.name + " COMMAND --help"
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
74
		exit( 0 )
75
76
77
	def print_version( self ):
78
		print "stdhome " + self.version
79
		print
80
		print "Copyright (C) 2013 Tim Marston"
81
		print
82
		#      01234567890123456789012345678901234567890123456789012345678901234567890123456789
83
		print "This program is free software, and you may use, modify and redistribute it"
84
		print "under the terms of the GNU General Public License version 3 or later.  This"
85
		print "program comes with ABSOLUTELY NO WARRANTY, to the extent permitted by law."
86
		print
87
		print "For more information, including documentation, please see the project website"
88
		print "at http://ed.am/dev/stdhome."
89
		print
90
		print "Please report bugs to <tim@ed.am>."
91
		exit( 0 )
92
93
94
	def check_command( self, command ):
95
		"""
96
		Check that the given command is valid and return the full name of the command.
97
	
98
		Arguments:
99
		- `command`: the given command
100
		"""
101
		# commands
102
		if [ 'init', 'update', 'resolve', 'add', 'remove', 'revert',
103
			 'stage-add', 'stage-remove', 'stage-revert', 'stage-status',
104
			 'stage-commit' ].count( command ) == 1:
105
			return command
106
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
107
		# resolve aliases
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
108
		elif command == 'up':
109
			return 'update'
110
		elif command == 'rm':
111
			return 'remove'
112
113
		# invalid
114
		else:
115
			return None
116
117
118
	def get_command_argument( self, args ):
119
		"""
120
		Find the first program argument what isn't an option argument.
121
122
        Arguments:
123
        - `args`: the program arguments
124
        """
125
		while args:
126
			if args[ 0 ] == '--':
127
				return args[ 1 ] if len( args ) > 1 else None
128
			if args[ 0 ][ 0 : 1 ] != '-':
129
				return args[ 0 ]
130
			args = args[ 1 : ]
131
		return None
132
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
133
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
134
	def run( self ):
135
		# make an initial attempt to parse the command line, looking only for
136
		# --help and --version, so that they have the chance to run without a
137
		# command being specified
138
		try:
139
			opts, args = getopt.gnu_getopt(
140
				sys.argv[ 1: ], "",
141
				[ "help", "version" ] )
142
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
143
			for opt, optarg in opts:
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
144
				# we only show help if there are no non-option arguments (e.g.,
145
				# a command) specified.  If a command has been specified it will
146
				# have to be parsed and --help will be handled by it, instead)
147
				if opt == "--help" and len( args ) == 0:
148
					self.print_help()
149
				elif opt == "--version":
150
					self.print_version()
151
152
		except getopt.GetoptError as e:
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
153
			# ignore errors -- we aren't parsing the command line properly yet
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
154
			pass
155
156
		# find the first non-option argument (the command)
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
157
		the.command = self.get_command_argument( sys.argv[ 1: ] )
158
		if the.command == None:
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
159
			self.print_usage( "missing command" )
160
161
		# check command is valid
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
162
		the.command = self.check_command( the.command )
163
		if the.command == None:
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
164
			self.print_usage( "bad command" )
165
166
		# calculate class name
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
167
		bits = the.command.split( '-' )
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
168
		class_name = 'Command'
169
		for bit in bits:
170
			class_name += bit[ 0 ].upper() + bit[ 1 : ]
171
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
172
		# instantiate the command class
173
		module = __import__( 'stdhome.command_' + the.command,
174
							 fromlist = [ class_name ] )
175
		instance = getattr( module, class_name )()
176
177
		# run it
178
		try:
179
			instance.parse_command_line()
180
			instance.run()
181
		except ( getopt.GetoptError, self.UsageError ) as e:
182
			self.print_usage( e.msg )
183
		except self.FatalError as e:
184
			message = e.msg
185
			if the.verbose and e.output:
186
				message += '\n\nOUTPUT:\n' + e.output
187
			self.die( message )
188
189
190
	class UsageError( Exception ):
191
192
		def __init__( self, error_message ):
193
			self.msg = error_message
194
195
196
	class FatalError( Exception ):
197
198
		def __init__( self, message, output = None ):
199
			self.msg = message
200
			self.output = output