Revision 2715ade4 snf-pithos-tools/pithos/tools/sh.py

b/snf-pithos-tools/pithos/tools/sh.py
1 1
#!/usr/bin/env python
2 2

  
3 3
# Copyright 2011-2012 GRNET S.A. All rights reserved.
4
# 
4
#
5 5
# Redistribution and use in source and binary forms, with or
6 6
# without modification, are permitted provided that the following
7 7
# conditions are met:
8
# 
8
#
9 9
#   1. Redistributions of source code must retain the above
10 10
#      copyright notice, this list of conditions and the following
11 11
#      disclaimer.
12
# 
12
#
13 13
#   2. Redistributions in binary form must reproduce the above
14 14
#      copyright notice, this list of conditions and the following
15 15
#      disclaimer in the documentation and/or other materials
16 16
#      provided with the distribution.
17
# 
17
#
18 18
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 19
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 20
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
......
27 27
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 28
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 29
# POSSIBILITY OF SUCH DAMAGE.
30
# 
30
#
31 31
# The views and conclusions contained in the software and
32 32
# documentation are those of the authors and should not be
33 33
# interpreted as representing official policies, either expressed
......
52 52

  
53 53
_cli_commands = {}
54 54

  
55

  
55 56
def cli_command(*args):
56 57
    def decorator(cls):
57 58
        cls.commands = args
......
60 61
        return cls
61 62
    return decorator
62 63

  
64

  
63 65
def class_for_cli_command(name):
64 66
    return _cli_commands[name]
65 67

  
68

  
66 69
class Command(object):
67 70
    syntax = ''
68
    
71

  
69 72
    def __init__(self, name, argv):
70 73
        parser = OptionParser('%%prog %s [options] %s' % (name, self.syntax))
71 74
        parser.add_option('--url', dest='url', metavar='URL',
......
82 85
                          default=False, help='debug output')
83 86
        self.add_options(parser)
84 87
        options, args = parser.parse_args(argv)
85
        
88

  
86 89
        # Add options to self
87 90
        for opt in parser.option_list:
88 91
            key = opt.dest
89 92
            if key:
90 93
                val = getattr(options, key)
91 94
                setattr(self, key, val)
92
        
93
        self.client = Pithos_Client(self.url, self.token, self.user, self.verbose,
94
                             self.debug)
95
        
95

  
96
        self.client = Pithos_Client(
97
            self.url, self.token, self.user, self.verbose,
98
            self.debug)
99

  
96 100
        self.parser = parser
97 101
        self.args = args
98
    
102

  
99 103
    def _build_args(self, attrs):
100 104
        args = {}
101 105
        for a in [a for a in attrs if getattr(self, a)]:
......
104 108

  
105 109
    def add_options(self, parser):
106 110
        pass
107
    
111

  
108 112
    def execute(self, *args):
109 113
        pass
110 114

  
115

  
111 116
@cli_command('list', 'ls')
112 117
class List(Command):
113 118
    syntax = '[<container>[/<object>]]'
114 119
    description = 'list containers or objects'
115
    
120

  
116 121
    def add_options(self, parser):
117 122
        parser.add_option('-l', action='store_true', dest='detail',
118 123
                          default=False, help='show detailed output')
......
147 152
                          default=False, help='show only shared')
148 153
        parser.add_option('--public', action='store_true', dest='public',
149 154
                          default=False, help='show only public')
150
        
151
    
155

  
152 156
    def execute(self, container=None):
153 157
        if container:
154 158
            self.list_objects(container)
155 159
        else:
156 160
            self.list_containers()
157
    
161

  
158 162
    def list_containers(self):
159 163
        attrs = ['limit', 'marker', 'if_modified_since',
160 164
                 'if_unmodified_since', 'shared', 'public']
161 165
        args = self._build_args(attrs)
162 166
        args['format'] = 'json' if self.detail else 'text'
163
        
167

  
164 168
        if getattr(self, 'until'):
165 169
            t = _time.strptime(self.until, self.format)
166 170
            args['until'] = int(_time.mktime(t))
167
        
171

  
168 172
        l = self.client.list_containers(**args)
169 173
        print_list(l)
170
    
174

  
171 175
    def list_objects(self, container):
172 176
        #prepate params
173 177
        params = {}
......
176 180
                 'shared', 'public']
177 181
        args = self._build_args(attrs)
178 182
        args['format'] = 'json' if self.detail else 'text'
179
        
183

  
180 184
        if self.until:
181 185
            t = _time.strptime(self.until, self.format)
182 186
            args['until'] = int(_time.mktime(t))
183
        
187

  
184 188
        container, sep, object = container.partition('/')
185 189
        if object:
186 190
            return
187
        
191

  
188 192
        detail = 'json'
189 193
        #if request with meta quering disable trash filtering
190 194
        show_trashed = True if self.meta else False
191 195
        l = self.client.list_objects(container, **args)
192 196
        print_list(l, detail=self.detail)
193 197

  
198

  
194 199
@cli_command('meta')
195 200
class Meta(Command):
196 201
    syntax = '[<container>[/<object>]]'
197 202
    description = 'get account/container/object metadata'
198
    
203

  
199 204
    def add_options(self, parser):
200 205
        parser.add_option('-r', action='store_true', dest='restricted',
201 206
                          default=False, help='show only user defined metadata')
......
206 211
        parser.add_option('--version', action='store', dest='version',
207 212
                          default=None, help='show specific version \
208 213
                                  (applies only for objects)')
209
    
214

  
210 215
    def execute(self, path=''):
211 216
        container, sep, object = path.partition('/')
212 217
        args = {'restricted': self.restricted}
213 218
        if getattr(self, 'until'):
214 219
            t = _time.strptime(self.until, self.format)
215 220
            args['until'] = int(_time.mktime(t))
216
        
221

  
217 222
        if object:
218 223
            meta = self.client.retrieve_object_metadata(container, object,
219 224
                                                        self.restricted,
......
222 227
            meta = self.client.retrieve_container_metadata(container, **args)
223 228
        else:
224 229
            meta = self.client.retrieve_account_metadata(**args)
225
        if meta == None:
230
        if meta is None:
226 231
            print 'Entity does not exist'
227 232
        else:
228 233
            print_dict(meta, header=None)
229 234

  
235

  
230 236
@cli_command('create')
231 237
class CreateContainer(Command):
232 238
    syntax = '<container> [key=val] [...]'
233 239
    description = 'create a container'
234
    
240

  
235 241
    def add_options(self, parser):
236 242
        parser.add_option('--versioning', action='store', dest='versioning',
237 243
                          default=None, help='set container versioning (auto/none)')
238 244
        parser.add_option('--quota', action='store', dest='quota',
239 245
                          default=None, help='set default container quota')
240
    
246

  
241 247
    def execute(self, container, *args):
242 248
        meta = {}
243 249
        for arg in args:
......
248 254
            policy['versioning'] = self.versioning
249 255
        if getattr(self, 'quota'):
250 256
            policy['quota'] = self.quota
251
        ret = self.client.create_container(container, meta=meta, policies=policy)
257
        ret = self.client.create_container(
258
            container, meta=meta, policies=policy)
252 259
        if not ret:
253 260
            print 'Container already exists'
254 261

  
262

  
255 263
@cli_command('delete', 'rm')
256 264
class Delete(Command):
257 265
    syntax = '<container>[/<object>]'
258 266
    description = 'delete a container or an object'
259
    
267

  
260 268
    def add_options(self, parser):
261 269
        parser.add_option('--until', action='store', dest='until',
262 270
                          default=None, help='remove history until that date')
......
268 276
        parser.add_option('-r', action='store_true',
269 277
                          dest='recursive', default=False,
270 278
                          help='mass delimiter objects with delimiter /')
271
    
279

  
272 280
    def execute(self, path):
273 281
        container, sep, object = path.partition('/')
274 282
        until = None
275 283
        if getattr(self, 'until'):
276 284
            t = _time.strptime(self.until, self.format)
277 285
            until = int(_time.mktime(t))
278
        
286

  
279 287
        kwargs = {}
280 288
        if self.delimiter:
281 289
            kwargs['delimiter'] = self.delimiter
282 290
        elif self.recursive:
283 291
            kwargs['delimiter'] = '/'
284
        
292

  
285 293
        if object:
286 294
            self.client.delete_object(container, object, until, **kwargs)
287 295
        else:
288 296
            self.client.delete_container(container, until, **kwargs)
289 297

  
298

  
290 299
@cli_command('get')
291 300
class GetObject(Command):
292 301
    syntax = '<container>/<object>'
293 302
    description = 'get the data of an object'
294
    
303

  
295 304
    def add_options(self, parser):
296 305
        parser.add_option('-l', action='store_true', dest='detail',
297 306
                          default=False, help='show detailed output')
......
323 332
        parser.add_option('--hashmap', action='store_true',
324 333
                          dest='hashmap', default=False,
325 334
                          help='get the object hashmap instead')
326
    
335

  
327 336
    def execute(self, path):
328 337
        attrs = ['if_match', 'if_none_match', 'if_modified_since',
329 338
                 'if_unmodified_since', 'hashmap']
......
333 342
            args['range'] = 'bytes=%s' % self.range
334 343
        if getattr(self, 'if_range'):
335 344
            args['if-range'] = 'If-Range:%s' % getattr(self, 'if_range')
336
        
345

  
337 346
        container, sep, object = path.partition('/')
338 347
        data = None
339 348
        if self.versionlist:
......
341 350
                args.pop('detail')
342 351
            args.pop('format')
343 352
            self.detail = True
344
            data = self.client.retrieve_object_versionlist(container, object, **args)
353
            data = self.client.retrieve_object_versionlist(
354
                container, object, **args)
345 355
        elif self.version:
346 356
            data = self.client.retrieve_object_version(container, object,
347 357
                                                       self.version, **args)
......
350 360
                args.pop('detail')
351 361
            args.pop('format')
352 362
            self.detail = True
353
            data = self.client.retrieve_object_hashmap(container, object, **args)
363
            data = self.client.retrieve_object_hashmap(
364
                container, object, **args)
354 365
        else:
355
            data = self.client.retrieve_object(container, object, **args)    
356
        
366
            data = self.client.retrieve_object(container, object, **args)
367

  
357 368
        f = open(self.file, 'w') if self.file else stdout
358
        if self.detail or type(data) == types.DictionaryType:
369
        if self.detail or isinstance(data, types.DictionaryType):
359 370
            if self.versionlist:
360 371
                print_versions(data, f=f)
361 372
            else:
......
364 375
            f.write(data)
365 376
        f.close()
366 377

  
378

  
367 379
@cli_command('mkdir')
368 380
class PutMarker(Command):
369 381
    syntax = '<container>/<directory marker>'
370 382
    description = 'create a directory marker'
371
    
383

  
372 384
    def execute(self, path):
373 385
        container, sep, object = path.partition('/')
374 386
        self.client.create_directory_marker(container, object)
375 387

  
388

  
376 389
@cli_command('put')
377 390
class PutObject(Command):
378 391
    syntax = '<container>/<object> [key=val] [...]'
379 392
    description = 'create/override object'
380
    
393

  
381 394
    def add_options(self, parser):
382
        parser.add_option('--use_hashes', action='store_true', dest='use_hashes',
383
                          default=False, help='provide hashmap instead of data')
395
        parser.add_option(
396
            '--use_hashes', action='store_true', dest='use_hashes',
397
            default=False, help='provide hashmap instead of data')
384 398
        parser.add_option('--chunked', action='store_true', dest='chunked',
385 399
                          default=False, help='set chunked transfer mode')
386 400
        parser.add_option('--etag', action='store', dest='etag',
......
409 423
        parser.add_option('--public', action='store_true',
410 424
                          dest='x_object_public', default=False,
411 425
                          help='make object publicly accessible')
412
    
426

  
413 427
    def execute(self, path, *args):
414 428
        if path.find('=') != -1:
415 429
            raise Fault('Missing path argument')
416
        
430

  
417 431
        #prepare user defined meta
418 432
        meta = {}
419 433
        for arg in args:
420 434
            key, sep, val = arg.partition('=')
421 435
            meta[key] = val
422
        
436

  
423 437
        attrs = ['etag', 'content_encoding', 'content_disposition',
424 438
                 'content_type', 'x_object_sharing', 'x_object_public']
425 439
        args = self._build_args(attrs)
426
        
440

  
427 441
        container, sep, object = path.partition('/')
428
        
442

  
429 443
        f = None
430 444
        if self.srcpath:
431 445
            f = open(self.srcpath) if self.srcpath != '-' else stdin
432
        
446

  
433 447
        if self.use_hashes and not f:
434 448
            raise Fault('Illegal option combination')
435
        
449

  
436 450
        if self.chunked:
437 451
            self.client.create_object_using_chunks(container, object, f,
438
                                                    meta=meta, **args)
452
                                                   meta=meta, **args)
439 453
        elif self.use_hashes:
440 454
            data = f.read()
441 455
            hashmap = json.loads(data)
442 456
            self.client.create_object_by_hashmap(container, object, hashmap,
443
                                             meta=meta, **args)
457
                                                 meta=meta, **args)
444 458
        elif self.x_object_manifest:
445
            self.client.create_manifestation(container, object, self.x_object_manifest)
459
            self.client.create_manifestation(
460
                container, object, self.x_object_manifest)
446 461
        elif not f:
447
            self.client.create_zero_length_object(container, object, meta=meta, **args)
462
            self.client.create_zero_length_object(
463
                container, object, meta=meta, **args)
448 464
        else:
449 465
            self.client.create_object(container, object, f, meta=meta, **args)
450 466
        if f:
451 467
            f.close()
452 468

  
469

  
453 470
@cli_command('copy', 'cp')
454 471
class CopyObject(Command):
455 472
    syntax = '<src container>/<src object> [<dst container>/]<dst object> [key=val] [...]'
456 473
    description = 'copy an object to a different location'
457
    
474

  
458 475
    def add_options(self, parser):
459 476
        parser.add_option('--version', action='store',
460 477
                          dest='version', default=False,
......
471 488
        parser.add_option('-r', action='store_true',
472 489
                          dest='recursive', default=False,
473 490
                          help='mass copy with delimiter /')
474
    
491

  
475 492
    def execute(self, src, dst, *args):
476 493
        src_container, sep, src_object = src.partition('/')
477 494
        dst_container, sep, dst_object = dst.partition('/')
478
        
495

  
479 496
        #prepare user defined meta
480 497
        meta = {}
481 498
        for arg in args:
482 499
            key, sep, val = arg.partition('=')
483 500
            meta[key] = val
484
        
501

  
485 502
        if not sep:
486 503
            dst_container = src_container
487 504
            dst_object = dst
488
        
489
        args = {'content_type':self.content_type} if self.content_type else {}
505

  
506
        args = {'content_type': self.content_type} if self.content_type else {}
490 507
        if self.delimiter:
491
        	args['delimiter'] = self.delimiter
508
            args['delimiter'] = self.delimiter
492 509
        elif self.recursive:
493
        	args['delimiter'] = '/'
510
            args['delimiter'] = '/'
494 511
        self.client.copy_object(src_container, src_object, dst_container,
495 512
                                dst_object, meta, self.public, self.version,
496 513
                                **args)
497 514

  
515

  
498 516
@cli_command('set')
499 517
class SetMeta(Command):
500 518
    syntax = '[<container>[/<object>]] key=val [key=val] [...]'
501 519
    description = 'set account/container/object metadata'
502
    
520

  
503 521
    def execute(self, path, *args):
504 522
        #in case of account fix the args
505 523
        if path.find('=') != -1:
......
519 537
        else:
520 538
            self.client.update_account_metadata(**meta)
521 539

  
540

  
522 541
@cli_command('update')
523 542
class UpdateObject(Command):
524 543
    syntax = '<container>/<object> path [key=val] [...]'
525 544
    description = 'update object metadata/data (default mode: append)'
526
    
545

  
527 546
    def add_options(self, parser):
528 547
        parser.add_option('-a', action='store_true', dest='append',
529 548
                          default=True, help='append data')
......
542 561
                          help='provide the presentation style of the object')
543 562
        parser.add_option('--manifest', action='store', type='str',
544 563
                          dest='x_object_manifest', default=None,
545
                          help='use for large file support')        
564
                          help='use for large file support')
546 565
        parser.add_option('--sharing', action='store',
547 566
                          dest='x_object_sharing', default=None,
548 567
                          help='define sharing object policy')
......
558 577
        parser.add_option('--replace', action='store_true',
559 578
                          dest='replace', default=False,
560 579
                          help='override metadata')
561
    
580

  
562 581
    def execute(self, path, *args):
563 582
        if path.find('=') != -1:
564 583
            raise Fault('Missing path argument')
565
        
584

  
566 585
        #prepare user defined meta
567 586
        meta = {}
568 587
        for arg in args:
569 588
            key, sep, val = arg.partition('=')
570 589
            meta[key] = val
571
        
572
        
590

  
573 591
        attrs = ['content_encoding', 'content_disposition', 'x_object_sharing',
574 592
                 'x_object_public', 'x_object_manifest', 'replace', 'offset',
575 593
                 'content_range']
576 594
        args = self._build_args(attrs)
577
        
595

  
578 596
        if self.no_sharing:
579 597
            args['x_object_sharing'] = ''
580
        
598

  
581 599
        container, sep, object = path.partition('/')
582
        
600

  
583 601
        f = None
584 602
        if self.srcpath:
585 603
            f = open(self.srcpath) if self.srcpath != '-' else stdin
586
        
604

  
587 605
        if self.chunked:
588 606
            self.client.update_object_using_chunks(container, object, f,
589
                                                    meta=meta, **args)
607
                                                   meta=meta, **args)
590 608
        else:
591 609
            self.client.update_object(container, object, f, meta=meta, **args)
592 610
        if f:
593 611
            f.close()
594 612

  
613

  
595 614
@cli_command('move', 'mv')
596 615
class MoveObject(Command):
597 616
    syntax = '<src container>/<src object> [<dst container>/]<dst object>'
598 617
    description = 'move an object to a different location'
599
    
618

  
600 619
    def add_options(self, parser):
601 620
        parser.add_option('--public', action='store_true',
602 621
                          dest='public', default=False,
......
610 629
        parser.add_option('-r', action='store_true',
611 630
                          dest='recursive', default=False,
612 631
                          help='mass move objects with delimiter /')
613
    
632

  
614 633
    def execute(self, src, dst, *args):
615 634
        src_container, sep, src_object = src.partition('/')
616 635
        dst_container, sep, dst_object = dst.partition('/')
617 636
        if not sep:
618 637
            dst_container = src_container
619 638
            dst_object = dst
620
        
639

  
621 640
        #prepare user defined meta
622 641
        meta = {}
623 642
        for arg in args:
624 643
            key, sep, val = arg.partition('=')
625 644
            meta[key] = val
626
        
627
        args = {'content_type':self.content_type} if self.content_type else {}
645

  
646
        args = {'content_type': self.content_type} if self.content_type else {}
628 647
        if self.delimiter:
629
        	args['delimiter'] = self.delimiter
648
            args['delimiter'] = self.delimiter
630 649
        elif self.recursive:
631
        	args['delimiter'] = '/'
650
            args['delimiter'] = '/'
632 651
        self.client.move_object(src_container, src_object, dst_container,
633 652
                                dst_object, meta, self.public, **args)
634 653

  
654

  
635 655
@cli_command('unset')
636 656
class UnsetObject(Command):
637 657
    syntax = '<container>/[<object>] key [key] [...]'
638 658
    description = 'delete metadata info'
639
    
659

  
640 660
    def execute(self, path, *args):
641 661
        #in case of account fix the args
642 662
        if len(args) == 0:
......
655 675
        else:
656 676
            self.client.delete_account_metadata(meta)
657 677

  
678

  
658 679
@cli_command('group')
659 680
class CreateGroup(Command):
660 681
    syntax = 'key=val [key=val] [...]'
661 682
    description = 'create account groups'
662
    
683

  
663 684
    def execute(self, *args):
664 685
        groups = {}
665 686
        for arg in args:
......
667 688
            groups[key] = val
668 689
        self.client.set_account_groups(**groups)
669 690

  
691

  
670 692
@cli_command('ungroup')
671 693
class DeleteGroup(Command):
672 694
    syntax = 'key [key] [...]'
673 695
    description = 'delete account groups'
674
    
696

  
675 697
    def execute(self, *args):
676 698
        groups = []
677 699
        for arg in args:
678 700
            groups.append(arg)
679 701
        self.client.unset_account_groups(groups)
680 702

  
703

  
681 704
@cli_command('policy')
682 705
class SetPolicy(Command):
683 706
    syntax = 'container key=val [key=val] [...]'
684 707
    description = 'set container policies'
685
    
708

  
686 709
    def execute(self, path, *args):
687 710
        if path.find('=') != -1:
688 711
            raise Fault('Missing container argument')
689
        
712

  
690 713
        container, sep, object = path.partition('/')
691
        
714

  
692 715
        if object:
693 716
            raise Fault('Only containers have policies')
694
        
717

  
695 718
        policies = {}
696 719
        for arg in args:
697 720
            key, sep, val = arg.partition('=')
698 721
            policies[key] = val
699
        
722

  
700 723
        self.client.set_container_policies(container, **policies)
701 724

  
725

  
702 726
@cli_command('publish')
703 727
class PublishObject(Command):
704 728
    syntax = '<container>/<object>'
705 729
    description = 'publish an object'
706
    
730

  
707 731
    def execute(self, src):
708 732
        src_container, sep, src_object = src.partition('/')
709
        
733

  
710 734
        self.client.publish_object(src_container, src_object)
711 735

  
736

  
712 737
@cli_command('unpublish')
713 738
class UnpublishObject(Command):
714 739
    syntax = '<container>/<object>'
715 740
    description = 'unpublish an object'
716
    
741

  
717 742
    def execute(self, src):
718 743
        src_container, sep, src_object = src.partition('/')
719
        
744

  
720 745
        self.client.unpublish_object(src_container, src_object)
721 746

  
747

  
722 748
@cli_command('sharing')
723 749
class SharingObject(Command):
724 750
    syntax = 'list users sharing objects with the user'
725 751
    description = 'list user accounts sharing objects with the user'
726
    
752

  
727 753
    def add_options(self, parser):
728 754
        parser.add_option('-l', action='store_true', dest='detail',
729 755
                          default=False, help='show detailed output')
......
732 758
        parser.add_option('--marker', action='store', type='str',
733 759
                          dest='marker', default=None,
734 760
                          help='show output greater then marker')
735
        
736
    
761

  
737 762
    def execute(self):
738 763
        attrs = ['limit', 'marker']
739 764
        args = self._build_args(attrs)
740 765
        args['format'] = 'json' if self.detail else 'text'
741
        
766

  
742 767
        print_list(self.client.list_shared_by_others(**args))
743 768

  
769

  
744 770
@cli_command('send')
745 771
class Send(Command):
746 772
    syntax = '<file> <container>[/<prefix>]'
747 773
    description = 'upload file to container (using prefix)'
748
    
774

  
749 775
    def execute(self, file, path):
750 776
        container, sep, prefix = path.partition('/')
751 777
        upload(self.client, file, container, prefix)
752 778

  
779

  
753 780
@cli_command('receive')
754 781
class Receive(Command):
755 782
    syntax = '<container>/<object> <file>'
756 783
    description = 'download object to file'
757
    
784

  
758 785
    def execute(self, path, file):
759 786
        container, sep, object = path.partition('/')
760 787
        download(self.client, container, object, file)
761 788

  
789

  
762 790
def print_usage():
763 791
    cmd = Command('', [])
764 792
    parser = cmd.parser
765 793
    parser.usage = '%prog <command> [options]'
766 794
    parser.print_help()
767
    
795

  
768 796
    commands = []
769 797
    for cls in set(_cli_commands.values()):
770 798
        name = ', '.join(cls.commands)
......
772 800
        commands.append('  %s %s' % (name.ljust(12), description))
773 801
    print '\nCommands:\n' + '\n'.join(sorted(commands))
774 802

  
803

  
775 804
def print_dict(d, header='name', f=stdout, detail=True):
776 805
    header = header if header in d else 'subdir'
777 806
    if header and header in d:
778
        f.write('%s\n' %d.pop(header).encode('utf8'))
807
        f.write('%s\n' % d.pop(header).encode('utf8'))
779 808
    if detail:
780 809
        patterns = ['^x_(account|container|object)_meta_(\w+)$']
781 810
        patterns.append(patterns[0].replace('_', '-'))
782 811
        for key, val in sorted(d.items()):
783 812
            f.write('%s: %s\n' % (key.rjust(30), val))
784 813

  
814

  
785 815
def print_list(l, verbose=False, f=stdout, detail=True):
786 816
    for elem in l:
787 817
        #if it's empty string continue
788 818
        if not elem:
789 819
            continue
790
        if type(elem) == types.DictionaryType:
820
        if isinstance(elem, types.DictionaryType):
791 821
            print_dict(elem, f=f, detail=detail)
792
        elif type(elem) == types.StringType:
822
        elif isinstance(elem, types.StringType):
793 823
            if not verbose:
794 824
                elem = elem.split('Traceback')[0]
795 825
            f.write('%s\n' % elem)
796 826
        else:
797 827
            f.write('%s\n' % elem)
798 828

  
829

  
799 830
def print_versions(data, f=stdout):
800 831
    if 'versions' not in data:
801
        f.write('%s\n' %data)
832
        f.write('%s\n' % data)
802 833
        return
803 834
    f.write('versions:\n')
804 835
    for id, t in data['versions']:
805
        f.write('%s @ %s\n' % (str(id).rjust(30), datetime.fromtimestamp(float(t))))
836
        f.write('%s @ %s\n' % (str(id).rjust(30),
837
                datetime.fromtimestamp(float(t))))
806 838

  
807 839

  
808 840
def main():
......
812 844
    except (IndexError, KeyError):
813 845
        print_usage()
814 846
        exit(1)
815
    
847

  
816 848
    cmd = cls(name, argv[2:])
817
    
849

  
818 850
    try:
819 851
        cmd.execute(*cmd.args)
820 852
    except TypeError, e:

Also available in: Unified diff