Remove lib package.
[pithos] / snf-pithos-tools / pithos / tools / sh.py
1 #!/usr/bin/env python
2
3 # Copyright 2011-2012 GRNET S.A. All rights reserved.
4
5 # Redistribution and use in source and binary forms, with or
6 # without modification, are permitted provided that the following
7 # conditions are met:
8
9 #   1. Redistributions of source code must retain the above
10 #      copyright notice, this list of conditions and the following
11 #      disclaimer.
12
13 #   2. Redistributions in binary form must reproduce the above
14 #      copyright notice, this list of conditions and the following
15 #      disclaimer in the documentation and/or other materials
16 #      provided with the distribution.
17
18 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 # POSSIBILITY OF SUCH DAMAGE.
30
31 # The views and conclusions contained in the software and
32 # documentation are those of the authors and should not be
33 # interpreted as representing official policies, either expressed
34 # or implied, of GRNET S.A.
35
36 from getpass import getuser
37 from optparse import OptionParser
38 from os import environ
39 from sys import argv, exit, stdin, stdout
40 from datetime import datetime
41
42 from pithos.tools.lib.client import Pithos_Client, Fault
43 from pithos.tools.lib.util import get_user, get_auth, get_url
44 from pithos.tools.lib.transfer import upload, download
45
46 import json
47 import logging
48 import types
49 import re
50 import time as _time
51 import os
52
53 _cli_commands = {}
54
55 def cli_command(*args):
56     def decorator(cls):
57         cls.commands = args
58         for name in args:
59             _cli_commands[name] = cls
60         return cls
61     return decorator
62
63 def class_for_cli_command(name):
64     return _cli_commands[name]
65
66 class Command(object):
67     syntax = ''
68     
69     def __init__(self, name, argv):
70         parser = OptionParser('%%prog %s [options] %s' % (name, self.syntax))
71         parser.add_option('--url', dest='url', metavar='URL',
72                           default=get_url(), help='server URL (currently: %s)' % get_url())
73         parser.add_option('--user', dest='user', metavar='USER',
74                           default=get_user(),
75                           help='account USER (currently: %s)' % get_user())
76         parser.add_option('--token', dest='token', metavar='TOKEN',
77                           default=get_auth(),
78                           help='account TOKEN (currently: %s)' % get_auth())
79         parser.add_option('-v', action='store_true', dest='verbose',
80                           default=False, help='verbose output')
81         parser.add_option('-d', action='store_true', dest='debug',
82                           default=False, help='debug output')
83         self.add_options(parser)
84         options, args = parser.parse_args(argv)
85         
86         # Add options to self
87         for opt in parser.option_list:
88             key = opt.dest
89             if key:
90                 val = getattr(options, key)
91                 setattr(self, key, val)
92         
93         self.client = Pithos_Client(self.url, self.token, self.user, self.verbose,
94                              self.debug)
95         
96         self.parser = parser
97         self.args = args
98     
99     def _build_args(self, attrs):
100         args = {}
101         for a in [a for a in attrs if getattr(self, a)]:
102             args[a] = getattr(self, a)
103         return args
104
105     def add_options(self, parser):
106         pass
107     
108     def execute(self, *args):
109         pass
110
111 @cli_command('list', 'ls')
112 class List(Command):
113     syntax = '[<container>[/<object>]]'
114     description = 'list containers or objects'
115     
116     def add_options(self, parser):
117         parser.add_option('-l', action='store_true', dest='detail',
118                           default=False, help='show detailed output')
119         parser.add_option('-n', action='store', type='int', dest='limit',
120                           default=10000, help='show limited output')
121         parser.add_option('--marker', action='store', type='str',
122                           dest='marker', default=None,
123                           help='show output greater then marker')
124         parser.add_option('--prefix', action='store', type='str',
125                           dest='prefix', default=None,
126                           help='show output starting with prefix')
127         parser.add_option('--delimiter', action='store', type='str',
128                           dest='delimiter', default=None,
129                           help='show output up to the delimiter')
130         parser.add_option('--path', action='store', type='str',
131                           dest='path', default=None,
132                           help='show output starting with prefix up to /')
133         parser.add_option('--meta', action='store', type='str',
134                           dest='meta', default=None,
135                           help='show output having the specified meta keys')
136         parser.add_option('--if-modified-since', action='store', type='str',
137                           dest='if_modified_since', default=None,
138                           help='show output if modified since then')
139         parser.add_option('--if-unmodified-since', action='store', type='str',
140                           dest='if_unmodified_since', default=None,
141                           help='show output if not modified since then')
142         parser.add_option('--until', action='store', dest='until',
143                           default=None, help='show metadata until that date')
144         parser.add_option('--format', action='store', dest='format',
145                           default='%d/%m/%Y', help='format to parse until date')
146     
147     def execute(self, container=None):
148         if container:
149             self.list_objects(container)
150         else:
151             self.list_containers()
152     
153     def list_containers(self):
154         attrs = ['limit', 'marker', 'if_modified_since',
155                  'if_unmodified_since']
156         args = self._build_args(attrs)
157         args['format'] = 'json' if self.detail else 'text'
158         
159         if getattr(self, 'until'):
160             t = _time.strptime(self.until, self.format)
161             args['until'] = int(_time.mktime(t))
162         
163         l = self.client.list_containers(**args)
164         print_list(l)
165     
166     def list_objects(self, container):
167         #prepate params
168         params = {}
169         attrs = ['limit', 'marker', 'prefix', 'delimiter', 'path',
170                  'meta', 'if_modified_since', 'if_unmodified_since']
171         args = self._build_args(attrs)
172         args['format'] = 'json' if self.detail else 'text'
173         
174         if self.until:
175             t = _time.strptime(self.until, self.format)
176             args['until'] = int(_time.mktime(t))
177         
178         container, sep, object = container.partition('/')
179         if object:
180             return
181         
182         detail = 'json'
183         #if request with meta quering disable trash filtering
184         show_trashed = True if self.meta else False
185         l = self.client.list_objects(container, **args)
186         print_list(l, detail=self.detail)
187
188 @cli_command('meta')
189 class Meta(Command):
190     syntax = '[<container>[/<object>]]'
191     description = 'get account/container/object metadata'
192     
193     def add_options(self, parser):
194         parser.add_option('-r', action='store_true', dest='restricted',
195                           default=False, help='show only user defined metadata')
196         parser.add_option('--until', action='store', dest='until',
197                           default=None, help='show metadata until that date')
198         parser.add_option('--format', action='store', dest='format',
199                           default='%d/%m/%Y', help='format to parse until date')
200         parser.add_option('--version', action='store', dest='version',
201                           default=None, help='show specific version \
202                                   (applies only for objects)')
203     
204     def execute(self, path=''):
205         container, sep, object = path.partition('/')
206         args = {'restricted': self.restricted}
207         if getattr(self, 'until'):
208             t = _time.strptime(self.until, self.format)
209             args['until'] = int(_time.mktime(t))
210         
211         if object:
212             meta = self.client.retrieve_object_metadata(container, object,
213                                                         self.restricted,
214                                                         self.version)
215         elif container:
216             meta = self.client.retrieve_container_metadata(container, **args)
217         else:
218             meta = self.client.retrieve_account_metadata(**args)
219         if meta == None:
220             print 'Entity does not exist'
221         else:
222             print_dict(meta, header=None)
223
224 @cli_command('create')
225 class CreateContainer(Command):
226     syntax = '<container> [key=val] [...]'
227     description = 'create a container'
228     policy={}
229     
230     def add_options(self, parser):
231         parser.add_option('--versioning', action='store', dest=policy['versioning'],
232                           default=None, help='set container versioning (auto/none)')
233         parser.add_option('--quota', action='store', dest=policy['quota'],
234                           default=None, help='set default container quota')
235     
236     def execute(self, container, *args):
237         meta = {}
238         for arg in args:
239             key, sep, val = arg.partition('=')
240             meta[key] = val
241         ret = self.client.create_container(container, meta=meta, policies=policy)
242         if not ret:
243             print 'Container already exists'
244
245 @cli_command('delete', 'rm')
246 class Delete(Command):
247     syntax = '<container>[/<object>]'
248     description = 'delete a container or an object'
249     
250     def add_options(self, parser):
251         parser.add_option('--until', action='store', dest='until',
252                           default=None, help='remove history until that date')
253         parser.add_option('--format', action='store', dest='format',
254                           default='%d/%m/%Y', help='format to parse until date')
255     
256     def execute(self, path):
257         container, sep, object = path.partition('/')
258         until = None
259         if getattr(self, 'until'):
260             t = _time.strptime(self.until, self.format)
261             until = int(_time.mktime(t))
262         
263         if object:
264             self.client.delete_object(container, object, until)
265         else:
266             self.client.delete_container(container, until)
267
268 @cli_command('get')
269 class GetObject(Command):
270     syntax = '<container>/<object>'
271     description = 'get the data of an object'
272     
273     def add_options(self, parser):
274         parser.add_option('-l', action='store_true', dest='detail',
275                           default=False, help='show detailed output')
276         parser.add_option('--range', action='store', dest='range',
277                           default=None, help='show range of data')
278         parser.add_option('--if-range', action='store', dest='if_range',
279                           default=None, help='show range of data')
280         parser.add_option('--if-match', action='store', dest='if_match',
281                           default=None, help='show output if ETags match')
282         parser.add_option('--if-none-match', action='store',
283                           dest='if_none_match', default=None,
284                           help='show output if ETags don\'t match')
285         parser.add_option('--if-modified-since', action='store', type='str',
286                           dest='if_modified_since', default=None,
287                           help='show output if modified since then')
288         parser.add_option('--if-unmodified-since', action='store', type='str',
289                           dest='if_unmodified_since', default=None,
290                           help='show output if not modified since then')
291         parser.add_option('-o', action='store', type='str',
292                           dest='file', default=None,
293                           help='save output in file')
294         parser.add_option('--version', action='store', type='str',
295                           dest='version', default=None,
296                           help='get the specific \
297                                version')
298         parser.add_option('--versionlist', action='store_true',
299                           dest='versionlist', default=False,
300                           help='get the full object version list')
301         parser.add_option('--hashmap', action='store_true',
302                           dest='hashmap', default=False,
303                           help='get the object hashmap instead')
304     
305     def execute(self, path):
306         attrs = ['if_match', 'if_none_match', 'if_modified_since',
307                  'if_unmodified_since', 'hashmap']
308         args = self._build_args(attrs)
309         args['format'] = 'json' if self.detail else 'text'
310         if self.range:
311             args['range'] = 'bytes=%s' % self.range
312         if getattr(self, 'if_range'):
313             args['if-range'] = 'If-Range:%s' % getattr(self, 'if_range')
314         
315         container, sep, object = path.partition('/')
316         data = None
317         if self.versionlist:
318             if 'detail' in args.keys():
319                 args.pop('detail')
320             args.pop('format')
321             self.detail = True
322             data = self.client.retrieve_object_versionlist(container, object, **args)
323         elif self.version:
324             data = self.client.retrieve_object_version(container, object,
325                                                        self.version, **args)
326         elif self.hashmap:
327             if 'detail' in args.keys():
328                 args.pop('detail')
329             args.pop('format')
330             self.detail = True
331             data = self.client.retrieve_object_hashmap(container, object, **args)
332         else:
333             data = self.client.retrieve_object(container, object, **args)    
334         
335         f = open(self.file, 'w') if self.file else stdout
336         if self.detail or type(data) == types.DictionaryType:
337             if self.versionlist:
338                 print_versions(data, f=f)
339             else:
340                 print_dict(data, f=f)
341         else:
342             f.write(data)
343         f.close()
344
345 @cli_command('mkdir')
346 class PutMarker(Command):
347     syntax = '<container>/<directory marker>'
348     description = 'create a directory marker'
349     
350     def execute(self, path):
351         container, sep, object = path.partition('/')
352         self.client.create_directory_marker(container, object)
353
354 @cli_command('put')
355 class PutObject(Command):
356     syntax = '<container>/<object> [key=val] [...]'
357     description = 'create/override object'
358     
359     def add_options(self, parser):
360         parser.add_option('--use_hashes', action='store_true', dest='use_hashes',
361                           default=False, help='provide hashmap instead of data')
362         parser.add_option('--chunked', action='store_true', dest='chunked',
363                           default=False, help='set chunked transfer mode')
364         parser.add_option('--etag', action='store', dest='etag',
365                           default=None, help='check written data')
366         parser.add_option('--content-encoding', action='store',
367                           dest='content_encoding', default=None,
368                           help='provide the object MIME content type')
369         parser.add_option('--content-disposition', action='store', type='str',
370                           dest='content_disposition', default=None,
371                           help='provide the presentation style of the object')
372         #parser.add_option('-S', action='store',
373         #                  dest='segment_size', default=False,
374         #                  help='use for large file support')
375         parser.add_option('--manifest', action='store',
376                           dest='x_object_manifest', default=None,
377                           help='provide object parts prefix in <container>/<object> form')
378         parser.add_option('--content-type', action='store',
379                           dest='content_type', default=None,
380                           help='create object with specific content type')
381         parser.add_option('--sharing', action='store',
382                           dest='x_object_sharing', default=None,
383                           help='define sharing object policy')
384         parser.add_option('-f', action='store',
385                           dest='srcpath', default=None,
386                           help='file descriptor to read from (pass - for standard input)')
387         parser.add_option('--public', action='store_true',
388                           dest='x_object_public', default=False,
389                           help='make object publicly accessible')
390     
391     def execute(self, path, *args):
392         if path.find('=') != -1:
393             raise Fault('Missing path argument')
394         
395         #prepare user defined meta
396         meta = {}
397         for arg in args:
398             key, sep, val = arg.partition('=')
399             meta[key] = val
400         
401         attrs = ['etag', 'content_encoding', 'content_disposition',
402                  'content_type', 'x_object_sharing', 'x_object_public']
403         args = self._build_args(attrs)
404         
405         container, sep, object = path.partition('/')
406         
407         f = None
408         if self.srcpath:
409             f = open(self.srcpath) if self.srcpath != '-' else stdin
410         
411         if self.use_hashes and not f:
412             raise Fault('Illegal option combination')
413         
414         if self.chunked:
415             self.client.create_object_using_chunks(container, object, f,
416                                                     meta=meta, **args)
417         elif self.use_hashes:
418             data = f.read()
419             if data is object:
420                 hashmap = json.loads()
421                 self.client.create_object_by_hashmap(container, object, hashmap,
422                                                  meta=meta, **args)
423             else:
424                 print "Expected object"
425         elif self.x_object_manifest:
426             self.client.create_manifestation(container, object, self.x_object_manifest)
427         elif not f:
428             self.client.create_zero_length_object(container, object, meta=meta, **args)
429         else:
430             self.client.create_object(container, object, f, meta=meta, **args)
431         if f:
432             f.close()
433
434 @cli_command('copy', 'cp')
435 class CopyObject(Command):
436     syntax = '<src container>/<src object> [<dst container>/]<dst object> [key=val] [...]'
437     description = 'copy an object to a different location'
438     
439     def add_options(self, parser):
440         parser.add_option('--version', action='store',
441                           dest='version', default=False,
442                           help='copy specific version')
443         parser.add_option('--public', action='store_true',
444                           dest='public', default=False,
445                           help='make object publicly accessible')
446         parser.add_option('--content-type', action='store',
447                           dest='content_type', default=None,
448                           help='change object\'s content type')
449     
450     def execute(self, src, dst, *args):
451         src_container, sep, src_object = src.partition('/')
452         dst_container, sep, dst_object = dst.partition('/')
453         
454         #prepare user defined meta
455         meta = {}
456         for arg in args:
457             key, sep, val = arg.partition('=')
458             meta[key] = val
459         
460         if not sep:
461             dst_container = src_container
462             dst_object = dst
463         
464         args = {'content_type':self.content_type} if self.content_type else {}
465         self.client.copy_object(src_container, src_object, dst_container,
466                                 dst_object, meta, self.public, self.version,
467                                 **args)
468
469 @cli_command('set')
470 class SetMeta(Command):
471     syntax = '[<container>[/<object>]] key=val [key=val] [...]'
472     description = 'set account/container/object metadata'
473     
474     def execute(self, path, *args):
475         #in case of account fix the args
476         if path.find('=') != -1:
477             args = list(args)
478             args.append(path)
479             args = tuple(args)
480             path = ''
481         meta = {}
482         for arg in args:
483             key, sep, val = arg.partition('=')
484             meta[key.strip()] = val.strip()
485         container, sep, object = path.partition('/')
486         if object:
487             self.client.update_object_metadata(container, object, **meta)
488         elif container:
489             self.client.update_container_metadata(container, **meta)
490         else:
491             self.client.update_account_metadata(**meta)
492
493 @cli_command('update')
494 class UpdateObject(Command):
495     syntax = '<container>/<object> path [key=val] [...]'
496     description = 'update object metadata/data (default mode: append)'
497     
498     def add_options(self, parser):
499         parser.add_option('-a', action='store_true', dest='append',
500                           default=True, help='append data')
501         parser.add_option('--offset', action='store',
502                           dest='offset',
503                           default=None, help='starting offest to be updated')
504         parser.add_option('--range', action='store', dest='content_range',
505                           default=None, help='range of data to be updated')
506         parser.add_option('--chunked', action='store_true', dest='chunked',
507                           default=False, help='set chunked transfer mode')
508         parser.add_option('--content-encoding', action='store',
509                           dest='content_encoding', default=None,
510                           help='provide the object MIME content type')
511         parser.add_option('--content-disposition', action='store', type='str',
512                           dest='content_disposition', default=None,
513                           help='provide the presentation style of the object')
514         parser.add_option('--manifest', action='store', type='str',
515                           dest='x_object_manifest', default=None,
516                           help='use for large file support')        
517         parser.add_option('--sharing', action='store',
518                           dest='x_object_sharing', default=None,
519                           help='define sharing object policy')
520         parser.add_option('--nosharing', action='store_true',
521                           dest='no_sharing', default=None,
522                           help='clear object sharing policy')
523         parser.add_option('-f', action='store',
524                           dest='srcpath', default=None,
525                           help='file descriptor to read from: pass - for standard input')
526         parser.add_option('--public', action='store_true',
527                           dest='x_object_public', default=False,
528                           help='make object publicly accessible')
529         parser.add_option('--replace', action='store_true',
530                           dest='replace', default=False,
531                           help='override metadata')
532     
533     def execute(self, path, *args):
534         if path.find('=') != -1:
535             raise Fault('Missing path argument')
536         
537         #prepare user defined meta
538         meta = {}
539         for arg in args:
540             key, sep, val = arg.partition('=')
541             meta[key] = val
542         
543         
544         attrs = ['content_encoding', 'content_disposition', 'x_object_sharing',
545                  'x_object_public', 'x_object_manifest', 'replace', 'offset',
546                  'content_range']
547         args = self._build_args(attrs)
548         
549         if self.no_sharing:
550             args['x_object_sharing'] = ''
551         
552         container, sep, object = path.partition('/')
553         
554         f = None
555         if self.srcpath:
556             f = open(self.srcpath) if self.srcpath != '-' else stdin
557         
558         if self.chunked:
559             self.client.update_object_using_chunks(container, object, f,
560                                                     meta=meta, **args)
561         else:
562             self.client.update_object(container, object, f, meta=meta, **args)
563         if f:
564             f.close()
565
566 @cli_command('move', 'mv')
567 class MoveObject(Command):
568     syntax = '<src container>/<src object> [<dst container>/]<dst object>'
569     description = 'move an object to a different location'
570     
571     def add_options(self, parser):
572         parser.add_option('--public', action='store_true',
573                           dest='public', default=False,
574                           help='make object publicly accessible')
575         parser.add_option('--content-type', action='store',
576                           dest='content_type', default=None,
577                           help='change object\'s content type')
578     
579     def execute(self, src, dst, *args):
580         src_container, sep, src_object = src.partition('/')
581         dst_container, sep, dst_object = dst.partition('/')
582         if not sep:
583             dst_container = src_container
584             dst_object = dst
585         
586         #prepare user defined meta
587         meta = {}
588         for arg in args:
589             key, sep, val = arg.partition('=')
590             meta[key] = val
591         
592         args = {'content_type':self.content_type} if self.content_type else {}
593         self.client.move_object(src_container, src_object, dst_container,
594                                 dst_object, meta, self.public, **args)
595
596 @cli_command('unset')
597 class UnsetObject(Command):
598     syntax = '<container>/[<object>] key [key] [...]'
599     description = 'delete metadata info'
600     
601     def execute(self, path, *args):
602         #in case of account fix the args
603         if len(args) == 0:
604             args = list(args)
605             args.append(path)
606             args = tuple(args)
607             path = ''
608         meta = []
609         for key in args:
610             meta.append(key)
611         container, sep, object = path.partition('/')
612         if object:
613             self.client.delete_object_metadata(container, object, meta)
614         elif container:
615             self.client.delete_container_metadata(container, meta)
616         else:
617             self.client.delete_account_metadata(meta)
618
619 @cli_command('group')
620 class CreateGroup(Command):
621     syntax = 'key=val [key=val] [...]'
622     description = 'create account groups'
623     
624     def execute(self, *args):
625         groups = {}
626         for arg in args:
627             key, sep, val = arg.partition('=')
628             groups[key] = val
629         self.client.set_account_groups(**groups)
630
631 @cli_command('ungroup')
632 class DeleteGroup(Command):
633     syntax = 'key [key] [...]'
634     description = 'delete account groups'
635     
636     def execute(self, *args):
637         groups = []
638         for arg in args:
639             groups.append(arg)
640         self.client.unset_account_groups(groups)
641
642 @cli_command('policy')
643 class SetPolicy(Command):
644     syntax = 'container key=val [key=val] [...]'
645     description = 'set container policies'
646     
647     def execute(self, path, *args):
648         if path.find('=') != -1:
649             raise Fault('Missing container argument')
650         
651         container, sep, object = path.partition('/')
652         
653         if object:
654             raise Fault('Only containers have policies')
655         
656         policies = {}
657         for arg in args:
658             key, sep, val = arg.partition('=')
659             policies[key] = val
660         
661         self.client.set_container_policies(container, **policies)
662
663 @cli_command('publish')
664 class PublishObject(Command):
665     syntax = '<container>/<object>'
666     description = 'publish an object'
667     
668     def execute(self, src):
669         src_container, sep, src_object = src.partition('/')
670         
671         self.client.publish_object(src_container, src_object)
672
673 @cli_command('unpublish')
674 class UnpublishObject(Command):
675     syntax = '<container>/<object>'
676     description = 'unpublish an object'
677     
678     def execute(self, src):
679         src_container, sep, src_object = src.partition('/')
680         
681         self.client.unpublish_object(src_container, src_object)
682
683 @cli_command('sharing')
684 class SharingObject(Command):
685     syntax = 'list users sharing objects with the user'
686     description = 'list user accounts sharing objects with the user'
687     
688     def add_options(self, parser):
689         parser.add_option('-l', action='store_true', dest='detail',
690                           default=False, help='show detailed output')
691         parser.add_option('-n', action='store', type='int', dest='limit',
692                           default=10000, help='show limited output')
693         parser.add_option('--marker', action='store', type='str',
694                           dest='marker', default=None,
695                           help='show output greater then marker')
696         
697     
698     def execute(self):
699         attrs = ['limit', 'marker']
700         args = self._build_args(attrs)
701         args['format'] = 'json' if self.detail else 'text'
702         
703         print_list(self.client.list_shared_by_others(**args))
704
705 @cli_command('send')
706 class Send(Command):
707     syntax = '<file> <container>[/<prefix>]'
708     description = 'upload file to container (using prefix)'
709     
710     def execute(self, file, path):
711         container, sep, prefix = path.partition('/')
712         upload(self.client, file, container, prefix)
713
714 @cli_command('receive')
715 class Receive(Command):
716     syntax = '<container>/<object> <file>'
717     description = 'download object to file'
718     
719     def execute(self, path, file):
720         container, sep, object = path.partition('/')
721         download(self.client, container, object, file)
722
723 def print_usage():
724     cmd = Command('', [])
725     parser = cmd.parser
726     parser.usage = '%prog <command> [options]'
727     parser.print_help()
728     
729     commands = []
730     for cls in set(_cli_commands.values()):
731         name = ', '.join(cls.commands)
732         description = getattr(cls, 'description', '')
733         commands.append('  %s %s' % (name.ljust(12), description))
734     print '\nCommands:\n' + '\n'.join(sorted(commands))
735
736 def print_dict(d, header='name', f=stdout, detail=True):
737     header = header if header in d else 'subdir'
738     if header and header in d:
739         f.write('%s\n' %d.pop(header).encode('utf8'))
740     if detail:
741         patterns = ['^x_(account|container|object)_meta_(\w+)$']
742         patterns.append(patterns[0].replace('_', '-'))
743         for key, val in sorted(d.items()):
744             f.write('%s: %s\n' % (key.rjust(30), val))
745
746 def print_list(l, verbose=False, f=stdout, detail=True):
747     for elem in l:
748         #if it's empty string continue
749         if not elem:
750             continue
751         if type(elem) == types.DictionaryType:
752             print_dict(elem, f=f, detail=detail)
753         elif type(elem) == types.StringType:
754             if not verbose:
755                 elem = elem.split('Traceback')[0]
756             f.write('%s\n' % elem)
757         else:
758             f.write('%s\n' % elem)
759
760 def print_versions(data, f=stdout):
761     if 'versions' not in data:
762         f.write('%s\n' %data)
763         return
764     f.write('versions:\n')
765     for id, t in data['versions']:
766         f.write('%s @ %s\n' % (str(id).rjust(30), datetime.fromtimestamp(float(t))))
767
768
769 def main():
770     try:
771         name = argv[1]
772         cls = class_for_cli_command(name)
773     except (IndexError, KeyError):
774         print_usage()
775         exit(1)
776     
777     cmd = cls(name, argv[2:])
778     
779     try:
780         cmd.execute(*cmd.args)
781     except TypeError, e:
782         cmd.parser.print_help()
783         exit(1)
784     except Fault, f:
785         status = '%s ' % f.status if f.status else ''
786         print '%s%s' % (status, f.data)
787
788
789 if __name__ == '__main__':
790     main()