root/adminconsoleproviderpatch/0.9/admin-provider-refactor.diff

Revision 96, 62.8 kB (checked in by athomas, 3 years ago)

AdminConsoleProviderPatch:

Updated for latest trunk

  • trac/ticket/api.py

    old new  
    2626from trac.perm import IPermissionRequestor 
    2727from trac.wiki import IWikiSyntaxProvider 
    2828from trac.Search import ISearchSource, query_to_sql, shorten_result 
     29from trac.scripts.admin import IAdminConsoleProvider 
    2930 
    30  
    3131class MyLinkResolver(Component): 
    3232    """ 
    3333    A dummy macro used by the unit test. We need to supply our own macro 
     
    200200                   date, author, 
    201201                   util.escape(shorten_result(desc, query.split()))) 
    202202             
     203class TicketAdminConsole(Component): 
     204    """ Provides trac-admin with ticket related commands """ 
     205 
     206    # IAdminConsoleProvider methods 
     207    implements(IAdminConsoleProvider) 
     208 
     209    def get_console_commands(self, tracadm): 
     210        self.tracadm = tracadm 
     211        from trac.ticket.model import Type, Priority, Severity 
     212        self._enum_map = {'ticket_type': Type, 'priority': Priority, 
     213            'severity': Severity } 
     214 
     215        yield ('ticket_type', self._help_ticket_type, self.do_ticket_type, self.complete_ticket_type) 
     216        yield ('severity', self._help_severity, self.do_severity, self.complete_severity) 
     217        yield ('priority', self._help_priority, self.do_priority, self.complete_priority) 
     218        yield ('component', self._help_component, self.do_component, self.complete_component) 
     219        yield ('version', [('version list', 'Show versions'), 
     220                           ('version add <name> [time]', 'Add version'), 
     221                           ('version rename <name> <newname>', 
     222                            'Rename version'), 
     223                           ('version time <name> <time>', 
     224                            'Set version date (Format: "%s" or "now")' 
     225                            % tracadm._date_format_hint), 
     226                       ('version remove <name>', 'Remove version')], 
     227                self.do_version, self.complete_version) 
     228 
     229 
     230 
     231    ## (Ticket) Type 
     232    _help_ticket_type = [('ticket_type list', 'Show possible ticket types'), 
     233                         ('ticket_type add <value>', 'Add a ticket type'), 
     234                         ('ticket_type change <value> <newvalue>', 
     235                          'Change a ticket type'), 
     236                         ('ticket_type remove <value>', 'Remove a ticket type')] 
     237 
     238    def complete_ticket_type (self, text, line, begidx, endidx): 
     239        if begidx == 16: 
     240            comp = self.get_enum_list ('ticket_type') 
     241        elif begidx < 15: 
     242            comp = ['list', 'add', 'change', 'remove'] 
     243        return self.tracadm.word_complete(text, comp) 
     244 
     245    def do_ticket_type(self, line): 
     246        self._do_enum('ticket_type', line) 
     247 
     248    ## (Ticket) Priority 
     249    _help_priority = [('priority list', 'Show possible ticket priorities'), 
     250                       ('priority add <value>', 'Add a priority value option'), 
     251                       ('priority change <value> <newvalue>', 
     252                        'Change a priority value'), 
     253                       ('priority remove <value>', 'Remove priority value')] 
     254 
     255    def complete_priority (self, text, line, begidx, endidx): 
     256        if begidx == 16: 
     257            comp = self.get_enum_list ('priority') 
     258        elif begidx < 15: 
     259            comp = ['list', 'add', 'change', 'remove'] 
     260        return self.tracadm.word_complete(text, comp) 
     261 
     262    def do_priority(self, line): 
     263        self._do_enum('priority', line) 
     264 
     265    ## (Ticket) Severity 
     266    _help_severity = [('severity list', 'Show possible ticket severities'), 
     267                      ('severity add <value>', 'Add a severity value option'), 
     268                      ('severity change <value> <newvalue>', 
     269                       'Change a severity value'), 
     270                      ('severity remove <value>', 'Remove severity value')] 
     271 
     272    def complete_severity (self, text, line, begidx, endidx): 
     273        if begidx == 16: 
     274            comp = self.get_enum_list ('severity') 
     275        elif begidx < 15: 
     276            comp = ['list', 'add', 'change', 'remove'] 
     277        return self.tracadm.word_complete(text, comp) 
     278 
     279    def do_severity(self, line): 
     280        self._do_enum('severity', line) 
     281 
     282    # Type, priority, severity share the same datastructure and methods: 
     283 
     284    def _do_enum(self, type, line): 
     285        arg = self.tracadm.arg_tokenize(line) 
     286        try: 
     287            if arg[0]  == 'list': 
     288                self._do_enum_list(type) 
     289            elif arg[0] == 'add' and len(arg)==2: 
     290                name = arg[1] 
     291                self._do_enum_add(type, name) 
     292            elif arg[0] == 'change'  and len(arg)==3: 
     293                name = arg[1] 
     294                newname = arg[2] 
     295                self._do_enum_change(type, name, newname) 
     296            elif arg[0] == 'remove'  and len(arg)==2: 
     297                name = arg[1] 
     298                self._do_enum_remove(type, name) 
     299            else: 
     300                self.tracadm.do_help (type) 
     301        except Exception, e: 
     302            print 'Command %s failed:' % arg[0], e 
     303 
     304    def _do_enum_list(self, type): 
     305        enum_cls = self._enum_map[type] 
     306        self.tracadm.print_listing(['Possible Values'], 
     307                           [(e.name,) for e in enum_cls.select(self.tracadm.env_open())]) 
     308 
     309    def _do_enum_add(self, type, name): 
     310        sql = ("INSERT INTO enum(value,type,name) " 
     311               " SELECT 1+COALESCE(max(value),0),'%(type)s','%(name)s'" 
     312               "   FROM enum WHERE type='%(type)s'" 
     313               % {'type':type, 'name':name}) 
     314        self.tracadm.db_update(sql) 
     315 
     316    def _do_enum_change(self, type, name, newname): 
     317        enum_cls = self._enum_map[type] 
     318        enum = enum_cls(self.tracadm.env_open(), name) 
     319        enum.name = newname 
     320        enum.update() 
     321 
     322    def _do_enum_remove(self, type, name): 
     323        enum_cls = self._enum_map[type] 
     324        enum = enum_cls(self.tracadm.env_open(), name) 
     325        enum.delete() 
     326 
     327    def get_enum_list(self, type): 
     328        rows = self.tracadm.db_query("SELECT name FROM enum WHERE type='%s'" % type) 
     329        return [row[0] for row in rows] 
     330 
     331    # Component 
     332    _help_component = [('component list', 'Show available components'), 
     333                       ('component add <name> <owner>', 'Add a new component'), 
     334                       ('component rename <name> <newname>', 
     335                        'Rename a component'), 
     336                       ('component remove <name>', 
     337                        'Remove/uninstall component'), 
     338                       ('component chown <name> <owner>', 
     339                        'Change component ownership')] 
     340 
     341    def complete_component(self, text, line, begidx, endidx): 
     342        if begidx in (16, 17): 
     343            comp = self.get_component_list() 
     344        elif begidx > 15 and line.startswith('component chown '): 
     345            comp = self.get_user_list() 
     346        else: 
     347            comp = ['list', 'add', 'rename', 'remove', 'chown'] 
     348        return self.tracadm.word_complete(text, comp) 
     349 
     350    def do_component(self, line): 
     351        arg = self.tracadm.arg_tokenize(line) 
     352        try: 
     353            if arg[0]  == 'list': 
     354                self._do_component_list() 
     355            elif arg[0] == 'add' and len(arg)==3: 
     356                name = arg[1] 
     357                owner = arg[2] 
     358                self._do_component_add(name, owner) 
     359            elif arg[0] == 'rename' and len(arg)==3: 
     360                name = arg[1] 
     361                newname = arg[2] 
     362                self._do_component_rename(name, newname) 
     363            elif arg[0] == 'remove'  and len(arg)==2: 
     364                name = arg[1] 
     365                self._do_component_remove(name) 
     366            elif arg[0] == 'chown' and len(arg)==3: 
     367                name = arg[1] 
     368                owner = arg[2] 
     369                self._do_component_set_owner(name, owner) 
     370            else: 
     371                self.tracadm.do_help ('component') 
     372        except Exception, e: 
     373            print 'Component %s failed:' % arg[0], e 
     374 
     375    def _do_component_list(self): 
     376        from trac.ticket.model import Component 
     377        data = [] 
     378        for c in Component.select(self.tracadm.env_open()): 
     379            data.append((c.name, c.owner)) 
     380        self.tracadm.print_listing(['Name', 'Owner'], data) 
     381 
     382    def _do_component_add(self, name, owner): 
     383        from trac.ticket.model import Component 
     384        component = Component(self.tracadm.env_open()) 
     385        component.name = name 
     386        component.owner = owner 
     387        component.insert() 
     388 
     389    def _do_component_rename(self, name, newname): 
     390        from trac.ticket.model import Component 
     391        component = Component(self.tracadm.env_open(), name) 
     392        component.name = newname 
     393        component.update() 
     394 
     395    def _do_component_remove(self, name): 
     396        from trac.ticket.model import Component 
     397        component = Component(self.tracadm.env_open(), name) 
     398        component.delete() 
     399 
     400    def _do_component_set_owner(self, name, owner): 
     401        from trac.ticket.model import Component 
     402        component = Component(self.tracadm.env_open(), name) 
     403        component.owner = owner 
     404        component.update() 
     405 
     406    def get_user_list(self): 
     407        rows = self.tracadm.db_query("SELECT DISTINCT username FROM permission") 
     408        return [row[0] for row in rows] 
     409 
     410    def get_component_list(self): 
     411        rows = self.tracadm.db_query("SELECT name FROM component") 
     412        return [row[0] for row in rows] 
     413 
     414    def complete_version (self, text, line, begidx, endidx): 
     415        if begidx in (13, 15): 
     416            comp = self.get_version_list() 
     417        elif begidx < 13: 
     418            comp = ['list', 'add', 'rename', 'time', 'remove'] 
     419        return self.tracadm.word_complete(text, comp) 
     420 
     421    def do_version(self, line): 
     422        arg = self.tracadm.arg_tokenize(line) 
     423        try: 
     424            if arg[0]  == 'list': 
     425                self._do_version_list() 
     426            elif arg[0] == 'add' and len(arg) in [2,3]: 
     427                self._do_version_add(arg[1]) 
     428                if len(arg) == 3: 
     429                    self._do_version_time(arg[1], arg[2]) 
     430            elif arg[0] == 'rename' and len(arg) == 3: 
     431                self._do_version_rename(arg[1], arg[2]) 
     432            elif arg[0] == 'time' and len(arg) == 3: 
     433                self._do_version_time(arg[1], arg[2]) 
     434            elif arg[0] == 'remove' and len(arg) == 2: 
     435                self._do_version_remove(arg[1]) 
     436            else: 
     437                self.tracadm.do_help('version') 
     438        except Exception, e: 
     439            print 'Command %s failed:' % arg[0], e 
     440 
     441    def _do_version_list(self): 
     442        from trac.ticket.model import Version 
     443        data = [] 
     444        for v in Version.select(self.tracadm.env_open()): 
     445            data.append((v.name, v.time and self.tracadm._format_date(v.time))) 
     446        self.tracadm.print_listing(['Name', 'Time'], data) 
     447 
     448    def _do_version_rename(self, name, newname): 
     449        from trac.ticket.model import Version 
     450        version = Version(self.tracadm.env_open(), name) 
     451        version.name = newname 
     452        version.update() 
     453 
     454    def _do_version_add(self, name): 
     455        from trac.ticket.model import Version 
     456        version = Version(self.tracadm.env_open()) 
     457        version.name = name 
     458        version.insert() 
     459 
     460    def _do_version_remove(self, name): 
     461        from trac.ticket.model import Version 
     462        version = Version(self.tracadm.env_open(), name) 
     463        version.delete() 
     464 
     465    def _do_version_time(self, name, t): 
     466        from trac.ticket.model import Version 
     467        version = Version(self.tracadm.env_open(), name) 
     468        version.time = self.tracadm._parse_date(t) 
     469        version.update() 
     470 
     471    def get_version_list(self): 
     472        rows = self.db_query("SELECT name FROM version") 
     473        return [row[0] for row in rows] 
  • trac/Milestone.py

    old new  
    3030from trac.web.chrome import add_link, add_stylesheet, INavigationContributor 
    3131from trac.web.main import IRequestHandler 
    3232from trac.wiki import wiki_to_html, wiki_to_oneliner, IWikiSyntaxProvider 
     33from trac.scripts.admin import IAdminConsoleProvider 
    3334 
    34  
    3535class Milestone(object): 
    3636 
    3737    def __init__(self, env, name=None, db=None): 
     
    465465    def _format_link(self, formatter, ns, name, label): 
    466466        return '<a class="milestone" href="%s">%s</a>' \ 
    467467               % (formatter.href.milestone(name), label) 
     468 
     469 
     470class MilestoneAdminConsole(Component): 
     471    """ Provide trac-admin with commands for manipulating milestones """ 
     472 
     473    implements(IAdminConsoleProvider) 
     474 
     475    # IAdminConsoleProvider methods 
     476    def get_console_commands(self, tracadm): 
     477        self.tracadm = tracadm 
     478        yield ('milestone', [('milestone list', 'Show milestones'), 
     479                            ('milestone add <name> [due]', 'Add milestone'), 
     480                            ('milestone rename <name> <newname>', 
     481                             'Rename milestone'), 
     482                            ('milestone due <name> <due>', 
     483                             'Set milestone due date (Format: "%s" or "now")' 
     484                             % tracadm._date_format_hint), 
     485                            ('milestone completed <name> <completed>', 
     486                             'Set milestone completed date (Format: "%s" or "now")' 
     487                             % tracadm._date_format_hint), 
     488                            ('milestone remove <name>', 'Remove milestone')], 
     489            self.do_milestone, self.complete_milestone) 
     490 
     491 
     492    def complete_milestone (self, text, line, begidx, endidx): 
     493        if begidx in (15, 17): 
     494            comp = self.get_milestone_list() 
     495        elif begidx < 15: 
     496            comp = ['list', 'add', 'rename', 'time', 'remove'] 
     497        return self.tracadm.word_complete(text, comp) 
     498 
     499    def do_milestone(self, line): 
     500        arg = self.tracadm.arg_tokenize(line) 
     501        try: 
     502            if arg[0]  == 'list': 
     503                self._do_milestone_list() 
     504            elif arg[0] == 'add' and len(arg) in [2,3]: 
     505                self._do_milestone_add(arg[1]) 
     506                if len(arg) == 3: 
     507                    self._do_milestone_set_due(arg[1], arg[2]) 
     508            elif arg[0] == 'rename' and len(arg) == 3: 
     509                self._do_milestone_rename(arg[1], arg[2]) 
     510            elif arg[0] == 'remove' and len(arg) == 2: 
     511                self._do_milestone_remove(arg[1]) 
     512            elif arg[0] == 'due' and len(arg) == 3: 
     513                self._do_milestone_set_due(arg[1], arg[2]) 
     514            elif arg[0] == 'completed' and len(arg) == 3: 
     515                self._do_milestone_set_completed(arg[1], arg[2]) 
     516            else: 
     517                self.tracadm.do_help('milestone') 
     518        except Exception, e: 
     519            print 'Command %s failed:' % arg[0], e 
     520 
     521    def _do_milestone_list(self): 
     522        data = [] 
     523        for m in Milestone.select(self.tracadm.env_open()): 
     524            data.append((m.name, m.due and self.tracadm._format_date(m.due), 
     525                         m.completed and self.tracadm._format_datetime(m.completed))) 
     526 
     527        self.tracadm.print_listing(['Name', 'Due', 'Completed'], data) 
     528 
     529    def _do_milestone_rename(self, name, newname): 
     530        milestone = Milestone(self.tracadm.env_open(), name) 
     531        milestone.name = newname 
     532        milestone.update() 
     533 
     534    def _do_milestone_add(self, name): 
     535        milestone = Milestone(self.tracadm.env_open()) 
     536        milestone.name = name 
     537        milestone.insert() 
     538 
     539    def _do_milestone_remove(self, name): 
     540        milestone = Milestone(self.tracadm.env_open(), name) 
     541        milestone.delete() 
     542 
     543    def _do_milestone_set_due(self, name, t): 
     544        milestone = Milestone(self.tracadm.env_open(), name) 
     545        milestone.due = self.tracadm._parse_date(t) 
     546        milestone.update() 
     547 
     548    def _do_milestone_set_completed(self, name, t): 
     549        milestone = Milestone(self.tracadm.env_open(), name) 
     550        milestone.completed = self.tracadm._parse_date(t) 
     551        milestone.update() 
     552 
     553    def get_milestone_list(self): 
     554        rows = self.tracadm.db_query("SELECT name FROM milestone") 
     555        return [row[0] for row in rows] 
  • trac/scripts/admin.py

    old new  
    3333import urllib 
    3434 
    3535import trac 
    36 from trac import perm, util 
     36from trac import util 
    3737from trac.config import default_dir 
    3838from trac.env import Environment 
    39 from trac.Milestone import Milestone 
    40 from trac.perm import PermissionSystem 
    41 from trac.ticket.model import * 
     39from trac.core import ComponentManager, ExtensionPoint, Interface 
     40from trac.loader import load_components 
    4241 
     42__all__ = [ 'IAdminConsoleProvider' ] 
     43 
    4344try: 
    4445    sum 
    4546except NameError: 
     
    5051            tot += item 
    5152        return tot 
    5253 
     54class IAdminConsoleProvider(Interface): 
     55    """ 
     56    Extension point interface for components to provide an administrative 
     57    interface from within trac-admin. 
     58    """ 
    5359 
     60    def get_console_commands(tracadm): 
     61        """ 
     62        Return an iterable of (name, help, callable, completer) tuples. 
     63 
     64        tracadm is a TracAdmin instance. 
     65 
     66        completer can be null. help is in the same format that trac-admin 
     67        uses. 
     68        """ 
     69 
     70class AdminCommands(trac.core.Component): 
     71    """ 
     72    Component end-point for IAdminConsoleProvider extensions 
     73    """ 
     74    admin_providers = ExtensionPoint(IAdminConsoleProvider) 
     75 
     76    def import_providers(self, env, tracadm): 
     77        load_components(env) 
     78        for provider in self.admin_providers: 
     79            for command in provider.get_console_commands(tracadm): 
     80                name, help, callback, completer = command 
     81                setattr(tracadm.__class__, 'do_' + name, callback) 
     82                setattr(tracadm.__class__, '_help_' + name, help) 
     83                if completer: 
     84                    setattr(tracadm.__class__, 'complete_' + name, completer) 
     85 
    5486class TracAdmin(cmd.Cmd): 
    5587    intro = '' 
    5688    license = trac.__license_long__ 
     
    6496    _date_format = '%Y-%m-%d' 
    6597    _datetime_format = '%Y-%m-%d %H:%M:%S' 
    6698    _date_format_hint = 'YYYY-MM-DD' 
     99    _admin_commands = None 
    67100 
    68101    def __init__(self, envdir=None): 
    69102        cmd.Cmd.__init__(self) 
     
    72105            self.env_set(os.path.abspath(envdir)) 
    73106 
    74107    def docmd(self, cmd='help'): 
     108        self.env_check() 
    75109        self.onecmd(cmd) 
    76110 
    77111    def emptyline(self): 
     
    84118              '%(copy)s\n\n'                                    \ 
    85119              "Type:  '?' or 'help' for help on commands.\n" %  \ 
    86120              {'ver':trac.__version__,'copy':__copyright__} 
     121        self.env_check() 
    87122        self.cmdloop() 
    88123 
    89124    ## 
     
    99134    def env_check(self): 
    100135        try: 
    101136            self.__env = Environment(self.envname) 
    102         except: 
     137            try: 
     138                self._admin_commands = AdminCommands(self.__env) 
     139                self._admin_commands.import_providers(self.__env, self) 
     140            except Exception, e: 
     141                print "Failed to initialise extension points.", e 
     142                raise 
     143        except Exception, e: 
    103144            return 0 
    104145        return 1 
    105146 
    106147    def env_create(self, db_str): 
    107148        try: 
    108149            self.__env = Environment(self.envname, create=True, db_str=db_str) 
     150            self._admin_commands = AdminCommands(self.__env) 
     151            self._admin_commands.import_providers(self.__env, self) 
    109152            return self.__env 
    110153        except Exception, e: 
    111154            print 'Failed to create environment.', e 
     
    117160        try: 
    118161            if not self.__env: 
    119162                self.__env = Environment(self.envname) 
     163                self._admin_commands = AdminCommands(self.__env) 
     164                self._admin_commands.import_providers(self.__env, self) 
    120165            return self.__env 
    121166        except Exception, e: 
    122167            print 'Failed to open environment.', e 
     
    209254                               xrange(0, (1 + len(sep)) * cnum + sum(colw))]) 
    210255        print 
    211256 
     257 
     258 
    212259    def print_doc(self, doc, decor=False): 
    213260        if not doc: return 
    214261        self.print_listing(['Command', 'Description'], doc, '  --', decor)  
     
    217264        rows = self.db_query("SELECT name FROM component") 
    218265        return [row[0] for row in rows] 
    219266 
    220     def get_user_list(self): 
    221         rows = self.db_query("SELECT DISTINCT username FROM permission") 
    222         return [row[0] for row in rows] 
    223  
    224     def get_wiki_list(self): 
    225         rows = self.db_query('SELECT DISTINCT name FROM wiki')  
    226         return [row[0] for row in rows] 
    227  
    228267    def get_dir_list(self, pathstr, justdirs=False): 
    229268        dname = os.path.dirname(pathstr) 
    230269        d = os.path.join(os.getcwd(), dname) 
     
    241280            result = dlist 
    242281        return result 
    243282 
    244     def get_enum_list(self, type): 
    245         rows = self.db_query("SELECT name FROM enum WHERE type='%s'" % type) 
    246         return [row[0] for row in rows] 
    247283 
    248     def get_milestone_list(self): 
    249         rows = self.db_query("SELECT name FROM milestone") 
    250         return [row[0] for row in rows] 
    251  
    252284    def get_version_list(self): 
    253285        rows = self.db_query("SELECT name FROM version") 
    254286        return [row[0] for row in rows] 
     
    299331            except AttributeError: 
    300332                print "No documentation found for '%s'" % arg[0] 
    301333        else: 
    302             docs = (self._help_about + self._help_help + 
    303                     self._help_initenv + self._help_hotcopy + 
    304                     self._help_resync + self._help_upgrade + 
    305                     self._help_wiki + 
    306 #                    self._help_config + self._help_wiki + 
    307                     self._help_permission + self._help_component + 
    308                     self._help_ticket_type + self._help_priority + 
    309                     self._help_severity +  self._help_version + 
    310                     self._help_milestone) 
     334            # Extract documentation from all _help_* members 
     335            docs = [] 
     336            doc_strings = [doc for doc in dir(self) if doc.startswith('_help_') and doc[6:] not in ('EOF', 'exit', 'quit')] 
     337            for doc in doc_strings: 
     338                docs.extend(getattr(self, doc)) 
     339            docs.sort(lambda a, b: cmp(a[0], b[0])) 
     340 
    311341            print 'trac-admin - The Trac Administration Console %s' % trac.__version__ 
    312342            if not self.interactive: 
    313343                print 
     
    342372    do_EOF = do_quit # Alias 
    343373 
    344374 
    345     # Component 
    346     _help_component = [('component list', 'Show available components'), 
    347                        ('component add <name> <owner>', 'Add a new component'), 
    348                        ('component rename <name> <newname>', 
    349                         'Rename a component'), 
    350                        ('component remove <name>', 
    351                         'Remove/uninstall component'), 
    352                        ('component chown <name> <owner>', 
    353                         'Change component ownership')] 
    354  
    355     def complete_component(self, text, line, begidx, endidx): 
    356         if begidx in (16, 17): 
    357             comp = self.get_component_list() 
    358         elif begidx > 15 and line.startswith('component chown '): 
    359             comp = self.get_user_list() 
    360         else: 
    361             comp = ['list', 'add', 'rename', 'remove', 'chown'] 
    362         return self.word_complete(text, comp) 
    363  
    364     def do_component(self, line): 
    365         arg = self.arg_tokenize(line) 
    366         try: 
    367             if arg[0]  == 'list': 
    368                 self._do_component_list() 
    369             elif arg[0] == 'add' and len(arg)==3: 
    370                 name = arg[1] 
    371                 owner = arg[2] 
    372                 self._do_component_add(name, owner) 
    373             elif arg[0] == 'rename' and len(arg)==3: 
    374                 name = arg[1] 
    375                 newname = arg[2] 
    376                 self._do_component_rename(name, newname) 
    377             elif arg[0] == 'remove'  and len(arg)==2: 
    378                 name = arg[1] 
    379                 self._do_component_remove(name) 
    380             elif arg[0] == 'chown' and len(arg)==3: 
    381                 name = arg[1] 
    382                 owner = arg[2] 
    383                 self._do_component_set_owner(name, owner) 
    384             else:     
    385                 self.do_help ('component') 
    386         except Exception, e: 
    387             print 'Component %s failed:' % arg[0], e 
    388  
    389     def _do_component_list(self): 
    390         data = [] 
    391         for c in Component.select(self.env_open()): 
    392             data.append((c.name, c.owner)) 
    393         self.print_listing(['Name', 'Owner'], data) 
    394  
    395     def _do_component_add(self, name, owner): 
    396         component = Component(self.env_open()) 
    397         component.name = name 
    398         component.owner = owner 
    399         component.insert() 
    400  
    401     def _do_component_rename(self, name, newname): 
    402         component = Component(self.env_open(), name) 
    403         component.name = newname 
    404         component.update() 
    405  
    406     def _do_component_remove(self, name): 
    407         component = Component(self.env_open(), name) 
    408         component.delete() 
    409  
    410     def _do_component_set_owner(self, name, owner): 
    411         component = Component(self.env_open(), name) 
    412         component.owner = owner 
    413         component.update() 
    414  
    415  
    416     ## Permission 
    417     _help_permission = [('permission list [user]', 'List permission rules'), 
    418                         ('permission add <user> <action> [action] [...]', 
    419                          'Add a new permission rule'), 
    420                         ('permission remove <user> <action> [action] [...]', 
    421                          'Remove permission rule')] 
    422  
    423     def complete_permission(self, text, line, begidx, endidx): 
    424         argv = self.arg_tokenize(line) 
    425         argc = len(argv) 
    426         if line[-1] == ' ': # Space starts new argument 
    427             argc += 1 
    428         if argc == 2: 
    429             comp = ['list', 'add', 'remove'] 
    430         elif argc >= 4: 
    431             comp = perm.permissions + perm.meta_permissions.keys() 
    432             comp.sort() 
    433         return self.word_complete(text, comp) 
    434  
    435     def do_permission(self, line): 
    436         arg = self.arg_tokenize(line) 
    437         try: 
    438             if arg[0]  == 'list': 
    439                 user = None 
    440                 if len(arg) > 1: 
    441                     user = arg[1] 
    442                 self._do_permission_list(user) 
    443             elif arg[0] == 'add' and len(arg) >= 3: 
    444                 user = arg[1] 
    445                 for action in arg[2:]: 
    446                     self._do_permission_add(user, action) 
    447             elif arg[0] == 'remove'  and len(arg) >= 3: 
    448                 user = arg[1] 
    449                 for action in arg[2:]: 
    450                     self._do_permission_remove(user, action) 
    451             else: 
    452                 self.do_help('permission') 
    453         except Exception, e: 
    454             print 'Permission %s failed:' % arg[0], e 
    455  
    456     def _do_permission_list(self, user=None): 
    457         if user: 
    458             rows = self.db_query("SELECT username, action FROM permission " 
    459                                  "WHERE username='%s' ORDER BY action" % user) 
    460         else: 
    461             rows = self.db_query("SELECT username, action FROM permission " 
    462                                  "ORDER BY username, action") 
    463         self.print_listing(['User', 'Action'], rows) 
    464         print 
    465         print 'Available actions:' 
    466         actions = PermissionSystem(self.env_open()).get_actions() 
    467         actions.sort() 
    468         text = ', '.join(actions) 
    469         print util.wrap(text, initial_indent=' ', subsequent_indent=' ', 
    470                         linesep='\n') 
    471         print 
    472  
    473     def _do_permission_add(self, user, action): 
    474         if not action.islower() and not action.isupper(): 
    475             print 'Group names must be in lower case and actions in upper case' 
    476             return 
    477         self.db_update("INSERT INTO permission VALUES('%s', '%s')" 
    478                        % (user, action)) 
    479  
    480     def _do_permission_remove(self, user, action): 
    481         sql = "DELETE FROM permission" 
    482         clauses = [] 
    483         if action != '*': 
    484             clauses.append("action='%s'" % action) 
    485         if user != '*': 
    486             clauses.append("username='%s'" % user) 
    487         if clauses: 
    488             sql += " WHERE " + " AND ".join(clauses) 
    489         self.db_update(sql) 
    490  
    491  
    492375    ## Initenv 
    493376    _help_initenv = [('initenv', 
    494377                      'Create and initialize a new environment interactively'), 
     
    589472            print ' Installing default wiki pages' 
    590473            cnx = self.__env.get_db_cnx() 
    591474            cursor = cnx.cursor() 
    592             self._do_wiki_load(default_dir('wiki'), cursor
     475            self.do_wiki('load ' + default_dir('wiki')
    593476            cnx.commit() 
    594477 
    595478            print ' Indexing repository' 
     
    650533             
    651534        print 'done.' 
    652535 
    653  
    654     ## Wiki 
    655     _help_wiki = [('wiki list', 'List wiki pages'), 
    656                   ('wiki remove <name>', 'Remove wiki page'), 
    657                   ('wiki export <page> [file]', 
    658                    'Export wiki page to file or stdout'), 
    659                   ('wiki import <page> [file]', 
    660                    'Import wiki page from file or stdin'), 
    661                   ('wiki dump <directory>', 
    662                    'Export all wiki pages to files named by title'), 
    663                   ('wiki load <directory>', 
    664                    'Import all wiki pages from directory'), 
    665                   ('wiki upgrade', 
    666                    'Upgrade default wiki pages to current version')] 
    667  
    668     def complete_wiki(self, text, line, begidx, endidx): 
    669         argv = self.arg_tokenize(line) 
    670         argc = len(argv) 
    671         if line[-1] == ' ': # Space starts new argument 
    672             argc += 1 
    673         if argc == 2: 
    674             comp = ['list', 'remove', 'import', 'export', 'dump', 'load', 
    675                     'upgrade'] 
    676         else: 
    677             if argv[1] in ('dump', 'load'): 
    678                 comp = self.get_dir_list(argv[-1], 1) 
    679             elif argv[1] in ('export', 'import'): 
    680                 if argc == 3: 
    681                     comp = self.get_wiki_list() 
    682                 elif argc == 4: 
    683                     comp = self.get_dir_list(argv[-1]) 
    684         return self.word_complete(text, comp) 
    685  
    686     def do_wiki(self, line): 
    687         arg = self.arg_tokenize(line) 
    688         try: 
    689             if arg[0]  == 'list': 
    690                 self._do_wiki_list() 
    691             elif arg[0] == 'remove'  and len(arg)==2: 
    692                 name = arg[1] 
    693                 self._do_wiki_remove(name) 
    694             elif arg[0] == 'import' and len(arg) == 3: 
    695                 title = arg[1] 
    696                 file = arg[2] 
    697                 self._do_wiki_import(file, title) 
    698             elif arg[0] == 'export'  and len(arg) in [2,3]: 
    699                 page = arg[1] 
    700                 file = (len(arg) == 3 and arg[2]) or None 
    701                 self._do_wiki_export(page, file) 
    702             elif arg[0] == 'dump' and len(arg) in [1,2]: 
    703                 dir = (len(arg) == 2 and arg[1]) or '' 
    704                 self._do_wiki_dump(dir) 
    705             elif arg[0] == 'load' and len(arg) in [1,2]: 
    706                 dir = (len(arg) == 2 and arg[1]) or '' 
    707                 self._do_wiki_load(dir) 
    708             elif arg[0] == 'upgrade' and len(arg) == 1: 
    709                 self._do_wiki_load(default_dir('wiki'), 
    710                                    ignore=['WikiStart', 'checkwiki.py']) 
    711             else:     
    712                 self.do_help ('wiki') 
    713         except Exception, e: 
    714             print 'Wiki %s failed:' % arg[0], e 
    715  
    716     def _do_wiki_list(self): 
    717         rows = self.db_query("SELECT name,max(version),time " 
    718                              "FROM wiki GROUP BY name ORDER BY name") 
    719         self.print_listing(['Title', 'Edits', 'Modified'], 
    720                            [(r[0], r[1], self._format_datetime(r[2])) for r in rows]) 
    721  
    722     def _do_wiki_remove(self, name): 
    723         cnx = self.db_open() 
    724         cursor = cnx.cursor() 
    725         cursor.execute('SELECT name FROM wiki WHERE name=%s', name) 
    726         if not cursor.fetchone(): 
    727             raise Exception("No such wiki page '%s'" % name) 
    728         cursor.execute("DELETE FROM wiki WHERE name=%s", (name,)) 
    729  
    730     def _do_wiki_import(self, filename, title, cursor=None): 
    731         if not os.path.isfile(filename): 
    732             print "%s is not a file" % filename 
    733             return 
    734         f = open(filename,'r') 
    735         data = util.to_utf8(f.read()) 
    736  
    737         # Make sure we don't insert the exact same page twice 
    738         rows = self.db_query("SELECT text FROM wiki WHERE name='%s' " 
    739                              "ORDER BY version DESC LIMIT 1" % title, cursor) 
    740         old = list(rows) 
    741         if old and data == old[0][0]: 
    742             print '  %s already up to date.' % title 
    743             return 
    744  
    745         data = data.replace("'", "''") # Escape ' for safe SQL 
    746         f.close() 
    747  
    748         sql = ("INSERT INTO wiki(version,name,time,author,ipnr,text) " 
    749                " SELECT 1+COALESCE(max(version),0),'%(title)s','%(time)s'," 
    750                " '%(author)s','%(ipnr)s','%(text)s' FROM wiki " 
    751                " WHERE name='%(title)s'"  
    752                % {'title':title, 
    753                   'time':int(time.time()), 
    754                   'author':'trac', 
    755                   'ipnr':'127.0.0.1', 
    756                   'locked':'0', 
    757                   'text':data}) 
    758         self.db_update(sql, cursor) 
    759  
    760     def _do_wiki_export(self, page, filename=''): 
    761         data = self.db_query("SELECT text FROM wiki WHERE name='%s' " 
    762                              "ORDER BY version DESC LIMIT 1" % page) 
    763         text = data.next()[0] 
    764         if not filename: 
    765             print text 
    766         else: 
    767             if os.path.isfile(filename): 
    768                 raise Exception("File '%s' exists" % filename) 
    769             f = open(filename,'w') 
    770             f.write(text) 
    771             f.close() 
    772  
    773     def _do_wiki_dump(self, dir): 
    774         pages = self.get_wiki_list() 
    775         for p in pages: 
    776             dst = os.path.join(dir, urllib.quote(p, '')) 
    777             print " %s => %s" % (p, dst) 
    778             self._do_wiki_export(p, dst) 
    779  
    780     def _do_wiki_load(self, dir, cursor=None, ignore=[]): 
    781         for page in os.listdir(dir): 
    782             if page in ignore: 
    783                 continue 
    784             filename = os.path.join(dir, page) 
    785             page = urllib.unquote(page) 
    786             if os.path.isfile(filename): 
    787                 print " %s => %s" % (filename, page) 
    788                 self._do_wiki_import(filename, page, cursor) 
    789  
    790  
    791     ## (Ticket) Type 
    792     _help_ticket_type = [('ticket_type list', 'Show possible ticket types'), 
    793                          ('ticket_type add <value>', 'Add a ticket type'), 
    794                          ('ticket_type change <value> <newvalue>', 
    795                           'Change a ticket type'), 
    796                          ('ticket_type remove <value>', 'Remove a ticket type')] 
    797   
    798     def complete_ticket_type (self, text, line, begidx, endidx): 
    799         if begidx == 16: 
    800             comp = self.get_enum_list ('ticket_type') 
    801         elif begidx < 15: 
    802             comp = ['list', 'add', 'change', 'remove'] 
    803         return self.word_complete(text, comp) 
    804   
    805     def do_ticket_type(self, line): 
    806         self._do_enum('ticket_type', line) 
    807   
    808     ## (Ticket) Priority 
    809     _help_priority = [('priority list', 'Show possible ticket priorities'), 
    810                        ('priority add <value>', 'Add a priority value option'), 
    811                        ('priority change <value> <newvalue>', 
    812                         'Change a priority value'), 
    813                        ('priority remove <value>', 'Remove priority value')] 
    814  
    815     def complete_priority (self, text, line, begidx, endidx): 
    816         if begidx == 16: 
    817             comp = self.get_enum_list ('priority') 
    818         elif begidx < 15: 
    819             comp = ['list', 'add', 'change', 'remove'] 
    820         return self.word_complete(text, comp) 
    821  
    822     def do_priority(self, line): 
    823         self._do_enum('priority', line) 
    824  
    825     ## (Ticket) Severity 
    826     _help_severity = [('severity list', 'Show possible ticket severities'), 
    827                       ('severity add <value>', 'Add a severity value option'), 
    828                       ('severity change <value> <newvalue>', 
    829                        'Change a severity value'), 
    830                       ('severity remove <value>', 'Remove severity value')] 
    831  
    832     def complete_severity (self, text, line, begidx, endidx): 
    833         if begidx == 16: 
    834             comp = self.get_enum_list ('severity') 
    835         elif begidx < 15: