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