/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 ):
3 by Tim Marston
added bzr as a vcs backend; finished init command; implemented deployment
41
		command = ' ' + the.command if the.command else ''
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
42
		self.die( error_message + \
43
				  "\nTry '%s%s --help' for more information." % \
3 by Tim Marston
added bzr as a vcs backend; finished init command; implemented deployment
44
				  ( self.name, command ) )
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
3 by Tim Marston
added bzr as a vcs backend; finished init command; implemented deployment
51
		print "Tool to manage a set of files in your home directory and distribute them across"
52
		print "multiple computers, merging local changes (in the same way as you would manage"
53
		print "source code under version control)."
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
54
		print
55
		print "Global options (for all commands):"
56
		print "     --help     display help and exit"
57
		print "     --version  output version information and exit"
58
		print
59
		print "Commands:"
60
		print "  init          initialise a local copy of your repositories"
61
		print "  update        update files in your home directory"
62
		print "  resolve       try to finish an update (that had conflicts)"
63
		print "  add           add local files/changes to the repository"
64
		print "  remove        remove a local file from the repository"
3 by Tim Marston
added bzr as a vcs backend; finished init command; implemented deployment
65
		print "  status        list files that have changed locally"
66
		print "  diff          shows changes made to local files"
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
67
		print "  revert        undo changes made to a local file"
68
		print "  stage-add     stage local files/changes"
69
		print "  stage-remove  stage the removal of files"
70
		print "  stage-revert  revert staged changes"
71
		print "  stage-status  show status of staging area"
3 by Tim Marston
added bzr as a vcs backend; finished init command; implemented deployment
72
		print "  stage-diff    shows staged changes"
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
73
		print "  stage-commit  commit staged changes to repository"
74
		print
75
		print "For help about a particular command (including the additional options that the"
76
		print "command accepts) try typing:"
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
77
		print "  $ " + self.name + " COMMAND --help"
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
78
		exit( 0 )
79
80
81
	def print_version( self ):
82
		print "stdhome " + self.version
83
		print
84
		print "Copyright (C) 2013 Tim Marston"
85
		print
86
		#      01234567890123456789012345678901234567890123456789012345678901234567890123456789
87
		print "This program is free software, and you may use, modify and redistribute it"
88
		print "under the terms of the GNU General Public License version 3 or later.  This"
89
		print "program comes with ABSOLUTELY NO WARRANTY, to the extent permitted by law."
90
		print
91
		print "For more information, including documentation, please see the project website"
92
		print "at http://ed.am/dev/stdhome."
93
		print
94
		print "Please report bugs to <tim@ed.am>."
95
		exit( 0 )
96
97
98
	def check_command( self, command ):
99
		"""
100
		Check that the given command is valid and return the full name of the command.
101
	
102
		Arguments:
103
		- `command`: the given command
104
		"""
105
		# commands
3 by Tim Marston
added bzr as a vcs backend; finished init command; implemented deployment
106
		if [ 'init', 'update', 'resolve', 'add', 'remove', 'revert', 'status',
107
			 'diff', 'stage-add', 'stage-remove', 'stage-revert',
108
			 'stage-status', 'stage-diff', 'stage-commit'
109
		].count( command ) == 1:
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
110
			return command
111
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
112
		# resolve aliases
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
113
		elif command == 'up':
114
			return 'update'
115
		elif command == 'rm':
116
			return 'remove'
117
118
		# invalid
119
		else:
120
			return None
121
122
123
	def get_command_argument( self, args ):
124
		"""
125
		Find the first program argument what isn't an option argument.
126
127
        Arguments:
128
        - `args`: the program arguments
129
        """
130
		while args:
131
			if args[ 0 ] == '--':
132
				return args[ 1 ] if len( args ) > 1 else None
133
			if args[ 0 ][ 0 : 1 ] != '-':
134
				return args[ 0 ]
135
			args = args[ 1 : ]
136
		return None
137
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
138
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
139
	def run( self ):
140
		# make an initial attempt to parse the command line, looking only for
141
		# --help and --version, so that they have the chance to run without a
142
		# command being specified
143
		try:
144
			opts, args = getopt.gnu_getopt(
145
				sys.argv[ 1: ], "",
146
				[ "help", "version" ] )
147
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
148
			for opt, optarg in opts:
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
149
				# we only show help if there are no non-option arguments (e.g.,
150
				# a command) specified.  If a command has been specified it will
151
				# have to be parsed and --help will be handled by it, instead)
152
				if opt == "--help" and len( args ) == 0:
153
					self.print_help()
154
				elif opt == "--version":
155
					self.print_version()
156
157
		except getopt.GetoptError as e:
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
158
			# 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
159
			pass
160
161
		# find the first non-option argument (the command)
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
162
		the.command = self.get_command_argument( sys.argv[ 1: ] )
163
		if the.command == None:
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
164
			self.print_usage( "missing command" )
165
166
		# check command is valid
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
167
		the.command = self.check_command( the.command )
168
		if the.command == None:
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
169
			self.print_usage( "bad command" )
170
3 by Tim Marston
added bzr as a vcs backend; finished init command; implemented deployment
171
		# calculate module and class name
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
172
		bits = the.command.split( '-' )
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
173
		class_name = 'Command'
3 by Tim Marston
added bzr as a vcs backend; finished init command; implemented deployment
174
		module_name = 'command'
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
175
		for bit in bits:
176
			class_name += bit[ 0 ].upper() + bit[ 1 : ]
3 by Tim Marston
added bzr as a vcs backend; finished init command; implemented deployment
177
			module_name += '_' + bit
1 by Tim Marston
initial commit; basic app startup and initial command-line processing
178
3 by Tim Marston
added bzr as a vcs backend; finished init command; implemented deployment
179
		# import module and instantiate the class
180
		module = __import__( 'stdhome.' + module_name,
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
181
							 fromlist = [ class_name ] )
182
		instance = getattr( module, class_name )()
183
3 by Tim Marston
added bzr as a vcs backend; finished init command; implemented deployment
184
		# run the command
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
185
		try:
186
			instance.parse_command_line()
187
			instance.run()
188
		except ( getopt.GetoptError, self.UsageError ) as e:
189
			self.print_usage( e.msg )
190
		except self.FatalError as e:
3 by Tim Marston
added bzr as a vcs backend; finished init command; implemented deployment
191
			message = e.msg.rstrip()
192
			if the.verbose and hasattr( e, 'output' ) and e.output:
193
				message += '\n\nOUTPUT:\n' + e.output.rstrip()
2 by Tim Marston
added global objects (the.repo, the.program), deployment object and implemented
194
			self.die( message )
195
196
197
	class UsageError( Exception ):
198
199
		def __init__( self, error_message ):
200
			self.msg = error_message
201
202
203
	class FatalError( Exception ):
204
205
		def __init__( self, message, output = None ):
206
			self.msg = message
207
			self.output = output