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