Statistics
| Branch: | Tag: | Revision:

root / tools / store @ f89e3cf4

History | View | Annotate | Download (28.4 kB)

1
#!/usr/bin/env python
2

    
3
# Copyright 2011 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 pithos.lib.client import Client, Fault
41
from datetime import datetime
42

    
43
import json
44
import logging
45
import types
46
import re
47
import time as _time
48
import os
49

    
50
DEFAULT_HOST = 'pithos.dev.grnet.gr'
51
DEFAULT_API = 'v1'
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('--host', dest='host', metavar='HOST',
72
                          default=DEFAULT_HOST, help='use server HOST')
73
        parser.add_option('--user', dest='user', metavar='USERNAME',
74
                          default=_get_user(),
75
                          help='use account USERNAME')
76
        parser.add_option('--token', dest='token', metavar='AUTH',
77
                          default=_get_auth(),
78
                          help='use account AUTH')
79
        parser.add_option('--api', dest='api', metavar='API',
80
                          default=DEFAULT_API, help='use api API')
81
        parser.add_option('-v', action='store_true', dest='verbose',
82
                          default=False, help='use verbose output')
83
        parser.add_option('-d', action='store_true', dest='debug',
84
                          default=False, help='use debug output')
85
        self.add_options(parser)
86
        options, args = parser.parse_args(argv)
87
        
88
        # Add options to self
89
        for opt in parser.option_list:
90
            key = opt.dest
91
            if key:
92
                val = getattr(options, key)
93
                setattr(self, key, val)
94
        
95
        self.client = Client(self.host, self.token, self.user, self.api, self.verbose,
96
                             self.debug)
97
        
98
        self.parser = parser
99
        self.args = args
100
        
101
    def add_options(self, parser):
102
        pass
103
    
104
    def execute(self, *args):
105
        pass
106

    
107
@cli_command('list', 'ls')
108
class List(Command):
109
    syntax = '[<container>[/<object>]]'
110
    description = 'list containers or objects'
111
    
112
    def add_options(self, parser):
113
        parser.add_option('-l', action='store_true', dest='detail',
114
                          default=False, help='show detailed output')
115
        parser.add_option('-n', action='store', type='int', dest='limit',
116
                          default=1000, help='show limited output')
117
        parser.add_option('--marker', action='store', type='str',
118
                          dest='marker', default=None,
119
                          help='show output greater then marker')
120
        parser.add_option('--prefix', action='store', type='str',
121
                          dest='prefix', default=None,
122
                          help='show output starting with prefix')
123
        parser.add_option('--delimiter', action='store', type='str',
124
                          dest='delimiter', default=None,
125
                          help='show output up to the delimiter')
126
        parser.add_option('--path', action='store', type='str',
127
                          dest='path', default=None,
128
                          help='show output starting with prefix up to /')
129
        parser.add_option('--meta', action='store', type='str',
130
                          dest='meta', default=None,
131
                          help='show output having the specified meta keys')
132
        parser.add_option('--if-modified-since', action='store', type='str',
133
                          dest='if_modified_since', default=None,
134
                          help='show output if modified since then')
135
        parser.add_option('--if-unmodified-since', action='store', type='str',
136
                          dest='if_unmodified_since', default=None,
137
                          help='show output if not modified since then')
138
        parser.add_option('--until', action='store', dest='until',
139
                          default=False, help='show metadata until that date')
140
        parser.add_option('--format', action='store', dest='format',
141
                          default='%d/%m/%Y', help='format to parse until date')
142
    
143
    def execute(self, container=None):
144
        if container:
145
            self.list_objects(container)
146
        else:
147
            self.list_containers()
148
    
149
    def list_containers(self):
150
        params = {'limit':self.limit, 'marker':self.marker}
151
        headers = {'IF_MODIFIED_SINCE':self.if_modified_since,
152
                   'IF_UNMODIFIED_SINCE':self.if_unmodified_since}
153
        
154
        if self.until:
155
            t = _time.strptime(self.until, self.format)
156
            params['until'] = int(_time.mktime(t))
157
        
158
        l = self.client.list_containers(self.detail, params, headers)
159
        print_list(l)
160
    
161
    def list_objects(self, container):
162
        #prepate params
163
        params = {}
164
        attrs = ['limit', 'marker', 'prefix', 'delimiter', 'path', 'meta']
165
        for a in [a for a in attrs if getattr(self, a)]:
166
            params[a] = getattr(self, a)
167
        
168
        if self.until:
169
            t = _time.strptime(self.until, self.format)
170
            params['until'] = int(_time.mktime(t))
171
        
172
        headers = {'IF_MODIFIED_SINCE':self.if_modified_since,
173
                   'IF_UNMODIFIED_SINCE':self.if_unmodified_since}
174
        container, sep, object = container.partition('/')
175
        if object:
176
            return
177
        
178
        detail = 'json'
179
        #if request with meta quering disable trash filtering
180
        show_trashed = True if self.meta else False
181
        l = self.client.list_objects(container, detail, headers,
182
                                     include_trashed = show_trashed, **params)
183
        print_list(l, detail=self.detail)
184

    
185
@cli_command('meta')
186
class Meta(Command):
187
    syntax = '[<container>[/<object>]]'
188
    description = 'get the metadata of an account, a container or an object'
189
    
190
    def add_options(self, parser):
191
        parser.add_option('-r', action='store_true', dest='restricted',
192
                          default=False, help='show only user defined metadata')
193
        parser.add_option('--until', action='store', dest='until',
194
                          default=False, help='show metadata until that date')
195
        parser.add_option('--format', action='store', dest='format',
196
                          default='%d/%m/%Y', help='format to parse until date')
197
        parser.add_option('--version', action='store', dest='version',
198
                          default=None, help='show specific version \
199
                                  (applies only for objects)')
200
    
201
    def execute(self, path=''):
202
        container, sep, object = path.partition('/')
203
        if self.until:
204
            t = _time.strptime(self.until, self.format)
205
            self.until = int(_time.mktime(t))
206
        if object:
207
            meta = self.client.retrieve_object_metadata(container, object,
208
                                                        self.restricted,
209
                                                        self.version)
210
        elif container:
211
            meta = self.client.retrieve_container_metadata(container,
212
                                                           self.restricted,
213
                                                           self.until)
214
        else:
215
            meta = self.client.account_metadata(self.restricted, self.until)
216
        if meta == None:
217
            print 'Entity does not exist'
218
        else:
219
            print_dict(meta, header=None)
220

    
221
@cli_command('create')
222
class CreateContainer(Command):
223
    syntax = '<container> [key=val] [...]'
224
    description = 'create a container'
225
    
226
    def execute(self, container, *args):
227
        headers = {}
228
        meta = {}
229
        for arg in args:
230
            key, sep, val = arg.partition('=')
231
            meta[key] = val
232
        ret = self.client.create_container(container, headers, **meta)
233
        if not ret:
234
            print 'Container already exists'
235

    
236
@cli_command('delete', 'rm')
237
class Delete(Command):
238
    syntax = '<container>[/<object>]'
239
    description = 'delete a container or an object'
240
    
241
    def execute(self, path):
242
        container, sep, object = path.partition('/')
243
        if object:
244
            self.client.delete_object(container, object)
245
        else:
246
            self.client.delete_container(container)
247

    
248
@cli_command('get')
249
class GetObject(Command):
250
    syntax = '<container>/<object>'
251
    description = 'get the data of an object'
252
    
253
    def add_options(self, parser):
254
        parser.add_option('-l', action='store_true', dest='detail',
255
                          default=False, help='show detailed output')
256
        parser.add_option('--range', action='store', dest='range',
257
                          default=None, help='show range of data')
258
        parser.add_option('--if-match', action='store', dest='if-match',
259
                          default=None, help='show output if ETags match')
260
        parser.add_option('--if-none-match', action='store',
261
                          dest='if-none-match', default=None,
262
                          help='show output if ETags don\'t match')
263
        parser.add_option('--if-modified-since', action='store', type='str',
264
                          dest='if-modified-since', default=None,
265
                          help='show output if modified since then')
266
        parser.add_option('--if-unmodified-since', action='store', type='str',
267
                          dest='if-unmodified-since', default=None,
268
                          help='show output if not modified since then')
269
        parser.add_option('-o', action='store', type='str',
270
                          dest='file', default=None,
271
                          help='save output in file')
272
        parser.add_option('--version', action='store', type='str',
273
                          dest='version', default=None,
274
                          help='get the specific \
275
                               version')
276
        parser.add_option('--versionlist', action='store_true',
277
                          dest='versionlist', default=False,
278
                          help='get the full object version list')
279
    
280
    def execute(self, path):
281
        headers = {}
282
        if self.range:
283
            headers['RANGE'] = 'bytes=%s' %self.range
284
        attrs = ['if-match', 'if-none-match', 'if-modified-since',
285
                 'if-unmodified-since']
286
        attrs = [a for a in attrs if getattr(self, a)]
287
        for a in attrs:
288
            headers[a.replace('-', '_').upper()] = getattr(self, a)
289
        container, sep, object = path.partition('/')
290
        if self.versionlist:
291
            self.version = 'list'
292
            self.detail = True
293
        data = self.client.retrieve_object(container, object, self.detail,
294
                                          headers, self.version)
295
        f = self.file and open(self.file, 'w') or stdout
296
        if self.detail:
297
            data = json.loads(data)
298
            if self.versionlist:
299
                print_versions(data, f=f)
300
            else:
301
                print_dict(data, f=f)
302
        else:
303
            f.write(data)
304
        f.close()
305

    
306
@cli_command('mkdir')
307
class PutMarker(Command):
308
    syntax = '<container>/<directory marker>'
309
    description = 'create a directory marker'
310
    
311
    def execute(self, path):
312
        container, sep, object = path.partition('/')
313
        self.client.create_directory_marker(container, object)
314

    
315
@cli_command('put')
316
class PutObject(Command):
317
    syntax = '<container>/<object> <path> [key=val] [...]'
318
    description = 'create/override object'
319
    
320
    def add_options(self, parser):
321
        parser.add_option('--use_hashes', action='store_true', dest='use_hashes',
322
                          default=False, help='provide hashmap instead of data')
323
        parser.add_option('--chunked', action='store_true', dest='chunked',
324
                          default=False, help='set chunked transfer mode')
325
        parser.add_option('--etag', action='store', dest='etag',
326
                          default=None, help='check written data')
327
        parser.add_option('--content-encoding', action='store',
328
                          dest='content-encoding', default=None,
329
                          help='provide the object MIME content type')
330
        parser.add_option('--content-disposition', action='store', type='str',
331
                          dest='content-disposition', default=None,
332
                          help='provide the presentation style of the object')
333
        parser.add_option('-S', action='store',
334
                          dest='segment-size', default=False,
335
                          help='use for large file support')
336
        parser.add_option('--manifest', action='store_true',
337
                          dest='manifest', default=None,
338
                          help='upload a manifestation file')
339
        parser.add_option('--type', action='store',
340
                          dest='content-type', default=False,
341
                          help='create object with specific content type')
342
        parser.add_option('--sharing', action='store',
343
                          dest='sharing', default=None,
344
                          help='define sharing object policy')
345
        parser.add_option('-f', action='store',
346
                          dest='srcpath', default=None,
347
                          help='file descriptor to read from (pass - for standard input)')
348
        parser.add_option('--public', action='store',
349
                          dest='public', default=None,
350
                          help='make object publicly accessible (\'True\'/\'False\')')
351
    
352
    def execute(self, path, *args):
353
        if path.find('=') != -1:
354
            raise Fault('Missing path argument')
355
        
356
        #prepare user defined meta
357
        meta = {}
358
        for arg in args:
359
            key, sep, val = arg.partition('=')
360
            meta[key] = val
361
        
362
        headers = {}
363
        manifest = getattr(self, 'manifest')
364
        if manifest:
365
            # if it's manifestation file
366
            # send zero-byte data with X-Object-Manifest header
367
            self.touch = True
368
            headers['X_OBJECT_MANIFEST'] = manifest
369
        if self.sharing:
370
            headers['X_OBJECT_SHARING'] = self.sharing
371
        
372
        attrs = ['etag', 'content-encoding', 'content-disposition',
373
                 'content-type']
374
        attrs = [a for a in attrs if getattr(self, a)]
375
        for a in attrs:
376
            headers[a.replace('-', '_').upper()] = getattr(self, a)
377
        
378
        container, sep, object = path.partition('/')
379
        
380
        f = None
381
        if self.srcpath:
382
            f = open(self.srcpath) if self.srcpath != '-' else stdin
383
        
384
        if self.use_hashes and not f:
385
            raise Fault('Illegal option combination')
386
        if self.public not in ['True', 'False', None]:
387
            raise Fault('Not acceptable value for public')
388
        public = eval(self.public) if self.public else None
389
        self.client.create_object(container, object, f, chunked=self.chunked,
390
                                  headers=headers, use_hashes=self.use_hashes,
391
                                  public=public, **meta)
392
        if f:
393
            f.close()
394

    
395
@cli_command('copy', 'cp')
396
class CopyObject(Command):
397
    syntax = '<src container>/<src object> [<dst container>/]<dst object>'
398
    description = 'copy an object to a different location'
399
    
400
    def add_options(self, parser):
401
        parser.add_option('--version', action='store',
402
                          dest='version', default=False,
403
                          help='copy specific version')
404
        parser.add_option('--public', action='store',
405
                          dest='public', default=None,
406
                          help='publish/unpublish object (\'True\'/\'False\')')
407
    
408
    def execute(self, src, dst):
409
        src_container, sep, src_object = src.partition('/')
410
        dst_container, sep, dst_object = dst.partition('/')
411
        if not sep:
412
            dst_container = src_container
413
            dst_object = dst
414
        version = getattr(self, 'version')
415
        if version:
416
            headers = {}
417
            headers['X_SOURCE_VERSION'] = version
418
        if self.public and self.nopublic:
419
            raise Fault('Conflicting options')
420
        if self.public not in ['True', 'False', None]:
421
            raise Fault('Not acceptable value for public')
422
        public = eval(self.public) if self.public else None
423
        self.client.copy_object(src_container, src_object, dst_container,
424
                                dst_object, public, headers)
425

    
426
@cli_command('set')
427
class SetMeta(Command):
428
    syntax = '[<container>[/<object>]] key=val [key=val] [...]'
429
    description = 'set metadata'
430
    
431
    def execute(self, path, *args):
432
        #in case of account fix the args
433
        if path.find('=') != -1:
434
            args = list(args)
435
            args.append(path)
436
            args = tuple(args)
437
            path = ''
438
        meta = {}
439
        for arg in args:
440
            key, sep, val = arg.partition('=')
441
            meta[key.strip()] = val.strip()
442
        container, sep, object = path.partition('/')
443
        if object:
444
            self.client.update_object_metadata(container, object, **meta)
445
        elif container:
446
            self.client.update_container_metadata(container, **meta)
447
        else:
448
            self.client.update_account_metadata(**meta)
449

    
450
@cli_command('update')
451
class UpdateObject(Command):
452
    syntax = '<container>/<object> path [key=val] [...]'
453
    description = 'update object metadata/data (default mode: append)'
454
    
455
    def add_options(self, parser):
456
        parser.add_option('-a', action='store_true', dest='append',
457
                          default=True, help='append data')
458
        parser.add_option('--offset', action='store',
459
                          dest='offset',
460
                          default=None, help='starting offest to be updated')
461
        parser.add_option('--range', action='store', dest='content-range',
462
                          default=None, help='range of data to be updated')
463
        parser.add_option('--chunked', action='store_true', dest='chunked',
464
                          default=False, help='set chunked transfer mode')
465
        parser.add_option('--content-encoding', action='store',
466
                          dest='content-encoding', default=None,
467
                          help='provide the object MIME content type')
468
        parser.add_option('--content-disposition', action='store', type='str',
469
                          dest='content-disposition', default=None,
470
                          help='provide the presentation style of the object')
471
        parser.add_option('--manifest', action='store', type='str',
472
                          dest='manifest', default=None,
473
                          help='use for large file support')        
474
        parser.add_option('--sharing', action='store',
475
                          dest='sharing', default=None,
476
                          help='define sharing object policy')
477
        parser.add_option('--nosharing', action='store_true',
478
                          dest='no_sharing', default=None,
479
                          help='clear object sharing policy')
480
        parser.add_option('-f', action='store',
481
                          dest='srcpath', default=None,
482
                          help='file descriptor to read from: pass - for standard input')
483
        parser.add_option('--public', action='store',
484
                          dest='public', default=None,
485
                          help='publish/unpublish object (\'True\'/\'False\')')
486
    
487
    def execute(self, path, *args):
488
        if path.find('=') != -1:
489
            raise Fault('Missing path argument')
490
        
491
        headers = {}
492
        if self.manifest:
493
            headers['X_OBJECT_MANIFEST'] = self.manifest
494
        if self.sharing:
495
            headers['X_OBJECT_SHARING'] = self.sharing
496
        
497
        if self.no_sharing:
498
            headers['X_OBJECT_SHARING'] = ''
499
        
500
        attrs = ['content-encoding', 'content-disposition']
501
        attrs = [a for a in attrs if getattr(self, a)]
502
        for a in attrs:
503
            headers[a.replace('-', '_').upper()] = getattr(self, a)
504
        
505
        #prepare user defined meta
506
        meta = {}
507
        for arg in args:
508
            key, sep, val = arg.partition('=')
509
            meta[key] = val
510
        
511
        container, sep, object = path.partition('/')
512
        
513
        f = None
514
        chunked = False
515
        if self.srcpath:
516
            f = self.srcpath != '-' and open(self.srcpath) or stdin
517
        if f:
518
            chunked = True if (self.chunked or f == stdin) else False
519
        if self.public not in ['True', 'False', None]:
520
            raise Fault('Not acceptable value for public')
521
        public = eval(self.public) if self.public else None
522
        self.client.update_object(container, object, f, chunked=chunked,
523
                                  headers=headers, offset=self.offset,
524
                                  public=public, **meta)
525
        if f:
526
            f.close()
527

    
528
@cli_command('move', 'mv')
529
class MoveObject(Command):
530
    syntax = '<src container>/<src object> [<dst container>/]<dst object>'
531
    description = 'move an object to a different location'
532
    
533
    def add_options(self, parser):
534
        parser.add_option('--public', action='store',
535
                          dest='public', default=None,
536
                          help='publish/unpublish object (\'True\'/\'False\')')
537
    
538
    def execute(self, src, dst):
539
        src_container, sep, src_object = src.partition('/')
540
        dst_container, sep, dst_object = dst.partition('/')
541
        if not sep:
542
            dst_container = src_container
543
            dst_object = dst
544
        if self.public not in ['True', 'False', None]:
545
            raise Fault('Not acceptable value for public')
546
        public = eval(self.public) if self.public else None
547
        self.client.move_object(src_container, src_object, dst_container,
548
                                dst_object, public, headers)
549

    
550
@cli_command('remove')
551
class TrashObject(Command):
552
    syntax = '<container>/<object>'
553
    description = 'trash an object'
554
    
555
    def execute(self, src):
556
        src_container, sep, src_object = src.partition('/')
557
        
558
        self.client.trash_object(src_container, src_object)
559

    
560
@cli_command('restore')
561
class RestoreObject(Command):
562
    syntax = '<container>/<object>'
563
    description = 'restore a trashed object'
564
    
565
    def execute(self, src):
566
        src_container, sep, src_object = src.partition('/')
567
        
568
        self.client.restore_object(src_container, src_object)
569

    
570
@cli_command('unset')
571
class UnsetObject(Command):
572
    syntax = '<container>/[<object>] key [key] [...]'
573
    description = 'delete metadata info'
574
    
575
    def execute(self, path, *args):
576
        #in case of account fix the args
577
        if len(args) == 0:
578
            args = list(args)
579
            args.append(path)
580
            args = tuple(args)
581
            path = ''
582
        meta = []
583
        for key in args:
584
            meta.append(key)
585
        container, sep, object = path.partition('/')
586
        if object:
587
            self.client.delete_object_metadata(container, object, meta)
588
        elif container:
589
            self.client.delete_container_metadata(container, meta)
590
        else:
591
            self.client.delete_account_metadata(meta)
592

    
593
@cli_command('group')
594
class SetGroup(Command):
595
    syntax = 'key=val [key=val] [...]'
596
    description = 'set group account info'
597
    
598
    def execute(self, *args):
599
        groups = {}
600
        for arg in args:
601
            key, sep, val = arg.partition('=')
602
            groups[key] = val
603
        self.client.set_account_groups(**groups)
604

    
605
@cli_command('policy')
606
class SetPolicy(Command):
607
    syntax = 'container key=val [key=val] [...]'
608
    description = 'set container policies'
609
    
610
    def execute(self, path, *args):
611
        if path.find('=') != -1:
612
            raise Fault('Missing container argument')
613
        
614
        container, sep, object = path.partition('/')
615
        
616
        if object:
617
            raise Fault('Only containers have policies')
618
        
619
        policies = {}
620
        for arg in args:
621
            key, sep, val = arg.partition('=')
622
            policies[key] = val
623
        
624
        self.client.set_container_policies(container, **policies)
625

    
626
@cli_command('publish')
627
class PublishObject(Command):
628
    syntax = '<container>/<object>'
629
    description = 'publish an object'
630
    
631
    def execute(self, src):
632
        src_container, sep, src_object = src.partition('/')
633
        
634
        self.client.publish_object(src_container, src_object)
635

    
636
@cli_command('unpublish')
637
class UnpublishObject(Command):
638
    syntax = '<container>/<object>'
639
    description = 'unpublish an object'
640
    
641
    def execute(self, src):
642
        src_container, sep, src_object = src.partition('/')
643
        
644
        self.client.unpublish_object(src_container, src_object)
645

    
646
def print_usage():
647
    cmd = Command('', [])
648
    parser = cmd.parser
649
    parser.usage = '%prog <command> [options]'
650
    parser.print_help()
651
    
652
    commands = []
653
    for cls in set(_cli_commands.values()):
654
        name = ', '.join(cls.commands)
655
        description = getattr(cls, 'description', '')
656
        commands.append('  %s %s' % (name.ljust(12), description))
657
    print '\nCommands:\n' + '\n'.join(sorted(commands))
658

    
659
def print_dict(d, header='name', f=stdout, detail=True):
660
    header = header in d and header or 'subdir'
661
    if header and header in d:
662
        f.write('%s\n' %d.pop(header))
663
    if detail:
664
        patterns = ['^x_(account|container|object)_meta_(\w+)$']
665
        patterns.append(patterns[0].replace('_', '-'))
666
        for key, val in sorted(d.items()):
667
            for p in patterns:
668
                p = re.compile(p)
669
                m = p.match(key)
670
                if m:
671
                    key = m.group(2)
672
            f.write('%s: %s\n' % (key.rjust(30), val))
673

    
674
def print_list(l, verbose=False, f=stdout, detail=True):
675
    for elem in l:
676
        #if it's empty string continue
677
        if not elem:
678
            continue
679
        if type(elem) == types.DictionaryType:
680
            print_dict(elem, f=f, detail=detail)
681
        elif type(elem) == types.StringType:
682
            if not verbose:
683
                elem = elem.split('Traceback')[0]
684
            f.write('%s\n' % elem)
685
        else:
686
            f.write('%s\n' % elem)
687

    
688
def print_versions(data, f=stdout):
689
    if 'versions' not in data:
690
        f.write('%s\n' %data)
691
        return
692
    f.write('versions:\n')
693
    for id, t in data['versions']:
694
        f.write('%s @ %s\n' % (str(id).rjust(30), datetime.fromtimestamp(t)))
695

    
696
def _get_user():
697
        try:
698
            return os.environ['PITHOS_USER']
699
        except KeyError:
700
            return getuser()
701

    
702
def _get_auth():
703
        try:
704
            return os.environ['PITHOS_AUTH']
705
        except KeyError:
706
            return '0000'
707
    
708

    
709
def main():
710
    try:
711
        name = argv[1]
712
        cls = class_for_cli_command(name)
713
    except (IndexError, KeyError):
714
        print_usage()
715
        exit(1)
716
    
717
    cmd = cls(name, argv[2:])
718
    
719
    try:
720
        cmd.execute(*cmd.args)
721
    except TypeError, e:
722
        cmd.parser.print_help()
723
        exit(1)
724
    except Fault, f:
725
        status = f.status and '%s ' % f.status or ''
726
        print '%s%s' % (status, f.data)
727

    
728
if __name__ == '__main__':
729
    main()