comparison roundup/admin.py @ 6180:4f45ce95f016

pep8 changes.
author John Rouillard <rouilj@ieee.org>
date Tue, 19 May 2020 17:10:08 -0400
parents 41907e1f9c3f
children cc66cf905147
comparison
equal deleted inserted replaced
6179:a701c9c81597 6180:4f45ce95f016
23 23
24 __docformat__ = 'restructuredtext' 24 __docformat__ = 'restructuredtext'
25 25
26 import csv, getopt, getpass, os, re, shutil, sys, operator 26 import csv, getopt, getpass, os, re, shutil, sys, operator
27 27
28 from roundup import date, hyperdb, roundupdb, init, password, token 28 from roundup import date, hyperdb, init, password, token
29 from roundup import __version__ as roundup_version 29 from roundup import __version__ as roundup_version
30 import roundup.instance 30 import roundup.instance
31 from roundup.configuration import CoreConfig, NoConfigError, UserConfig 31 from roundup.configuration import CoreConfig, NoConfigError, UserConfig
32 from roundup.i18n import _ 32 from roundup.i18n import _
33 from roundup.exceptions import UsageError 33 from roundup.exceptions import UsageError
37 try: 37 try:
38 from UserDict import UserDict 38 from UserDict import UserDict
39 except ImportError: 39 except ImportError:
40 from collections import UserDict 40 from collections import UserDict
41 41
42
42 class CommandDict(UserDict): 43 class CommandDict(UserDict):
43 """Simple dictionary that lets us do lookups using partial keys. 44 """Simple dictionary that lets us do lookups using partial keys.
44 45
45 Original code submitted by Engelbert Gruber. 46 Original code submitted by Engelbert Gruber.
46 """ 47 """
47 _marker = [] 48 _marker = []
49
48 def get(self, key, default=_marker): 50 def get(self, key, default=_marker):
49 if key in self.data: 51 if key in self.data:
50 return [(key, self.data[key])] 52 return [(key, self.data[key])]
51 keylist = sorted(self.data) 53 keylist = sorted(self.data)
52 l = [] 54 l = []
54 if ki.startswith(key): 56 if ki.startswith(key):
55 l.append((ki, self.data[ki])) 57 l.append((ki, self.data[ki]))
56 if not l and default is self._marker: 58 if not l and default is self._marker:
57 raise KeyError(key) 59 raise KeyError(key)
58 return l 60 return l
61
59 62
60 class AdminTool: 63 class AdminTool:
61 """ A collection of methods used in maintaining Roundup trackers. 64 """ A collection of methods used in maintaining Roundup trackers.
62 65
63 Typically these methods are accessed through the roundup-admin 66 Typically these methods are accessed through the roundup-admin
87 """Get the class - raise an exception if it doesn't exist. 90 """Get the class - raise an exception if it doesn't exist.
88 """ 91 """
89 try: 92 try:
90 return self.db.getclass(classname) 93 return self.db.getclass(classname)
91 except KeyError: 94 except KeyError:
92 raise UsageError(_('no such class "%(classname)s"')%locals()) 95 raise UsageError(_('no such class "%(classname)s"') % locals())
93 96
94 def props_from_args(self, args): 97 def props_from_args(self, args):
95 """ Produce a dictionary of prop: value from the args list. 98 """ Produce a dictionary of prop: value from the args list.
96 99
97 The args list is specified as ``prop=value prop=value ...``. 100 The args list is specified as ``prop=value prop=value ...``.
98 """ 101 """
99 props = {} 102 props = {}
100 for arg in args: 103 for arg in args:
101 if arg.find('=') == -1: 104 if arg.find('=') == -1:
102 raise UsageError(_('argument "%(arg)s" not propname=value' 105 raise UsageError(_('argument "%(arg)s" not propname=value') %
103 )%locals()) 106 locals())
104 l = arg.split('=') 107 l = arg.split('=')
105 if len(l) < 2: 108 if len(l) < 2:
106 raise UsageError(_('argument "%(arg)s" not propname=value' 109 raise UsageError(_('argument "%(arg)s" not propname=value') %
107 )%locals()) 110 locals())
108 key, value = l[0], '='.join(l[1:]) 111 key, value = l[0], '='.join(l[1:])
109 if value: 112 if value:
110 props[key] = value 113 props[key] = value
111 else: 114 else:
112 props[key] = None 115 props[key] = None
114 117
115 def usage(self, message=''): 118 def usage(self, message=''):
116 """ Display a simple usage message. 119 """ Display a simple usage message.
117 """ 120 """
118 if message: 121 if message:
119 message = _('Problem: %(message)s\n\n')%locals() 122 message = _('Problem: %(message)s\n\n')% locals()
120 sys.stdout.write( _("""%(message)sUsage: roundup-admin [options] [<command> <arguments>] 123 sys.stdout.write(_("""%(message)sUsage: roundup-admin [options] [<command> <arguments>]
121 124
122 Options: 125 Options:
123 -i instance home -- specify the issue tracker "home directory" to administer 126 -i instance home -- specify the issue tracker "home directory" to administer
124 -u -- the user[:password] to use for commands (default admin) 127 -u -- the user[:password] to use for commands (default admin)
125 -d -- print full designators not just class id numbers 128 -d -- print full designators not just class id numbers
136 Help: 139 Help:
137 roundup-admin -h 140 roundup-admin -h
138 roundup-admin help -- this help 141 roundup-admin help -- this help
139 roundup-admin help <command> -- command-specific help 142 roundup-admin help <command> -- command-specific help
140 roundup-admin help all -- all available help 143 roundup-admin help all -- all available help
141 """)%locals()) 144 """) % locals())
142 self.help_commands() 145 self.help_commands()
143 146
144 def help_commands(self): 147 def help_commands(self):
145 """List the commands available with their help summary. 148 """List the commands available with their help summary.
146 """ 149 """
147 sys.stdout.write( _('Commands: ')) 150 sys.stdout.write(_('Commands: '))
148 commands = [''] 151 commands = ['']
149 for command in self.commands.values(): 152 for command in self.commands.values():
150 h = _(command.__doc__).split('\n')[0] 153 h = _(command.__doc__).split('\n')[0]
151 commands.append(' '+h[7:]) 154 commands.append(' '+h[7:])
152 commands.sort() 155 commands.sort()
157 160
158 def help_commands_html(self, indent_re=re.compile(r'^(\s+)\S+')): 161 def help_commands_html(self, indent_re=re.compile(r'^(\s+)\S+')):
159 """ Produce an HTML command list. 162 """ Produce an HTML command list.
160 """ 163 """
161 commands = sorted(iter(self.commands.values()), 164 commands = sorted(iter(self.commands.values()),
162 key=operator.attrgetter('__name__')) 165 key=operator.attrgetter('__name__'))
163 for command in commands: 166 for command in commands:
164 h = _(command.__doc__).split('\n') 167 h = _(command.__doc__).split('\n')
165 name = command.__name__[3:] 168 name = command.__name__[3:]
166 usage = h[0] 169 usage = h[0]
167 print(""" 170 print("""
239 "." means "right now" 242 "." means "right now"
240 243
241 Command help: 244 Command help:
242 """)) 245 """))
243 for name, command in list(self.commands.items()): 246 for name, command in list(self.commands.items()):
244 print(_('%s:')%name) 247 print(_('%s:') % name)
245 print(' ', _(command.__doc__)) 248 print(' ', _(command.__doc__))
246 249
247 def do_help(self, args, nl_re=re.compile('[\r\n]'), 250 def do_help(self, args, nl_re=re.compile('[\r\n]'),
248 indent_re=re.compile(r'^(\s+)\S+')): 251 indent_re=re.compile(r'^(\s+)\S+')):
249 ''"""Usage: help topic 252 ''"""Usage: help topic
250 Give help about topic. 253 Give help about topic.
251 254
252 commands -- list commands 255 commands -- list commands
253 <command> -- help specific to a command 256 <command> -- help specific to a command
254 initopts -- init command options 257 initopts -- init command options
255 all -- all available help 258 all -- all available help
256 """ 259 """
257 if len(args)>0: 260 if len(args) > 0:
258 topic = args[0] 261 topic = args[0]
259 else: 262 else:
260 topic = 'help' 263 topic = 'help'
261
262 264
263 # try help_ methods 265 # try help_ methods
264 if topic in self.help: 266 if topic in self.help:
265 self.help[topic]() 267 self.help[topic]()
266 return 0 268 return 0
267 269
268 # try command docstrings 270 # try command docstrings
269 try: 271 try:
270 l = self.commands.get(topic) 272 l = self.commands.get(topic)
271 except KeyError: 273 except KeyError:
272 print(_('Sorry, no help for "%(topic)s"')%locals()) 274 print(_('Sorry, no help for "%(topic)s"') % locals())
273 return 1 275 return 1
274 276
275 # display the help for each match, removing the docsring indent 277 # display the help for each match, removing the docstring indent
276 for name, help in l: 278 for _name, help in l:
277 lines = nl_re.split(_(help.__doc__)) 279 lines = nl_re.split(_(help.__doc__))
278 print(lines[0]) 280 print(lines[0])
279 indent = indent_re.match(lines[1]) 281 indent = indent_re.match(lines[1])
280 if indent: indent = len(indent.group(1)) 282 if indent: indent = len(indent.group(1))
281 for line in lines[1:]: 283 for line in lines[1:]:
315 # we're interested in where the directory containing "share" is 317 # we're interested in where the directory containing "share" is
316 templates = {} 318 templates = {}
317 for N in 2, 4, 5, 6: 319 for N in 2, 4, 5, 6:
318 path = __file__ 320 path = __file__
319 # move up N elements in the path 321 # move up N elements in the path
320 for i in range(N): 322 for _i in range(N):
321 path = os.path.dirname(path) 323 path = os.path.dirname(path)
322 tdir = os.path.join(path, 'share', 'roundup', 'templates') 324 tdir = os.path.join(path, 'share', 'roundup', 'templates')
323 if os.path.isdir(tdir): 325 if os.path.isdir(tdir):
324 templates = init.listTemplates(tdir) 326 templates = init.listTemplates(tdir)
325 break 327 break
326 328
327 # OK, now try as if we're in the roundup source distribution 329 # OK, now try as if we're in the roundup source distribution
328 # directory, so this module will be in .../roundup-*/roundup/admin.py 330 # directory, so this module will be in .../roundup-*/roundup/admin.py
329 # and we're interested in the .../roundup-*/ part. 331 # and we're interested in the .../roundup-*/ part.
330 path = __file__ 332 path = __file__
331 for i in range(2): 333 for _i in range(2):
332 path = os.path.dirname(path) 334 path = os.path.dirname(path)
333 tdir = os.path.join(path, 'templates') 335 tdir = os.path.join(path, 'templates')
334 if os.path.isdir(tdir): 336 if os.path.isdir(tdir):
335 templates.update(init.listTemplates(tdir)) 337 templates.update(init.listTemplates(tdir))
336 338
380 # make sure the tracker home can be created 382 # make sure the tracker home can be created
381 tracker_home = os.path.abspath(tracker_home) 383 tracker_home = os.path.abspath(tracker_home)
382 parent = os.path.split(tracker_home)[0] 384 parent = os.path.split(tracker_home)[0]
383 if not os.path.exists(parent): 385 if not os.path.exists(parent):
384 raise UsageError(_('Instance home parent directory "%(parent)s"' 386 raise UsageError(_('Instance home parent directory "%(parent)s"'
385 ' does not exist')%locals()) 387 ' does not exist') % locals())
386 388
387 config_ini_file = os.path.join(tracker_home, CoreConfig.INI_FILE) 389 config_ini_file = os.path.join(tracker_home, CoreConfig.INI_FILE)
388 # check for both old- and new-style configs 390 # check for both old- and new-style configs
389 if list(filter(os.path.exists, [config_ini_file, 391 if list(filter(os.path.exists, [config_ini_file,
390 os.path.join(tracker_home, 'config.py')])): 392 os.path.join(tracker_home, 'config.py')])):
421 423
422 # Process configuration file definitions 424 # Process configuration file definitions
423 if len(args) > 3: 425 if len(args) > 3:
424 try: 426 try:
425 defns = dict([item.split("=") for item in args[3].split(",")]) 427 defns = dict([item.split("=") for item in args[3].split(",")])
426 except: 428 except Exception:
427 print(_('Error in configuration settings: "%s"') % args[3]) 429 print(_('Error in configuration settings: "%s"') % args[3])
428 raise 430 raise
429 else: 431 else:
430 defns = {} 432 defns = {}
431 433
432 defns['rdbms_backend'] = backend 434 defns['rdbms_backend'] = backend
433 435
434 # load config_ini.ini from template if it exists. 436 # load config_ini.ini from template if it exists.
435 # it sets parameters like template_engine that are 437 # it sets parameters like template_engine that are
436 # template specific. 438 # template specific.
437 template_config=UserConfig(templates[template]['path'] + 439 template_config = UserConfig(templates[template]['path'] +
438 "/config_ini.ini") 440 "/config_ini.ini")
439 for k in template_config.keys(): 441 for k in template_config.keys():
440 if k == 'HOME': # ignore home. It is a default param. 442 if k == 'HOME': # ignore home. It is a default param.
441 continue 443 continue
442 defns[k] = template_config[k] 444 defns[k] = template_config[k]
443 445
444 # install! 446 # install!
445 init.install(tracker_home, templates[template]['path'], settings=defns) 447 init.install(tracker_home, templates[template]['path'], settings=defns)
542 adminpw = getpass.getpass(_('Admin Password: ')) 544 adminpw = getpass.getpass(_('Admin Password: '))
543 confirm = getpass.getpass(_(' Confirm: ')) 545 confirm = getpass.getpass(_(' Confirm: '))
544 546
545 # make sure the tracker home is installed 547 # make sure the tracker home is installed
546 if not os.path.exists(tracker_home): 548 if not os.path.exists(tracker_home):
547 raise UsageError(_('Instance home does not exist')%locals()) 549 raise UsageError(_('Instance home does not exist') % locals())
548 try: 550 try:
549 tracker = roundup.instance.open(tracker_home) 551 tracker = roundup.instance.open(tracker_home)
550 except roundup.instance.TrackerError: 552 except roundup.instance.TrackerError:
551 raise UsageError(_('Instance has not been installed')%locals()) 553 raise UsageError(_('Instance has not been installed') % locals())
552 554
553 # is there already a database? 555 # is there already a database?
554 if tracker.exists(): 556 if tracker.exists():
555 if not self.force: 557 if not self.force:
556 ok = my_input(_( 558 ok = my_input(_(
563 # nuke it 565 # nuke it
564 tracker.nuke() 566 tracker.nuke()
565 567
566 # GO 568 # GO
567 tracker.init(password.Password(adminpw, config=tracker.config), 569 tracker.init(password.Password(adminpw, config=tracker.config),
568 tx_Source = 'cli') 570 tx_Source='cli')
569 571
570 return 0 572 return 0
571
572 573
573 def do_get(self, args): 574 def do_get(self, args):
574 ''"""Usage: get property designator[,designator]* 575 ''"""Usage: get property designator[,designator]*
575 Get the given property of one or more designator(s). 576 Get the given property of one or more designator(s).
576 577
593 raise UsageError(message) 594 raise UsageError(message)
594 595
595 # get the class 596 # get the class
596 cl = self.get_class(classname) 597 cl = self.get_class(classname)
597 try: 598 try:
598 id=[] 599 id = []
599 if self.separator: 600 if self.separator:
600 if self.print_designator: 601 if self.print_designator:
601 # see if property is a link or multilink for 602 # see if property is a link or multilink for
602 # which getting a desginator make sense. 603 # which getting a desginator make sense.
603 # Algorithm: Get the properties of the 604 # Algorithm: Get the properties of the
611 # append the new designators 612 # append the new designators
612 # print 613 # print
613 properties = cl.getprops() 614 properties = cl.getprops()
614 property = properties[propname] 615 property = properties[propname]
615 if not (isinstance(property, hyperdb.Multilink) or 616 if not (isinstance(property, hyperdb.Multilink) or
616 isinstance(property, hyperdb.Link)): 617 isinstance(property, hyperdb.Link)):
617 raise UsageError(_('property %s is not of type' 618 raise UsageError(_('property %s is not of type'
618 ' Multilink or Link so -d flag does not ' 619 ' Multilink or Link so -d flag does not '
619 'apply.')%propname) 620 'apply.') % propname)
620 propclassname = self.db.getclass(property.classname).classname 621 propclassname = self.db.getclass(property.classname).classname
621 id = cl.get(nodeid, propname) 622 id = cl.get(nodeid, propname)
622 for i in id: 623 for i in id:
623 l.append(propclassname + i) 624 l.append(propclassname + i)
624 else: 625 else:
628 else: 629 else:
629 if self.print_designator: 630 if self.print_designator:
630 properties = cl.getprops() 631 properties = cl.getprops()
631 property = properties[propname] 632 property = properties[propname]
632 if not (isinstance(property, hyperdb.Multilink) or 633 if not (isinstance(property, hyperdb.Multilink) or
633 isinstance(property, hyperdb.Link)): 634 isinstance(property, hyperdb.Link)):
634 raise UsageError(_('property %s is not of type' 635 raise UsageError(_('property %s is not of type'
635 ' Multilink or Link so -d flag does not ' 636 ' Multilink or Link so -d flag does not '
636 'apply.')%propname) 637 'apply.') % propname)
637 propclassname = self.db.getclass(property.classname).classname 638 propclassname = self.db.getclass(property.classname).classname
638 id = cl.get(nodeid, propname) 639 id = cl.get(nodeid, propname)
639 for i in id: 640 for i in id:
640 print(propclassname + i) 641 print(propclassname + i)
641 else: 642 else:
642 print(cl.get(nodeid, propname)) 643 print(cl.get(nodeid, propname))
643 except IndexError: 644 except IndexError:
644 raise UsageError(_('no such %(classname)s node ' 645 raise UsageError(_('no such %(classname)s node '
645 '"%(nodeid)s"')%locals()) 646 '"%(nodeid)s"') % locals())
646 except KeyError: 647 except KeyError:
647 raise UsageError(_('no such %(classname)s property ' 648 raise UsageError(_('no such %(classname)s property '
648 '"%(propname)s"')%locals()) 649 '"%(propname)s"') % locals())
649 if self.separator: 650 if self.separator:
650 print(self.separator.join(l)) 651 print(self.separator.join(l))
651 652
652 return 0 653 return 0
653
654 654
655 def do_set(self, args): 655 def do_set(self, args):
656 ''"""Usage: set items property=value property=value ... 656 ''"""Usage: set items property=value property=value ...
657 Set the given properties of one or more items(s). 657 Set the given properties of one or more items(s).
658 658
665 This command sets the properties to the values for all designators 665 This command sets the properties to the values for all designators
666 given. If the value is missing (ie. "property=") then the property 666 given. If the value is missing (ie. "property=") then the property
667 is un-set. If the property is a multilink, you specify the linked 667 is un-set. If the property is a multilink, you specify the linked
668 ids for the multilink as comma-separated numbers (ie "1,2,3"). 668 ids for the multilink as comma-separated numbers (ie "1,2,3").
669 """ 669 """
670 import copy # needed for copying props list 670 import copy # needed for copying props list
671 671
672 if len(args) < 2: 672 if len(args) < 2:
673 raise UsageError(_('Not enough arguments supplied')) 673 raise UsageError(_('Not enough arguments supplied'))
674 from roundup import hyperdb 674 from roundup import hyperdb
675 675
687 designators = [hyperdb.splitDesignator(x) for x in designators] 687 designators = [hyperdb.splitDesignator(x) for x in designators]
688 except hyperdb.DesignatorError as message: 688 except hyperdb.DesignatorError as message:
689 raise UsageError(message) 689 raise UsageError(message)
690 690
691 # get the props from the args 691 # get the props from the args
692 propset = self.props_from_args(args[1:]) # parse the cli once 692 propset = self.props_from_args(args[1:]) # parse the cli once
693 693
694 # now do the set for all the nodes 694 # now do the set for all the nodes
695 for classname, itemid in designators: 695 for classname, itemid in designators:
696 props = copy.copy(propset) # make a new copy for every designator 696 props = copy.copy(propset) # make a new copy for every designator
697 cl = self.get_class(classname) 697 cl = self.get_class(classname)
698 698
699 for key, value in list(props.items()): 699 for key, value in list(props.items()):
700 try: 700 try:
701 # You must reinitialize the props every time though. 701 # You must reinitialize the props every time though.
703 # set to 'demo,admin' (assuming it was set to demo 703 # set to 'demo,admin' (assuming it was set to demo
704 # in the db) after rawToHyperdb returns. 704 # in the db) after rawToHyperdb returns.
705 # This new value is used for all the rest of the 705 # This new value is used for all the rest of the
706 # designators if not reinitalized. 706 # designators if not reinitalized.
707 props[key] = hyperdb.rawToHyperdb(self.db, cl, itemid, 707 props[key] = hyperdb.rawToHyperdb(self.db, cl, itemid,
708 key, value) 708 key, value)
709 except hyperdb.HyperdbValueError as message: 709 except hyperdb.HyperdbValueError as message:
710 raise UsageError(message) 710 raise UsageError(message)
711 711
712 # try the set 712 # try the set
713 try: 713 try:
760 if self.separator: 760 if self.separator:
761 if self.print_designator: 761 if self.print_designator:
762 id = cl.filter(None, **props) 762 id = cl.filter(None, **props)
763 for i in id: 763 for i in id:
764 designator.append(classname + i) 764 designator.append(classname + i)
765 print(self.separator.join(designator), file=sys.stdout) 765 print(self.separator.join(designator))
766 else: 766 else:
767 print(self.separator.join(cl.find(**props)), 767 print(self.separator.join(cl.find(**props)))
768 file=sys.stdout)
769 else: 768 else:
770 if self.print_designator: 769 if self.print_designator:
771 id = cl.filter(None, **props) 770 id = cl.filter(None, **props)
772 for i in id: 771 for i in id:
773 designator.append(classname + i) 772 designator.append(classname + i)
774 print(designator,file=sys.stdout) 773 print(designator)
775 else: 774 else:
776 print(cl.filter(None, **props), file=sys.stdout) 775 print(cl.filter(None, **props))
777 except KeyError: 776 except KeyError:
778 raise UsageError(_('%(classname)s has no property ' 777 raise UsageError(_('%(classname)s has no property '
779 '"%(propname)s"') % locals()) 778 '"%(propname)s"') % locals())
780 except (ValueError, TypeError) as message: 779 except (ValueError, TypeError) as message:
781 raise UsageError(message) 780 raise UsageError(message)
804 values = value.split(',') 803 values = value.split(',')
805 else: 804 else:
806 values = [value] 805 values = [value]
807 d = props[propname] = {} 806 d = props[propname] = {}
808 for value in values: 807 for value in values:
809 value = hyperdb.rawToHyperdb(self.db, cl, None, propname, value) 808 value = hyperdb.rawToHyperdb(self.db, cl, None,
809 propname, value)
810 if isinstance(value, list): 810 if isinstance(value, list):
811 for entry in value: 811 for entry in value:
812 d[entry] = 1 812 d[entry] = 1
813 else: 813 else:
814 d[value] = 1 814 d[value] = 1
834 print(designator) 834 print(designator)
835 else: 835 else:
836 print(cl.find(**props)) 836 print(cl.find(**props))
837 except KeyError: 837 except KeyError:
838 raise UsageError(_('%(classname)s has no property ' 838 raise UsageError(_('%(classname)s has no property '
839 '"%(propname)s"')%locals()) 839 '"%(propname)s"') % locals())
840 except (ValueError, TypeError) as message: 840 except (ValueError, TypeError) as message:
841 raise UsageError(message) 841 raise UsageError(message)
842 return 0 842 return 0
843 843
844 def do_specification(self, args): 844 def do_specification(self, args):
856 # get the key property 856 # get the key property
857 keyprop = cl.getkey() 857 keyprop = cl.getkey()
858 for key in cl.properties: 858 for key in cl.properties:
859 value = cl.properties[key] 859 value = cl.properties[key]
860 if keyprop == key: 860 if keyprop == key:
861 sys.stdout.write( _('%(key)s: %(value)s (key property)\n')%locals() ) 861 sys.stdout.write(_('%(key)s: %(value)s (key property)\n') %
862 locals())
862 else: 863 else:
863 sys.stdout.write( _('%(key)s: %(value)s\n')%locals() ) 864 sys.stdout.write(_('%(key)s: %(value)s\n') % locals())
864 865
865 def do_display(self, args): 866 def do_display(self, args):
866 ''"""Usage: display designator[,designator]* 867 ''"""Usage: display designator[,designator]*
867 Show the property values for the given node(s). 868 Show the property values for the given node(s).
868 869
887 888
888 # display the values 889 # display the values
889 keys = sorted(cl.properties) 890 keys = sorted(cl.properties)
890 for key in keys: 891 for key in keys:
891 value = cl.get(nodeid, key) 892 value = cl.get(nodeid, key)
892 print(_('%(key)s: %(value)s')%locals()) 893 print(_('%(key)s: %(value)s') % locals())
893 894
894 def do_create(self, args): 895 def do_create(self, args):
895 ''"""Usage: create classname property=value ... 896 ''"""Usage: create classname property=value ...
896 Create a new entry of a given class. 897 Create a new entry of a given class.
897 898
908 # get the class 909 # get the class
909 cl = self.get_class(classname) 910 cl = self.get_class(classname)
910 911
911 # now do a create 912 # now do a create
912 props = {} 913 props = {}
913 properties = cl.getprops(protected = 0) 914 properties = cl.getprops(protected=0)
914 if len(args) == 1: 915 if len(args) == 1:
915 # ask for the properties 916 # ask for the properties
916 for key in properties: 917 for key in properties:
917 if key == 'id': continue 918 if key == 'id': continue
918 value = properties[key] 919 value = properties[key]
919 name = value.__class__.__name__ 920 name = value.__class__.__name__
920 if isinstance(value , hyperdb.Password): 921 if isinstance(value, hyperdb.Password):
921 again = None 922 again = None
922 while value != again: 923 while value != again:
923 value = getpass.getpass(_('%(propname)s (Password): ')%{ 924 value = getpass.getpass(_('%(propname)s (Password): ')
924 'propname': key.capitalize()}) 925 %
925 again = getpass.getpass(_(' %(propname)s (Again): ')%{ 926 {'propname': key.capitalize()})
926 'propname': key.capitalize()}) 927 again = getpass.getpass(_(' %(propname)s (Again): ')
928 %
929 {'propname': key.capitalize()})
927 if value != again: print(_('Sorry, try again...')) 930 if value != again: print(_('Sorry, try again...'))
928 if value: 931 if value:
929 props[key] = value 932 props[key] = value
930 else: 933 else:
931 value = my_input(_('%(propname)s (%(proptype)s): ')%{ 934 value = my_input(_('%(propname)s (%(proptype)s): ') % {
932 'propname': key.capitalize(), 'proptype': name}) 935 'propname': key.capitalize(), 'proptype': name})
933 if value: 936 if value:
934 props[key] = value 937 props[key] = value
935 else: 938 else:
936 props = self.props_from_args(args[1:]) 939 props = self.props_from_args(args[1:])
945 948
946 # check for the key property 949 # check for the key property
947 propname = cl.getkey() 950 propname = cl.getkey()
948 if propname and propname not in props: 951 if propname and propname not in props:
949 raise UsageError(_('you must provide the "%(propname)s" ' 952 raise UsageError(_('you must provide the "%(propname)s" '
950 'property.')%locals()) 953 'property.') % locals())
951 954
952 # do the actual create 955 # do the actual create
953 try: 956 try:
954 sys.stdout.write(cl.create(**props) + '\n') 957 sys.stdout.write(cl.create(**props) + '\n')
955 except (TypeError, IndexError, ValueError) as message: 958 except (TypeError, IndexError, ValueError) as message:
973 if len(args) > 2: 976 if len(args) > 2:
974 raise UsageError(_('Too many arguments supplied')) 977 raise UsageError(_('Too many arguments supplied'))
975 if len(args) < 1: 978 if len(args) < 1:
976 raise UsageError(_('Not enough arguments supplied')) 979 raise UsageError(_('Not enough arguments supplied'))
977 classname = args[0] 980 classname = args[0]
978 981
979 # get the class 982 # get the class
980 cl = self.get_class(classname) 983 cl = self.get_class(classname)
981 984
982 # figure the property 985 # figure the property
983 if len(args) > 1: 986 if len(args) > 1:
986 propname = cl.labelprop() 989 propname = cl.labelprop()
987 990
988 if self.separator: 991 if self.separator:
989 if len(args) == 2: 992 if len(args) == 2:
990 # create a list of propnames since user specified propname 993 # create a list of propnames since user specified propname
991 proplist=[] 994 proplist = []
992 for nodeid in cl.list(): 995 for nodeid in cl.list():
993 try: 996 try:
994 proplist.append(cl.get(nodeid, propname)) 997 proplist.append(cl.get(nodeid, propname))
995 except KeyError: 998 except KeyError:
996 raise UsageError(_('%(classname)s has no property ' 999 raise UsageError(_('%(classname)s has no property '
997 '"%(propname)s"')%locals()) 1000 '"%(propname)s"') % locals())
998 print(self.separator.join(proplist)) 1001 print(self.separator.join(proplist))
999 else: 1002 else:
1000 # create a list of index id's since user didn't specify 1003 # create a list of index id's since user didn't specify
1001 # otherwise 1004 # otherwise
1002 print(self.separator.join(cl.list())) 1005 print(self.separator.join(cl.list()))
1004 for nodeid in cl.list(): 1007 for nodeid in cl.list():
1005 try: 1008 try:
1006 value = cl.get(nodeid, propname) 1009 value = cl.get(nodeid, propname)
1007 except KeyError: 1010 except KeyError:
1008 raise UsageError(_('%(classname)s has no property ' 1011 raise UsageError(_('%(classname)s has no property '
1009 '"%(propname)s"')%locals()) 1012 '"%(propname)s"') % locals())
1010 print(_('%(nodeid)4s: %(value)s')%locals()) 1013 print(_('%(nodeid)4s: %(value)s') % locals())
1011 return 0 1014 return 0
1012 1015
1013 def do_table(self, args): 1016 def do_table(self, args):
1014 ''"""Usage: table classname [property[,property]*] 1017 ''"""Usage: table classname [property[,property]*]
1015 List the instances of a class in tabular form. 1018 List the instances of a class in tabular form.
1054 if ':' in spec: 1057 if ':' in spec:
1055 try: 1058 try:
1056 propname, width = spec.split(':') 1059 propname, width = spec.split(':')
1057 except (ValueError, TypeError): 1060 except (ValueError, TypeError):
1058 raise UsageError(_('"%(spec)s" not ' 1061 raise UsageError(_('"%(spec)s" not '
1059 'name:width')%locals()) 1062 'name:width') % locals())
1060 else: 1063 else:
1061 propname = spec 1064 propname = spec
1062 if propname not in all_props: 1065 if propname not in all_props:
1063 raise UsageError(_('%(classname)s has no property ' 1066 raise UsageError(_('%(classname)s has no property '
1064 '"%(propname)s"')%locals()) 1067 '"%(propname)s"') % locals())
1065 else: 1068 else:
1066 prop_names = cl.getprops() 1069 prop_names = cl.getprops()
1067 1070
1068 # now figure column widths 1071 # now figure column widths
1069 props = [] 1072 props = []
1073 if width == '': 1076 if width == '':
1074 props.append((name, len(spec))) 1077 props.append((name, len(spec)))
1075 else: 1078 else:
1076 props.append((name, int(width))) 1079 props.append((name, int(width)))
1077 else: 1080 else:
1078 # this is going to be slow 1081 # this is going to be slow
1079 maxlen = len(spec) 1082 maxlen = len(spec)
1080 for nodeid in cl.list(): 1083 for nodeid in cl.list():
1081 curlen = len(str(cl.get(nodeid, spec))) 1084 curlen = len(str(cl.get(nodeid, spec)))
1082 if curlen > maxlen: 1085 if curlen > maxlen:
1083 maxlen = curlen 1086 maxlen = curlen
1084 props.append((spec, maxlen)) 1087 props.append((spec, maxlen))
1085 1088
1086 # now display the heading 1089 # now display the heading
1087 print(' '.join([name.capitalize().ljust(width) for name,width in props])) 1090 print(' '.join([name.capitalize().ljust(width)
1091 for name, width in props]))
1088 1092
1089 # and the table data 1093 # and the table data
1090 for nodeid in cl.list(): 1094 for nodeid in cl.list():
1091 l = [] 1095 l = []
1092 for name, width in props: 1096 for name, width in props:
1098 # KeyError here means the node just doesn't have a 1102 # KeyError here means the node just doesn't have a
1099 # value for it 1103 # value for it
1100 value = '' 1104 value = ''
1101 else: 1105 else:
1102 value = str(nodeid) 1106 value = str(nodeid)
1103 f = '%%-%ds'%width 1107 f = '%%-%ds' % width
1104 l.append(f%value[:width]) 1108 l.append(f % value[:width])
1105 print(' '.join(l)) 1109 print(' '.join(l))
1106 return 0 1110 return 0
1107 1111
1108 def do_history(self, args): 1112 def do_history(self, args):
1109 ''"""Usage: history designator [skipquiet] 1113 ''"""Usage: history designator [skipquiet]
1133 1137
1134 try: 1138 try:
1135 print(self.db.getclass(classname).history(nodeid, 1139 print(self.db.getclass(classname).history(nodeid,
1136 skipquiet=skipquiet)) 1140 skipquiet=skipquiet))
1137 except KeyError: 1141 except KeyError:
1138 raise UsageError(_('no such class "%(classname)s"')%locals()) 1142 raise UsageError(_('no such class "%(classname)s"') % locals())
1139 except IndexError: 1143 except IndexError:
1140 raise UsageError(_('no such %(classname)s node ' 1144 raise UsageError(_('no such %(classname)s node '
1141 '"%(nodeid)s"')%locals()) 1145 '"%(nodeid)s"') % locals())
1142 return 0 1146 return 0
1143 1147
1144 def do_commit(self, args): 1148 def do_commit(self, args):
1145 ''"""Usage: commit 1149 ''"""Usage: commit
1146 Commit changes made to the database during an interactive session. 1150 Commit changes made to the database during an interactive session.
1188 except hyperdb.DesignatorError as message: 1192 except hyperdb.DesignatorError as message:
1189 raise UsageError(message) 1193 raise UsageError(message)
1190 try: 1194 try:
1191 self.db.getclass(classname).retire(nodeid) 1195 self.db.getclass(classname).retire(nodeid)
1192 except KeyError: 1196 except KeyError:
1193 raise UsageError(_('no such class "%(classname)s"')%locals()) 1197 raise UsageError(_('no such class "%(classname)s"') % locals())
1194 except IndexError: 1198 except IndexError:
1195 raise UsageError(_('no such %(classname)s node ' 1199 raise UsageError(_('no such %(classname)s node '
1196 '"%(nodeid)s"')%locals()) 1200 '"%(nodeid)s"') % locals())
1197 self.db_uncommitted = True 1201 self.db_uncommitted = True
1198 return 0 1202 return 0
1199 1203
1200 def do_restore(self, args): 1204 def do_restore(self, args):
1201 ''"""Usage: restore designator[,designator]* 1205 ''"""Usage: restore designator[,designator]*
1215 except hyperdb.DesignatorError as message: 1219 except hyperdb.DesignatorError as message:
1216 raise UsageError(message) 1220 raise UsageError(message)
1217 try: 1221 try:
1218 self.db.getclass(classname).restore(nodeid) 1222 self.db.getclass(classname).restore(nodeid)
1219 except KeyError: 1223 except KeyError:
1220 raise UsageError(_('no such class "%(classname)s"')%locals()) 1224 raise UsageError(_('no such class "%(classname)s"') % locals())
1221 except IndexError: 1225 except IndexError:
1222 raise UsageError(_('no such %(classname)s node ' 1226 raise UsageError(_('no such %(classname)s node '
1223 '"%(nodeid)s"')%locals()) 1227 '" % (nodeid)s"')%locals())
1224 self.db_uncommitted = True 1228 self.db_uncommitted = True
1225 return 0 1229 return 0
1226 1230
1227 def do_export(self, args, export_files=True): 1231 def do_export(self, args, export_files=True):
1228 ''"""Usage: export [[-]class[,class]] export_dir 1232 ''"""Usage: export [[-]class[,class]] export_dir
1244 dir = args[-1] 1248 dir = args[-1]
1245 1249
1246 # get the list of classes to export 1250 # get the list of classes to export
1247 if len(args) == 2: 1251 if len(args) == 2:
1248 if args[0].startswith('-'): 1252 if args[0].startswith('-'):
1249 classes = [ c for c in self.db.classes 1253 classes = [c for c in self.db.classes
1250 if not c in args[0][1:].split(',') ] 1254 if c not in args[0][1:].split(',')]
1251 else: 1255 else:
1252 classes = args[0].split(',') 1256 classes = args[0].split(',')
1253 else: 1257 else:
1254 classes = self.db.classes 1258 classes = self.db.classes
1255 1259
1266 # do all the classes specified 1270 # do all the classes specified
1267 for classname in classes: 1271 for classname in classes:
1268 cl = self.get_class(classname) 1272 cl = self.get_class(classname)
1269 1273
1270 if not export_files and hasattr(cl, 'export_files'): 1274 if not export_files and hasattr(cl, 'export_files'):
1271 sys.stdout.write('Exporting %s WITHOUT the files\r\n'% 1275 sys.stdout.write('Exporting %s WITHOUT the files\r\n' %
1272 classname) 1276 classname)
1273 1277
1274 f = open(os.path.join(dir, classname+'.csv'), 'w') 1278 f = open(os.path.join(dir, classname+'.csv'), 'w')
1275 writer = csv.writer(f, colon_separated) 1279 writer = csv.writer(f, colon_separated)
1276 1280
1281 writer.writerow(fields) 1285 writer.writerow(fields)
1282 1286
1283 # all nodes for this class 1287 # all nodes for this class
1284 for nodeid in cl.getnodeids(): 1288 for nodeid in cl.getnodeids():
1285 if self.verbose: 1289 if self.verbose:
1286 sys.stdout.write('\rExporting %s - %s'%(classname, nodeid)) 1290 sys.stdout.write('\rExporting %s - %s' %
1291 (classname, nodeid))
1287 sys.stdout.flush() 1292 sys.stdout.flush()
1288 node = cl.getnode(nodeid) 1293 node = cl.getnode(nodeid)
1289 exp = cl.export_list(propnames, nodeid) 1294 exp = cl.export_list(propnames, nodeid)
1290 lensum = sum ([len (repr_export(node[p])) for p in propnames]) 1295 lensum = sum([len(repr_export(node[p])) for p in propnames])
1291 # for a safe upper bound of field length we add 1296 # for a safe upper bound of field length we add
1292 # difference between CSV len and sum of all field lengths 1297 # difference between CSV len and sum of all field lengths
1293 d = sum ([len(x) for x in exp]) - lensum 1298 d = sum([len(x) for x in exp]) - lensum
1294 if not d > 0: 1299 if not d > 0:
1295 raise AssertionError("Bad assertion d > 0") 1300 raise AssertionError("Bad assertion d > 0")
1296 for p in propnames: 1301 for p in propnames:
1297 ll = len(repr_export(node[p])) + d 1302 ll = len(repr_export(node[p])) + d
1298 if ll > max_len: 1303 if ll > max_len:
1312 journals = csv.writer(jf, colon_separated) 1317 journals = csv.writer(jf, colon_separated)
1313 for row in cl.export_journals(): 1318 for row in cl.export_journals():
1314 journals.writerow(row) 1319 journals.writerow(row)
1315 jf.close() 1320 jf.close()
1316 if max_len > self.db.config.CSV_FIELD_SIZE: 1321 if max_len > self.db.config.CSV_FIELD_SIZE:
1317 print("Warning: config csv_field_size should be at least %s"%max_len, file=sys.stderr) 1322 print("Warning: config csv_field_size should be at least %s" %
1323 max_len, file=sys.stderr)
1318 return 0 1324 return 0
1319 1325
1320 def do_exporttables(self, args): 1326 def do_exporttables(self, args):
1321 ''"""Usage: exporttables [[-]class[,class]] export_dir 1327 ''"""Usage: exporttables [[-]class[,class]] export_dir
1322 Export the database to colon-separated-value files, excluding the 1328 Export the database to colon-separated-value files, excluding the
1352 create a new database using the imported data, then create a new 1358 create a new database using the imported data, then create a new
1353 database (or, tediously, retire all the old data.) 1359 database (or, tediously, retire all the old data.)
1354 """ 1360 """
1355 if len(args) < 1: 1361 if len(args) < 1:
1356 raise UsageError(_('Not enough arguments supplied')) 1362 raise UsageError(_('Not enough arguments supplied'))
1357 from roundup import hyperdb 1363
1358 1364 if hasattr(csv, 'field_size_limit'):
1359 if hasattr (csv, 'field_size_limit'):
1360 csv.field_size_limit(self.db.config.CSV_FIELD_SIZE) 1365 csv.field_size_limit(self.db.config.CSV_FIELD_SIZE)
1361 1366
1362 # directory to import from 1367 # directory to import from
1363 dir = args[0] 1368 dir = args[0]
1364 1369
1384 if file_props is None: 1389 if file_props is None:
1385 file_props = r 1390 file_props = r
1386 continue 1391 continue
1387 1392
1388 if self.verbose: 1393 if self.verbose:
1389 sys.stdout.write('\rImporting %s - %s'%(classname, n)) 1394 sys.stdout.write('\rImporting %s - %s' % (classname, n))
1390 sys.stdout.flush() 1395 sys.stdout.flush()
1391 1396
1392 # do the import and figure the current highest nodeid 1397 # do the import and figure the current highest nodeid
1393 nodeid = cl.import_list(file_props, r) 1398 nodeid = cl.import_list(file_props, r)
1394 if hasattr(cl, 'import_files'): 1399 if hasattr(cl, 'import_files'):
1445 m = date_re.match(value) 1450 m = date_re.match(value)
1446 if not m: 1451 if not m:
1447 raise ValueError(_('Invalid format')) 1452 raise ValueError(_('Invalid format'))
1448 m = m.groupdict() 1453 m = m.groupdict()
1449 if m['period']: 1454 if m['period']:
1450 pack_before = date.Date(". - %s"%value) 1455 pack_before = date.Date(". - %s" % value)
1451 elif m['date']: 1456 elif m['date']:
1452 pack_before = date.Date(value) 1457 pack_before = date.Date(value)
1453 self.db.pack(pack_before) 1458 self.db.pack(pack_before)
1454 self.db_uncommitted = True 1459 self.db_uncommitted = True
1455 return 0 1460 return 0
1467 if m: 1472 if m:
1468 cl = self.get_class(m.group(1)) 1473 cl = self.get_class(m.group(1))
1469 try: 1474 try:
1470 cl.index(m.group(2)) 1475 cl.index(m.group(2))
1471 except IndexError: 1476 except IndexError:
1472 raise UsageError(_('no such item "%(designator)s"')%{ 1477 raise UsageError(_('no such item "%(designator)s"') % {
1473 'designator': arg}) 1478 'designator': arg})
1474 else: 1479 else:
1475 cl = self.get_class(arg) 1480 cl = self.get_class(arg)
1476 self.db.reindex(arg) 1481 self.db.reindex(arg)
1477 else: 1482 else:
1485 if len(args) == 1: 1490 if len(args) == 1:
1486 role = args[0] 1491 role = args[0]
1487 try: 1492 try:
1488 roles = [(args[0], self.db.security.role[args[0]])] 1493 roles = [(args[0], self.db.security.role[args[0]])]
1489 except KeyError: 1494 except KeyError:
1490 sys.stdout.write( _('No such Role "%(role)s"\n')%locals() ) 1495 sys.stdout.write(_('No such Role "%(role)s"\n') % locals())
1491 return 1 1496 return 1
1492 else: 1497 else:
1493 roles = list(self.db.security.role.items()) 1498 roles = list(self.db.security.role.items())
1494 role = self.db.config.NEW_WEB_USER_ROLES 1499 role = self.db.config.NEW_WEB_USER_ROLES
1495 if ',' in role: 1500 if ',' in role:
1496 sys.stdout.write( _('New Web users get the Roles "%(role)s"\n')%locals() ) 1501 sys.stdout.write(_('New Web users get the Roles "%(role)s"\n')
1502 % locals())
1497 else: 1503 else:
1498 sys.stdout.write( _('New Web users get the Role "%(role)s"\n')%locals() ) 1504 sys.stdout.write(_('New Web users get the Role "%(role)s"\n')
1505 % locals())
1499 role = self.db.config.NEW_EMAIL_USER_ROLES 1506 role = self.db.config.NEW_EMAIL_USER_ROLES
1500 if ',' in role: 1507 if ',' in role:
1501 sys.stdout.write( _('New Email users get the Roles "%(role)s"\n')%locals() ) 1508 sys.stdout.write(_('New Email users get the Roles "%(role)s"\n') % locals())
1502 else: 1509 else:
1503 sys.stdout.write( _('New Email users get the Role "%(role)s"\n')%locals() ) 1510 sys.stdout.write(_('New Email users get the Role "%(role)s"\n') % locals())
1504 roles.sort() 1511 roles.sort()
1505 for rolename, role in roles: 1512 for _rolename, role in roles:
1506 sys.stdout.write( _('Role "%(name)s":\n')%role.__dict__ ) 1513 sys.stdout.write(_('Role "%(name)s":\n') % role.__dict__)
1507 for permission in role.permissions: 1514 for permission in role.permissions:
1508 d = permission.__dict__ 1515 d = permission.__dict__
1509 if permission.klass: 1516 if permission.klass:
1510 if permission.properties: 1517 if permission.properties:
1511 sys.stdout.write( _(' %(description)s (%(name)s for "%(klass)s"' + 1518 sys.stdout.write(_(' %(description)s (%(name)s for "%(klass)s"' +
1512 ': %(properties)s only)\n')%d ) 1519 ': %(properties)s only)\n') % d)
1513 # verify that properties exist; report bad props 1520 # verify that properties exist; report bad props
1514 bad_props=[] 1521 bad_props = []
1515 cl = self.db.getclass(permission.klass) 1522 cl = self.db.getclass(permission.klass)
1516 class_props = cl.getprops(protected=True) 1523 class_props = cl.getprops(protected=True)
1517 for p in permission.properties: 1524 for p in permission.properties:
1518 if p in class_props: 1525 if p in class_props:
1519 continue 1526 continue
1520 else: 1527 else:
1521 bad_props.append(p) 1528 bad_props.append(p)
1522 if bad_props: 1529 if bad_props:
1523 sys.stdout.write( _('\n **Invalid properties for %(class)s: %(props)s\n\n') % { "class": permission.klass, "props": bad_props }) 1530 sys.stdout.write(_('\n **Invalid properties for %(class)s: %(props)s\n\n') % {"class": permission.klass, "props": bad_props})
1524 else: 1531 else:
1525 sys.stdout.write( _(' %(description)s (%(name)s for "%(klass)s" ' + 1532 sys.stdout.write(_(' %(description)s (%(name)s for '
1526 'only)\n')%d ) 1533 '"%(klass)s" only)\n') % d)
1527 else: 1534 else:
1528 sys.stdout.write( _(' %(description)s (%(name)s)\n')%d ) 1535 sys.stdout.write(_(' %(description)s (%(name)s)\n') % d)
1529 return 0 1536 return 0
1530
1531 1537
1532 def do_migrate(self, args): 1538 def do_migrate(self, args):
1533 ''"""Usage: migrate 1539 ''"""Usage: migrate
1534 Update a tracker's database to be compatible with the Roundup 1540 Update a tracker's database to be compatible with the Roundup
1535 codebase. 1541 codebase.
1536 1542
1537 You should run the "migrate" command for your tracker once you've 1543 You should run the "migrate" command for your tracker once you've
1538 installed the latest codebase. 1544 installed the latest codebase.
1539 1545
1540 Do this before you use the web, command-line or mail interface and 1546 Do this before you use the web, command-line or mail interface and
1541 before any users access the tracker. 1547 before any users access the tracker.
1542 1548
1543 This command will respond with either "Tracker updated" (if you've 1549 This command will respond with either "Tracker updated" (if you've
1560 """ 1566 """
1561 command = args[0] 1567 command = args[0]
1562 1568
1563 # handle help now 1569 # handle help now
1564 if command == 'help': 1570 if command == 'help':
1565 if len(args)>1: 1571 if len(args) > 1:
1566 self.do_help(args[1:]) 1572 self.do_help(args[1:])
1567 return 0 1573 return 0
1568 self.do_help(['help']) 1574 self.do_help(['help'])
1569 return 0 1575 return 0
1570 if command == 'morehelp': 1576 if command == 'morehelp':
1577 try: 1583 try:
1578 functions = self.commands.get(command) 1584 functions = self.commands.get(command)
1579 except KeyError: 1585 except KeyError:
1580 # not a valid command 1586 # not a valid command
1581 print(_('Unknown command "%(command)s" ("help commands" for a ' 1587 print(_('Unknown command "%(command)s" ("help commands" for a '
1582 'list)')%locals()) 1588 'list)') % locals())
1583 return 1 1589 return 1
1584 1590
1585 # check for multiple matches 1591 # check for multiple matches
1586 if len(functions) > 1: 1592 if len(functions) > 1:
1587 print(_('Multiple commands match "%(command)s": %(list)s')%{'command': 1593 print(_('Multiple commands match "%(command)s": %(list)s') % \
1588 command, 'list': ', '.join([i[0] for i in functions])}) 1594 {'command': command,
1595 'list': ', '.join([i[0] for i in functions])})
1589 return 1 1596 return 1
1590 command, function = functions[0] 1597 command, function = functions[0]
1591 1598
1592 # make sure we have a tracker_home 1599 # make sure we have a tracker_home
1593 while not self.tracker_home: 1600 while not self.tracker_home:
1598 1605
1599 # before we open the db, we may be doing an install or init 1606 # before we open the db, we may be doing an install or init
1600 if command == 'initialise': 1607 if command == 'initialise':
1601 try: 1608 try:
1602 return self.do_initialise(self.tracker_home, args) 1609 return self.do_initialise(self.tracker_home, args)
1603 except UsageError as message: 1610 except UsageError as message: # noqa: F841
1604 print(_('Error: %(message)s')%locals()) 1611 print(_('Error: %(message)s') % locals())
1605 return 1 1612 return 1
1606 elif command == 'install': 1613 elif command == 'install':
1607 try: 1614 try:
1608 return self.do_install(self.tracker_home, args) 1615 return self.do_install(self.tracker_home, args)
1609 except UsageError as message: 1616 except UsageError as message: # noqa: F841
1610 print(_('Error: %(message)s')%locals()) 1617 print(_('Error: %(message)s') % locals())
1611 return 1 1618 return 1
1612 1619
1613 # get the tracker 1620 # get the tracker
1614 try: 1621 try:
1615 tracker = roundup.instance.open(self.tracker_home) 1622 tracker = roundup.instance.open(self.tracker_home)
1616 except ValueError as message: 1623 except ValueError as message: # noqa: F841
1617 self.tracker_home = '' 1624 self.tracker_home = ''
1618 print(_("Error: Couldn't open tracker: %(message)s")%locals()) 1625 print(_("Error: Couldn't open tracker: %(message)s") % locals())
1619 return 1 1626 return 1
1620 except NoConfigError as message: 1627 except NoConfigError as message: # noqa: F841
1621 self.tracker_home = '' 1628 self.tracker_home = ''
1622 print(_("Error: Couldn't open tracker: %(message)s")%locals()) 1629 print(_("Error: Couldn't open tracker: %(message)s") % locals())
1623 return 1 1630 return 1
1624 1631
1625 # only open the database once! 1632 # only open the database once!
1626 if not self.db: 1633 if not self.db:
1627 self.db = tracker.open(self.name) 1634 self.db = tracker.open(self.name)
1630 1637
1631 # do the command 1638 # do the command
1632 ret = 0 1639 ret = 0
1633 try: 1640 try:
1634 ret = function(args[1:]) 1641 ret = function(args[1:])
1635 except UsageError as message: 1642 except UsageError as message: # noqa: F841
1636 print(_('Error: %(message)s')%locals()) 1643 print(_('Error: %(message)s') % locals())
1637 print() 1644 print()
1638 print(function.__doc__) 1645 print(function.__doc__)
1639 ret = 1 1646 ret = 1
1640 except: 1647 except Exception:
1641 import traceback 1648 import traceback
1642 traceback.print_exc() 1649 traceback.print_exc()
1643 ret = 1 1650 ret = 1
1644 return ret 1651 return ret
1645 1652
1646 def interactive(self): 1653 def interactive(self):
1647 """Run in an interactive mode 1654 """Run in an interactive mode
1648 """ 1655 """
1649 print(_('Roundup %s ready for input.\nType "help" for help.' 1656 print(_('Roundup %s ready for input.\nType "help" for help.'
1650 % roundup_version)) 1657 % roundup_version))
1651 try: 1658 try:
1652 import readline 1659 import readline # noqa: F401
1653 except ImportError: 1660 except ImportError:
1654 print(_('Note: command history and editing not available')) 1661 print(_('Note: command history and editing not available'))
1655 1662
1656 while 1: 1663 while 1:
1657 try: 1664 try:
1661 break 1668 break
1662 if not command: continue 1669 if not command: continue
1663 try: 1670 try:
1664 args = token.token_split(command) 1671 args = token.token_split(command)
1665 except ValueError: 1672 except ValueError:
1666 continue # Ignore invalid quoted token 1673 continue # Ignore invalid quoted token
1667 if not args: continue 1674 if not args: continue
1668 if args[0] in ('quit', 'exit'): break 1675 if args[0] in ('quit', 'exit'): break
1669 self.run_command(args) 1676 self.run_command(args)
1670 1677
1671 # exit.. check for transactions 1678 # exit.. check for transactions
1697 for opt, arg in opts: 1704 for opt, arg in opts:
1698 if opt == '-h': 1705 if opt == '-h':
1699 self.usage() 1706 self.usage()
1700 return 0 1707 return 0
1701 elif opt == '-v': 1708 elif opt == '-v':
1702 print('%s (python %s)'%(roundup_version, sys.version.split()[0])) 1709 print('%s (python %s)' % (roundup_version,
1710 sys.version.split()[0]))
1703 return 0 1711 return 0
1704 elif opt == '-V': 1712 elif opt == '-V':
1705 self.verbose = 1 1713 self.verbose = 1
1706 elif opt == '-i': 1714 elif opt == '-i':
1707 self.tracker_home = arg 1715 self.tracker_home = arg
1708 elif opt == '-c': 1716 elif opt == '-c':
1709 if self.separator != None: 1717 if self.separator is not None:
1710 self.usage('Only one of -c, -S and -s may be specified') 1718 self.usage('Only one of -c, -S and -s may be specified')
1711 return 1 1719 return 1
1712 self.separator = ',' 1720 self.separator = ','
1713 elif opt == '-S': 1721 elif opt == '-S':
1714 if self.separator != None: 1722 if self.separator is not None:
1715 self.usage('Only one of -c, -S and -s may be specified') 1723 self.usage('Only one of -c, -S and -s may be specified')
1716 return 1 1724 return 1
1717 self.separator = arg 1725 self.separator = arg
1718 elif opt == '-s': 1726 elif opt == '-s':
1719 if self.separator != None: 1727 if self.separator is not None:
1720 self.usage('Only one of -c, -S and -s may be specified') 1728 self.usage('Only one of -c, -S and -s may be specified')
1721 return 1 1729 return 1
1722 self.separator = ' ' 1730 self.separator = ' '
1723 elif opt == '-d': 1731 elif opt == '-d':
1724 self.print_designator = 1 1732 self.print_designator = 1
1740 return ret 1748 return ret
1741 finally: 1749 finally:
1742 if self.db: 1750 if self.db:
1743 self.db.close() 1751 self.db.close()
1744 1752
1753
1745 if __name__ == '__main__': 1754 if __name__ == '__main__':
1746 tool = AdminTool() 1755 tool = AdminTool()
1747 sys.exit(tool.main()) 1756 sys.exit(tool.main())
1748 1757
1749 # vim: set filetype=python sts=4 sw=4 et si : 1758 # vim: set filetype=python sts=4 sw=4 et si :

Roundup Issue Tracker: http://roundup-tracker.org/