Browse code

- Added interactive configurator (--configure) - Added config dumper (--dump-config) - Improved --help output

git-svn-id: https://s3tools.svn.sourceforge.net/svnroot/s3tools/s3py/trunk@55 830e0280-6d2a-0410-9c65-932aecc39d9d

Michal Ludvig authored on 2007/01/19 09:20:14
Showing 2 changed files
... ...
@@ -46,6 +46,9 @@ class Config(object):
46 46
 			self.update_option(option, cp.get(option))
47 47
 		self._parsed_files.append(configfile)
48 48
 
49
+	def dump_config(self, stream):
50
+		ConfigDumper(stream).dump("default", self)
51
+
49 52
 	def update_option(self, option, value):
50 53
 		if value is None:
51 54
 			return
... ...
@@ -72,7 +75,7 @@ class Config(object):
72 72
 		else:							# string
73 73
 			setattr(Config, option, value)
74 74
 
75
-class ConfigParser:
75
+class ConfigParser(object):
76 76
 	def __init__(self, file, sections = []):
77 77
 		self.cfg = {}
78 78
 		self.parse_file(file, sections)
... ...
@@ -116,3 +119,13 @@ class ConfigParser:
116 116
 		if self.cfg.has_key(name):
117 117
 			return self.cfg[name]
118 118
 		return default
119
+
120
+class ConfigDumper(object):
121
+	def __init__(self, stream):
122
+		self.stream = stream
123
+
124
+	def dump(self, section, config):
125
+		self.stream.write("[%s]\n" % section)
126
+		for option in config.option_list():
127
+			self.stream.write("%s = %s\n" % (option, getattr(config, option)))
128
+
... ...
@@ -161,21 +161,66 @@ def cmd_object_del(args):
161 161
 	response = s3.object_delete(bucket, object)
162 162
 	output("Object %s deleted" % s3uri)
163 163
 
164
-commands = {
165
-	"mb" : ("Make bucket", "s3://BUCKET", cmd_bucket_create, 1),
166
-	"rb" : ("Remove bucket", "s3://BUCKET", cmd_bucket_delete, 1),
167
-	"ls" : ("List objects or buckets", "[s3://BUCKET[/PREFIX]]", cmd_ls, 0),
168
-	"la" : ("List all object in all buckets", "", cmd_buckets_list_all_all, 0),
169
-	"cp" : ("Copy files to / from S3 bucket", "SRC DST", cmd_cp, 2),
170
-	"put": ("Put file into bucket", "FILE [FILE...] s3://BUCKET[/PREFIX]", cmd_object_put, 2),
171
-	"get": ("Get file from bucket", "s3://BUCKET/OBJECT LOCAL_FILE", cmd_object_get, 1),
172
-	"del": ("Delete file from bucket", "s3://BUCKET/OBJECT", cmd_object_del, 1),
173
-	}
164
+def run_configure(config_file):
165
+	cfg = Config()
166
+	options = [
167
+		("access_key", "Access Key"),
168
+		("secret_key", "Secret Key"),
169
+		]
170
+	try:
171
+		while 1:
172
+			output("\nEnter new values or accept defaults in brackets with Enter.")
173
+			output("Refer to user manual for detailed description of all options.\n")
174
+			for option in options:
175
+				prompt = option[1]
176
+				try:
177
+					val = getattr(cfg, option[0])
178
+					if val not in (None, ""):
179
+						prompt += " [%s]" % val
180
+				except AttributeError:
181
+					pass
182
+
183
+				if len(option) >= 3:
184
+					output("%s" % option[2])
185
+
186
+				val = raw_input(prompt + ": ")
187
+				if val != "":
188
+					setattr(cfg, option[0], val)
189
+			output("\nNew settings:")
190
+			for option in options:
191
+				output("  %s: %s" % (option[1], getattr(cfg, option[0])))
192
+			val = raw_input("\nChange any setting? [y/N] ")
193
+			if not val.lower().startswith("y"):
194
+				break
195
+		f = open(config_file, "w")
196
+		cfg.dump_config(f)
197
+		f.close()
198
+		output("Configuration saved to '%s'" % config_file)
199
+
200
+	except (EOFError, KeyboardInterrupt):
201
+		output("\nConfiguration aborted. Changes were NOT saved.")
202
+		return
203
+	
204
+	except IOError, e:
205
+		error("Writing config file failed: %s: %s" % (config_file, e.strerror))
206
+		exit(1)
207
+
208
+commands = {}
209
+commands_list = [
210
+	{"cmd":"mb", "label":"Make bucket", "param":"s3://BUCKET", "func":cmd_bucket_create, "argc":1},
211
+	{"cmd":"rb", "label":"Remove bucket", "param":"s3://BUCKET", "func":cmd_bucket_delete, "argc":1},
212
+	{"cmd":"ls", "label":"List objects or buckets", "param":"[s3://BUCKET[/PREFIX]]", "func":cmd_ls, "argc":0},
213
+	{"cmd":"la", "label":"List all object in all buckets", "param":"", "func":cmd_buckets_list_all_all, "argc":0},
214
+	{"cmd":"cp", "label":"Copy files to / from S3 bucket", "param":"SRC DST", "func":cmd_cp, "argc":2},
215
+	{"cmd":"put", "label":"Put file into bucket", "param":"FILE [FILE...] s3://BUCKET[/PREFIX]", "func":cmd_object_put, "argc":2},
216
+	{"cmd":"get", "label":"Get file from bucket", "param":"s3://BUCKET/OBJECT LOCAL_FILE", "func":cmd_object_get, "argc":1},
217
+	{"cmd":"del", "label":"Delete file from bucket", "param":"s3://BUCKET/OBJECT", "func":cmd_object_del, "argc":1},
218
+	]
174 219
 
175 220
 def format_commands(progname):
176 221
 	help = "Commands:\n"
177
-	for cmd in commands:
178
-		help += "  %s\n      %s %s %s\n" % (commands[cmd][0], progname, cmd, commands[cmd][1])
222
+	for cmd in commands_list:
223
+		help += "  %s\n      %s %s %s\n" % (cmd["label"], progname, cmd["cmd"], cmd["param"])
179 224
 	return help
180 225
 
181 226
 class OptionMimeType(Option):
... ...
@@ -200,6 +245,11 @@ if __name__ == '__main__':
200 200
 		sys.stderr.write("ERROR: Python 2.5 or higher required, sorry.\n")
201 201
 		exit(1)
202 202
 
203
+	## Populate "commands" from "commands_list"
204
+	for cmd in commands_list:
205
+		if cmd.has_key("cmd"):
206
+			commands[cmd["cmd"]] = cmd
207
+
203 208
 	default_verbosity = Config().verbosity
204 209
 	optparser = OptionParser(option_class=OptionMimeType, formatter=MyHelpFormatter())
205 210
 	#optparser.disable_interspersed_args()
... ...
@@ -214,6 +264,8 @@ if __name__ == '__main__':
214 214
 	optparser.add_option("-P", "--acl-public", dest="acl_public", action="store_true", help="Store objects with ACL allowing read by anyone.")
215 215
 	optparser.add_option("-m", "--mime-type", dest="default_mime_type", type="mimetype", metavar="MIME/TYPE", help="Default MIME-type to be set for objects stored.")
216 216
 	optparser.add_option("-M", "--guess-mime-type", dest="guess_mime_type", action="store_true", help="Guess MIME-type of files by their extension. Falls back to default MIME-Type as specified by --mime-type option")
217
+	optparser.add_option(      "--dump-config", dest="dump_config", action="store_true", help="Dump current configuration after parsin config files and command line options and exit.")
218
+	optparser.add_option(      "--configure", dest="run_configure", action="store_true", help="Invoke interactive (re)configuration tool.")
217 219
 
218 220
 	optparser.set_usage(optparser.usage + " COMMAND [parameters]")
219 221
 	optparser.set_description('S3cmd is a tool to manage objects in '+
... ...
@@ -230,23 +282,40 @@ if __name__ == '__main__':
230 230
 	logging.basicConfig(level=options.verbosity, format='%(levelname)s: %(message)s')
231 231
 	
232 232
 	## Now finally parse the config file
233
-	Config(options.config)
233
+	try:
234
+		cfg = Config(options.config)
235
+	except IOError, e:
236
+		if options.run_configure:
237
+			cfg = Config()
238
+		else:
239
+			error("%s: %s"  % (options.config, e.strerror))
240
+			error("Configuration file not available.")
241
+			error("Consider using --configure parameter to create one.")
242
+			exit(1)
234 243
 
235 244
 	## And again some logging level adjustments
236 245
 	## according to configfile and command line parameters
237 246
 	if options.verbosity != default_verbosity:
238
-		Config().verbosity = options.verbosity
239
-	logging.root.setLevel(Config().verbosity)
247
+		cfg.verbosity = options.verbosity
248
+	logging.root.setLevel(cfg.verbosity)
240 249
 
241 250
 	## Update Config with other parameters
242
-	for parameter in (
243
-			"human_readable_sizes",
244
-			"force",
245
-			"show_uri",
246
-			"acl_public",):
247
-		if getattr(options, parameter) != None:
248
-			debug("Updating %s -> %s" % (parameter, getattr(options, parameter)))
249
-			setattr(Config, parameter, getattr(options, parameter))
251
+	for option in cfg.option_list():
252
+		try:
253
+			if getattr(options, option) != None:
254
+				debug("Updating %s -> %s" % (option, getattr(options, option)))
255
+				cfg.update_option(option, getattr(options, option))
256
+		except AttributeError:
257
+			## Some Config() options are not settable from command line
258
+			pass
259
+
260
+	if options.dump_config:
261
+		cfg.dump_config(sys.stdout)
262
+		exit(0)
263
+
264
+	if options.run_configure:
265
+		run_configure(options.config)
266
+		exit(0)
250 267
 
251 268
 	if len(args) < 1:
252 269
 		error("Missing command. Please run with --help for more information.")
... ...
@@ -254,16 +323,16 @@ if __name__ == '__main__':
254 254
 
255 255
 	command = args.pop(0)
256 256
 	try:
257
-		debug("Command: " + commands[command][0])
257
+		debug("Command: " + commands[command]["cmd"])
258 258
 		## We must do this lookup in extra step to 
259 259
 		## avoid catching all KeyError exceptions
260 260
 		## from inner functions.
261
-		cmd_func = commands[command][2]
261
+		cmd_func = commands[command]["func"]
262 262
 	except KeyError, e:
263 263
 		error("Invalid command: %s" % e)
264 264
 		exit(1)
265 265
 
266
-	if len(args) < commands[command][3]:
266
+	if len(args) < commands[command]["argc"]:
267 267
 		error("Not enough paramters for command '%s'" % command)
268 268
 		exit(1)
269 269