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