Edgewall Software

Ticket #1328: trac-admin-modularity.patch

File trac-admin-modularity.patch, 86.5 KB (added by Mark Rowe <edgewall.com@…>, 4 years ago)

Minor refactoring of trac-admin

  • scripts/trac-admin

    === scripts/trac-admin
    ==================================================================
     
    2121 along with this program; if not, write to the Free Software 
    2222 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.""" 
    2323 
    24 import os 
    25 import os.path 
    2624import sys 
    27 import time 
    28 import cmd 
    29 import shlex 
    30 import sqlite 
    31 import StringIO 
    32  
    33 from trac import perm 
    34 from trac import util 
    35 from trac import sync 
    36 import trac.siteconfig 
    37 import trac.Environment 
    38  
    39 def my_sum(list): 
    40     """Python2.1 doesn't have sum()""" 
    41     tot = 0 
    42     for item in list: 
    43         tot += item 
    44     return tot 
    45  
    46  
    47 class TracAdmin(cmd.Cmd): 
    48     intro = '' 
    49     license = trac.__license_long__ 
    50     credits = trac.__credits__ 
    51     doc_header = 'Trac Admin Console %(ver)s\n' \ 
    52                  'Available Commands:\n' \ 
    53                  % {'ver':trac.__version__ } 
    54     ruler = '' 
    55     prompt = "Trac> " 
    56     __env = None 
    57      
    58     def __init__(self,envdir=None): 
    59         cmd.Cmd.__init__(self) 
    60         self.interactive = 0 
    61         if envdir: 
    62             self.env_set(os.path.abspath(envdir)) 
    63  
    64     def docmd(self, cmd='help'): 
    65         self.onecmd(cmd) 
    66  
    67     def emptyline(self): 
    68         pass 
    69  
    70     def run(self): 
    71         self.interactive = 1 
    72         print 'Welcome to trac-admin %(ver)s\n'                \ 
    73               'Interactive Trac adminstration console.\n'       \ 
    74               '%(copy)s\n\n'                                    \ 
    75               "Type:  '?' or 'help' for help on commands.\n" %  \ 
    76               {'ver':trac.__version__,'copy':__copyright__} 
    77         while 1: 
    78             try: 
    79                 self.cmdloop() 
    80                 break 
    81             except KeyboardInterrupt: 
    82                 print "\n** Interrupt. Use 'quit' to exit **" 
    83  
    84     ## 
    85     ## Environment methods 
    86     ## 
    87  
    88     def env_set(self, envname): 
    89         self.envname = envname 
    90         self.prompt = "Trac [%s]> " % self.envname 
    91  
    92     def env_check(self): 
    93         try: 
    94             self.__env = trac.Environment.Environment(self.envname) 
    95         except: 
    96             return 0 
    97         return 1 
    98          
    99     def env_create(self): 
    100         try: 
    101             self.__env = trac.Environment.Environment (self.envname, create=1) 
    102             return self.__env 
    103         except Exception, e: 
    104             print 'Failed to create environment.', e 
    105             sys.exit(1) 
    106  
    107     def db_open(self): 
    108         try: 
    109             if not self.__env: 
    110                 self.__env = trac.Environment.Environment (self.envname) 
    111             return self.__env.get_db_cnx() 
    112         except Exception, e: 
    113             print 'Failed to open environment.', e 
    114             sys.exit(1) 
    115  
    116     def db_execsql (self, sql, cursor=None): 
    117         data = [] 
    118         if not cursor: 
    119             cnx=self.db_open() 
    120             cursor = cnx.cursor() 
    121         else: 
    122             cnx = None 
    123         cursor.execute(sql) 
    124         while 1: 
    125             row = cursor.fetchone() 
    126             if row == None: 
    127                 break 
    128             data.append(row) 
    129         if cnx: 
    130             cnx.commit() 
    131         return data 
    132  
    133     ## 
    134     ## Utility methods 
    135     ## 
    136  
    137     def arg_tokenize (self, argstr): 
    138         if hasattr(shlex, 'split'): 
    139             toks = shlex.split(argstr) 
    140         else: 
    141             def my_strip(s, c): 
    142                 """string::strip in python2.1 doesn't support arguments""" 
    143                 i = j = 0 
    144                 for i in range(len(s)): 
    145                     if not s[i] in c: 
    146                         break 
    147                 for j in range(len(s), 0, -1): 
    148                     if not s[j-1] in c: 
    149                         break 
    150                 return s[i:j] 
    151          
    152             lexer = shlex.shlex(StringIO.StringIO(argstr)) 
    153             lexer.wordchars = lexer.wordchars + ".,_/" 
    154             toks = [] 
    155             while 1: 
    156                 token = my_strip(lexer.get_token(), '"\'') 
    157                 if not token: 
    158                     break 
    159                 toks.append(token) 
    160         return toks or [''] 
    161  
    162     def word_complete (self, text, words): 
    163         return [a for a in words if a.startswith (text)] 
    164  
    165     def print_listing(self, headers, data, sep=' ',decor=1): 
    166         ldata = data 
    167         if decor: 
    168             ldata.insert (0, headers) 
    169         print 
    170         colw=[] 
    171         ncols = len(ldata[0]) # assumes all rows are of equal length 
    172         for cnum in xrange(0, ncols): 
    173             mw = 0 
    174             for cell in [str(d[cnum]) or '' for d in ldata]: 
    175                 if len(cell) > mw: 
    176                     mw = len(cell) 
    177             colw.append(mw) 
    178         for rnum in xrange(0, len(ldata)): 
    179             for cnum in xrange(0, ncols): 
    180                 if decor and rnum == 0: 
    181                     sp = ('%%%ds' % len(sep)) % ' '  # No separator in header 
    182                 else: 
    183                     sp = sep 
    184                 if cnum+1 == ncols: sp = '' # No separator after last column 
    185                 print ("%%-%ds%s" % (colw[cnum], sp)) % (ldata[rnum][cnum] or ''), 
    186             print 
    187             if rnum == 0 and decor: 
    188                 print ''.join(['-' for x in xrange(0,(1+len(sep))*cnum+my_sum(colw))]) 
    189         print 
    190  
    191     def print_doc(self,doc,decor=0): 
    192         if not doc: return 
    193         self.print_listing (['Command','Description'], doc, '  --', decor)  
    194  
    195     def get_component_list (self): 
    196         data = self.db_execsql ("SELECT name FROM component") 
    197         return [r[0] for r in data] 
    198  
    199     def get_user_list (self): 
    200         data = self.db_execsql ("SELECT DISTINCT username FROM permission") 
    201         return [r[0] for r in data] 
    202  
    203     def get_wiki_list (self): 
    204         data = self.db_execsql('SELECT DISTINCT name FROM wiki')  
    205         return [r[0] for r in data] 
    206  
    207     def get_dir_list (self, pathstr,justdirs=0): 
    208         dname = os.path.dirname(pathstr) 
    209         d = os.path.join(os.getcwd(), dname) 
    210         dlist = os.listdir(d) 
    211         if justdirs: 
    212             result = [] 
    213             for entry in dlist: 
    214                 try: 
    215                     if os.path.isdir(entry): 
    216                         result.append(entry) 
    217                 except: 
    218                     pass 
    219         else: 
    220             result = dlist 
    221         return result 
    222  
    223     def get_enum_list (self, type): 
    224         data = self.db_execsql("SELECT name FROM enum WHERE type='%s'" % type)  
    225         return [r[0] for r in data] 
    226  
    227     def get_milestone_list (self): 
    228         data = self.db_execsql("SELECT name FROM milestone")  
    229         return [r[0] for r in data] 
    230  
    231     def get_version_list (self): 
    232         data = self.db_execsql("SELECT name FROM version")  
    233         return [r[0] for r in data] 
    234  
    235     def _parse_datetime(self, t): 
    236         seconds = None 
    237         t = t.strip() 
    238         if t == 'now': 
    239             seconds = int(time.time()) 
    240         else: 
    241             for format in ['%x %X', '%x, %X', '%X %x', '%X, %x', '%x', '%c', 
    242                            '%b %d, %Y']: 
    243                 try: 
    244                     pt = time.strptime(t, format) 
    245                     seconds = int(time.mktime(pt)) 
    246                 except ValueError: 
    247                     continue 
    248                 break 
    249         if seconds == None: 
    250             try: 
    251                 seconds = int(t) 
    252             except ValueError: 
    253                 pass 
    254         if seconds == None: 
    255             print >> sys.stderr, 'Unknown time format' 
    256         return seconds 
    257  
    258  
    259     ## 
    260     ## Available Commands 
    261     ## 
    262  
    263     ## Help 
    264     _help_help = [('help', 'Show documentation')] 
    265  
    266     def do_help(self, line=None): 
    267         arg = self.arg_tokenize(line) 
    268         if arg[0]: 
    269             try: 
    270                 doc = getattr(self, "_help_" + arg[0]) 
    271                 self.print_doc (doc) 
    272             except AttributeError: 
    273                 print "No documentation found for '%s'" % arg[0] 
    274         else: 
    275             docs = (self._help_about + self._help_help + 
    276                     self._help_initenv + self._help_hotcopy + 
    277                     self._help_resync + self._help_upgrade + 
    278                     self._help_wiki + 
    279 #                    self._help_config + self._help_wiki + 
    280                     self._help_permission + self._help_component + 
    281                     self._help_priority + self._help_severity +  
    282                     self._help_version + self._help_milestone) 
    283             print 'trac-admin - The Trac Administration Console %s' % trac.__version__ 
    284             if not self.interactive: 
    285                 print 
    286                 print "Usage: trac-admin </path/to/projenv> [command [subcommand] [option ...]]\n" 
    287                 print "Invoking trac-admin without command starts "\ 
    288                        "interactive mode." 
    289             self.print_doc (docs) 
    290             print self.credits 
    291  
    292      
    293     ## About / Version 
    294     _help_about = [('about', 'Shows information about trac-admin')] 
    295  
    296     def do_about(self, line): 
    297         print 
    298         print 'Trac Admin Console %s' % trac.__version__ 
    299         print '=================================================================' 
    300         print self.license 
    301         print self.credits 
    302  
    303  
    304     ## Quit / EOF 
    305     _help_quit = [['quit', 'Exit the program']] 
    306     _help_exit = _help_quit 
    307     _help_EOF = _help_quit 
    308  
    309     def do_quit(self,line): 
    310         print 
    311         sys.exit() 
    312  
    313     do_exit = do_quit # Alias 
    314     do_EOF = do_quit # Alias 
    315  
    316 #    ## Component 
    317     _help_component = [('component list', 'Show available components'), 
    318                        ('component add <name> <owner>', 'Add a new component'), 
    319                        ('component rename <name> <newname>', 'Rename a component'), 
    320                        ('component remove <name>', 'Remove/uninstall component'), 
    321                        ('component chown <name> <owner>', 'Change component ownership')] 
    322  
    323     def complete_component (self, text, line, begidx, endidx): 
    324         if begidx in [16,17]: 
    325             comp = self.get_component_list() 
    326         elif begidx > 15 and line.startswith('component chown '): 
    327             comp = self.get_user_list() 
    328         else: 
    329             comp = ['list','add','rename','remove','chown'] 
    330         return self.word_complete(text, comp) 
    331  
    332     def do_component(self, line): 
    333         arg = self.arg_tokenize(line) 
    334         try: 
    335             if arg[0]  == 'list': 
    336                 self._do_component_list() 
    337             elif arg[0] == 'add' and len(arg)==3: 
    338                 name = arg[1] 
    339                 owner = arg[2] 
    340                 self._do_component_add(name, owner) 
    341             elif arg[0] == 'rename' and len(arg)==3: 
    342                 name = arg[1] 
    343                 newname = arg[2] 
    344                 self._do_component_rename(name, newname) 
    345             elif arg[0] == 'remove'  and len(arg)==2: 
    346                 name = arg[1] 
    347                 self._do_component_remove(name) 
    348             elif arg[0] == 'chown' and len(arg)==3: 
    349                 name = arg[1] 
    350                 owner = arg[2] 
    351                 self._do_component_set_owner(name, owner) 
    352             else:     
    353                 self.do_help ('component') 
    354         except Exception, e: 
    355             print 'Component %s failed:' % arg[0], e 
    356  
    357     def _do_component_list(self): 
    358         data = self.db_execsql('SELECT name, owner FROM component')  
    359         self.print_listing(['Name', 'Owner'], data) 
    360  
    361     def _do_component_add(self, name, owner): 
    362         data = self.db_execsql("INSERT INTO component VALUES('%s', '%s')" 
    363                                % (name, owner)) 
    364  
    365     def _do_component_rename(self, name, newname): 
    366         cnx = self.db_open() 
    367         cursor = cnx.cursor () 
    368         cursor.execute('SELECT name FROM component WHERE name=%s', name) 
    369         data = cursor.fetchone() 
    370         if not data: 
    371             raise Exception("No such component '%s'" % name) 
    372         self.db_execsql("UPDATE component SET name='%s' WHERE name='%s'" 
    373                         % (newname,name), cursor) 
    374         self.db_execsql("UPDATE ticket SET component='%s' WHERE component='%s'" 
    375                         % (newname,name), cursor) 
    376         cnx.commit() 
    377  
    378     def _do_component_remove(self, name): 
    379         cnx = self.db_open() 
    380         cursor = cnx.cursor () 
    381         cursor.execute('SELECT name FROM component WHERE name=%s', name) 
    382         data = cursor.fetchone() 
    383         if not data: 
    384             raise Exception("No such component '%s'" % name) 
    385         data = self.db_execsql("DELETE FROM component WHERE name='%s'" 
    386                                % (name)) 
    387  
    388     def _do_component_set_owner(self, name, owner): 
    389         cnx = self.db_open() 
    390         cursor = cnx.cursor () 
    391         cursor.execute('SELECT name FROM component WHERE name=%s', name) 
    392         data = cursor.fetchone() 
    393         if not data: 
    394             raise Exception("No such component '%s'" % name) 
    395         data = self.db_execsql("UPDATE component SET owner='%s' WHERE name='%s'" 
    396                                % (owner,name)) 
    397  
    398  
    399     ## Permission 
    400     _help_permission = [('permission list', 'List permission rules'), 
    401                        ('permission add <user> <action> [action] [...]', 'Add a new permission rule'), 
    402                        ('permission remove <user> <action> [action] [...]', 'Remove permission rule')] 
    403  
    404     def do_permission(self, line): 
    405         arg = self.arg_tokenize(line) 
    406         try: 
    407             if arg[0]  == 'list': 
    408                 self._do_permission_list() 
    409             elif arg[0] == 'add' and len(arg) >= 3: 
    410                 user = arg[1] 
    411                 for action in arg[2:]: 
    412                     self._do_permission_add(user, action) 
    413             elif arg[0] == 'remove'  and len(arg) >= 3: 
    414                 user = arg[1] 
    415                 for action in arg[2:]: 
    416                     self._do_permission_remove(user, action) 
    417             else:     
    418                 self.do_help ('permission') 
    419         except Exception, e: 
    420             print 'Permission %s failed:' % arg[0], e 
    421  
    422     def _do_permission_list(self): 
    423         data = self.db_execsql('SELECT username, action FROM permission ' \ 
    424                                'ORDER BY username, action')  
    425         self.print_listing(['User', 'Action'], data) 
    426         print 
    427         print 'Available actions:' 
    428         actions = perm.permissions + perm.meta_permissions.keys() 
    429         actions.sort() 
    430         text = ', '.join(actions) 
    431         print util.wrap(text, initial_indent=' ', subsequent_indent=' ') 
    432         print 
    433  
    434     def _do_permission_add(self, user, action): 
    435         if not action.islower() and not action.isupper(): 
    436             print 'Group names must be in lower case and actions in upper case' 
    437             return 
    438         if action.isupper() and not \ 
    439            action in perm.permissions + perm.meta_permissions.keys(): 
    440             print '%s is not a valid action. Use the permission list command ' \ 
    441                   'to see the available actions.' % (action) 
    442             return 
    443         self.db_execsql("INSERT INTO permission VALUES('%s', '%s')" % (user, action)) 
    444  
    445     def _do_permission_remove(self, user, action): 
    446         sql = "DELETE FROM permission" 
    447         clauses = [] 
    448         if action != '*': 
    449             clauses.append("action='%s'" % action) 
    450         if user != '*': 
    451             clauses.append("username='%s'" % user) 
    452         if clauses: 
    453             sql += " WHERE " + " AND ".join(clauses) 
    454         self.db_execsql(sql) 
    455  
    456     ## Initenv 
    457     _help_initenv = [('initenv', 'Create and initialize a new environment interactively'), 
    458                      ('initenv <projectname> <repospath> <templatepath>', 
    459                       'Create and initialize a new environment from arguments')] 
    460  
    461     def do_initdb(self, line): 
    462         self.do_initenv(line) 
    463          
    464     def get_initenv_args(self): 
    465         returnvals = [] 
    466         print 'Creating a new Trac environment at %s' % self.envname 
    467         print 
    468         print 'Trac will first ask a few questions about your environment ' 
    469         print 'in order to initalize and prepare the project database.' 
    470         print 
    471         print " Please enter the name of your project." 
    472         print " This name will be used in page titles and descriptions." 
    473         print 
    474         dp = 'My Project' 
    475         returnvals.append(raw_input('Project Name [%s]> ' % dp) or dp) 
    476         print 
    477         print ' Please specify the absolute path to the project Subversion repository.' 
    478         print ' Repository must be local, and trac-admin requires read+write' 
    479         print ' permission to initialize the Trac database.' 
    480         print 
    481         drp = '/var/svn/test' 
    482         prompt = 'Path to repository [%s]> ' % drp 
    483         returnvals.append(raw_input(prompt) or drp) 
    484         print 
    485         print ' Please enter location of Trac page templates.' 
    486         print ' Default is the location of the site-wide templates installed with Trac.' 
    487         print 
    488         dt = trac.siteconfig.__default_templates_dir__ 
    489         prompt = 'Templates directory [%s]> ' % dt 
    490         returnvals.append(raw_input(prompt) or dt) 
    491         return returnvals 
    492           
    493     def do_initenv(self, line): 
    494         if self.env_check(): 
    495             print "Initenv for '%s' failed.\nDoes an environment already exist?" % self.envname 
    496             return 
    497         arg = self.arg_tokenize(line) 
    498         project_name = None 
    499         repository_dir = None 
    500         templates_dir = None 
    501         if len(arg) == 1: 
    502             returnvals = self.get_initenv_args() 
    503             project_name = returnvals[0] 
    504             repository_dir = returnvals[1] 
    505             templates_dir = returnvals[2] 
    506         elif len(arg)!= 3: 
    507             print 'Wrong number of arguments to initenv %d' % len(arg) 
    508             return 
    509         else: 
    510             project_name = arg[0] 
    511             repository_dir = arg[1] 
    512             templates_dir = arg[2] 
    513         from svn import util, repos, core 
    514         core.apr_initialize() 
    515         pool = core.svn_pool_create(None) 
    516         try: 
    517             # Remove any trailing slash or else subversion might abort 
    518             if not os.path.split(repository_dir)[1]: 
    519                 repository_dir = os.path.split(repository_dir)[0] 
    520             rep = repos.svn_repos_open(repository_dir, pool) 
    521             fs_ptr = repos.svn_repos_fs(rep) 
    522         except Exception, e: 
    523             print repository_dir, 'Repository access error: %s' % e 
    524             return 
    525         if not os.access(os.path.join(templates_dir, 'browser.cs'), os.F_OK) \ 
    526            or not os.access(os.path.join(templates_dir, 'ticket.cs'), os.F_OK): 
    527             print templates_dir, "doesn't look like a Trac templates directory" 
    528             return 
    529         try: 
    530             print 'Creating and Initializing Project' 
    531             self.env_create() 
    532             cnx = self.__env.get_db_cnx() 
    533             print ' Inserting default data' 
    534             self.__env.insert_default_data() 
    535              
    536             print ' Configuring Project' 
    537             print '  trac.repository_dir' 
    538             self.__env.set_config('trac', 'repository_dir', repository_dir) 
    539             print '  trac.templates_dir' 
    540             self.__env.set_config('trac', 'templates_dir', templates_dir) 
    541             print '  project.name' 
    542             self.__env.set_config('project', 'name', project_name) 
    543             self.__env.save_config() 
    544             # Add a few default wiki pages 
    545             print ' Installing wiki pages' 
    546             cursor = cnx.cursor() 
    547             self._do_wiki_load(trac.siteconfig.__default_wiki_dir__,cursor) 
    548  
    549             print ' Indexing repository' 
    550             sync.sync(cnx, rep, fs_ptr, pool) 
    551         except Exception, e: 
    552             print 'Failed to initialize database.', e 
    553             sys.exit(2) 
    554  
    555  
    556         print "---------------------------------------------------------------------" 
    557         print 
    558         print 'Project database for \'%s\' created.' % project_name 
    559         print 
    560         print ' Customize settings for your project using the command:' 
    561         print 
    562         print '   trac-admin %s' % self.envname 
    563         print 
    564         print ' Don\'t forget, you also need to copy (or symlink) "trac/cgi-bin/trac.cgi"' 
    565         print ' to you web server\'s /cgi-bin/ directory, and then configure the server.' 
    566         print 
    567         print ' If you\'re using Apache, this config example snippet might be helpful:' 
    568         print 
    569         print '    Alias /trac "/wherever/you/installed/trac/htdocs/"' 
    570         print '    <Location "/cgi-bin/trac.cgi">' 
    571         print '        SetEnv TRAC_ENV "%s"' % self.envname 
    572         print '    </Location>' 
    573         print 
    574         print '    # You need something like this to authenticate users' 
    575         print '    <Location "/cgi-bin/trac.cgi/login">' 
    576         print '        AuthType Basic' 
    577         print '        AuthName "%s"' % project_name 
    578         print '        AuthUserFile /somewhere/trac.htpasswd' 
    579         print '        Require valid-user' 
    580         print '    </Location>' 
    581         print 
    582  
    583         print ' The latest documentation can also always be found on the project website:' 
    584         print ' http://projects.edgewall.com/trac/' 
    585         print 
    586         print 'Congratulations!' 
    587         print 
    588          
    589     _help_resync = [('resync', 'Re-synchronize trac with the repository')] 
    590      
    591     ## Resync 
    592     def do_resync(self, line): 
    593         from svn import util, repos, core 
    594         core.apr_initialize() 
    595         pool = core.svn_pool_create(None) 
    596  
    597         self.db_open() # We need to call this function to open the env, really stupid 
    598  
    599         # Remove any trailing slash or else subversion might abort 
    600         repository_dir = self.__env.get_config('trac', 'repository_dir') 
    601         if not os.path.split(repository_dir)[1]: 
    602             repository_dir = os.path.split(repository_dir)[0] 
    603              
    604         rep = repos.svn_repos_open(repository_dir, pool) 
    605         fs_ptr = repos.svn_repos_fs(rep) 
    606          
    607         cnx = self.__env.get_db_cnx() 
    608         print 'resyncing...' 
    609         self.db_execsql("DELETE FROM revision") 
    610         self.db_execsql("DELETE FROM node_change") 
    611         sync.sync(cnx, rep, fs_ptr, pool) 
    612         print 'done.' 
    613          
    614     ## Wiki 
    615     _help_wiki = [('wiki list', 'List wiki pages'), 
    616                   ('wiki remove <name>', 'Remove wiki page'), 
    617                   ('wiki export <page> [file]', 
    618                    'Export wiki page to file or stdout'), 
    619                   ('wiki import <page> [file]', 
    620                    'Import wiki page from file or stdin'), 
    621                   ('wiki dump <directory>', 
    622                    'Export all wiki pages to files named by title'), 
    623                   ('wiki load <directory>', 
    624                    'Import all wiki pages from directory'), 
    625                   ('wiki upgrade', 
    626                    'Upgrade default wiki pages to current version')] 
    627  
    628     def complete_wiki (self, text, line, begidx, endidx): 
    629         argv = self.arg_tokenize(line) 
    630         argc = len(argv) 
    631         if line[-1] == ' ': # Space starts new argument 
    632             argc += 1 
    633         if argc==2: 
    634             comp = ['list','remove','import','export','dump','load'] 
    635         else: 
    636             if argv[1] in ['dump','load']: 
    637                 comp = self.get_dir_list(argv[-1], 1) 
    638             elif argv[1] in ['export', 'import']: 
    639                 if argc==3: 
    640                     comp = self.get_wiki_list() 
    641                 elif argc==4: 
    642                     comp = self.get_dir_list(argv[-1]) 
    643         return self.word_complete(text, comp) 
    644  
    645     def do_wiki(self, line): 
    646         arg = self.arg_tokenize(line) 
    647         try: 
    648             if arg[0]  == 'list': 
    649                 self._do_wiki_list() 
    650             elif arg[0] == 'remove'  and len(arg)==2: 
    651                 name = arg[1] 
    652                 self._do_wiki_remove(name) 
    653             elif arg[0] == 'import' and len(arg) == 3: 
    654                 title = arg[1] 
    655                 file = arg[2] 
    656                 self._do_wiki_import(file, title) 
    657             elif arg[0] == 'export'  and len(arg) in [2,3]: 
    658                 page = arg[1] 
    659                 file = (len(arg) == 3 and arg[2]) or None 
    660                 self._do_wiki_export(page, file) 
    661             elif arg[0] == 'dump' and len(arg) in [1,2]: 
    662                 dir = (len(arg) == 2 and arg[1]) or '' 
    663                 self._do_wiki_dump(dir) 
    664             elif arg[0] == 'load' and len(arg) in [1,2]: 
    665                 dir = (len(arg) == 2 and arg[1]) or '' 
    666                 self._do_wiki_load(dir) 
    667             elif arg[0] == 'upgrade' and len(arg) == 1: 
    668                 self._do_wiki_load(trac.siteconfig.__default_wiki_dir__, 
    669                                    ignore=['WikiStart', 'checkwiki.py']) 
    670             else:     
    671                 self.do_help ('wiki') 
    672         except Exception, e: 
    673             print 'Wiki %s failed:' % arg[0], e 
    674  
    675     def _do_wiki_list(self): 
    676         data = self.db_execsql('SELECT name,max(version),time' 
    677                                ' FROM wiki GROUP BY name ORDER BY name') 
    678         ldata = [(d[0], d[1], time.ctime(d[2])) for d in data] 
    679         self.print_listing(['Title', 'Edits', 'Modified'], ldata) 
    680  
    681     def _do_wiki_remove(self, name): 
    682         cnx = self.db_open() 
    683         cursor = cnx.cursor () 
    684         cursor.execute('SELECT name FROM wiki WHERE name=%s', name) 
    685         data = cursor.fetchone() 
    686         if not data: 
    687             raise Exception("No such wiki page '%s'" % name) 
    688         data = self.db_execsql("DELETE FROM wiki WHERE name='%s'" 
    689                                % (name)) 
    690  
    691     def _do_wiki_import(self, filename, title, cursor=None): 
    692         if not os.path.isfile(filename): 
    693             print "%s is not a file" % filename 
    694             return 
    695         f = open(filename,'r') 
    696         data = util.to_utf8(f.read()) 
    697  
    698         # Make sure we don't insert the exact same page twice 
    699         old = self.db_execsql("SELECT text FROM wiki " 
    700                               "WHERE name='%s' " 
    701                               "ORDER BY version DESC LIMIT 1" % title, cursor) 
    702         if old and data == old[0][0]: 
    703             print '  %s already up to date.' % title 
    704             return 
    705          
    706         data = data.replace("'", "''") # Escape ' for safe SQL 
    707         f.close() 
    708          
    709         sql = ("INSERT INTO wiki(version,name,time,author,ipnr,text) " 
    710                " SELECT 1+COALESCE(max(version),0),'%(title)s','%(time)s'," 
    711                " '%(author)s','%(ipnr)s','%(text)s' FROM wiki " 
    712                " WHERE name='%(title)s'"  
    713                % {'title':title, 
    714                   'time':int(time.time()), 
    715                   'author':'trac', 
    716                   'ipnr':'127.0.0.1', 
    717                   'locked':'0', 
    718                   'text':data}) 
    719         self.db_execsql(sql, cursor) 
    720  
    721     def _do_wiki_export(self, page,filename=''): 
    722         data=self.db_execsql("SELECT text FROM wiki " 
    723                              " WHERE name='%s'" 
    724                              " ORDER BY version DESC LIMIT 1" % page) 
    725         text = data[0][0] 
    726         if not filename: 
    727             print text 
    728         else: 
    729             if os.path.isfile(filename): 
    730                 raise Exception("File '%s' exists" % filename) 
    731