Code

Updated to version 1.4 (python 2.2) version of pygettext
[roundup.git] / roundup-admin
index cde8cee934a84d330485a17bfb30c0d5780e291a..6a1d5c078efa1716ccc4a5e9e17d209857a4a983 100755 (executable)
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: roundup-admin,v 1.47 2001-11-26 22:55:56 richard Exp $
+# $Id: roundup-admin,v 1.55 2001-12-17 03:52:47 richard Exp $
 
-import sys
-if int(sys.version[0]) < 2:
-    print 'Roundup requires python 2.0 or later.'
-    sys.exit(1)
+# python version check
+from roundup import version_check
 
-import string, os, getpass, getopt, re, UserDict
+import sys, os, getpass, getopt, re, UserDict
 try:
     import csv
 except ImportError:
@@ -64,6 +62,7 @@ class AdminTool:
         for k in AdminTool.__dict__.keys():
             if k[:5] == 'help_':
                 self.help[k[5:]] = getattr(self, k)
+        self.instance_home = ''
         self.db = None
 
     def usage(self, message=''):
@@ -94,6 +93,28 @@ Options:
         print '\n'.join(commands)
         print
 
+    def help_commands_html(self, indent_re=re.compile(r'^(\s+)\S+')):
+       commands = self.commands.values()
+        def sortfun(a, b):
+            return cmp(a.__name__, b.__name__)
+        commands.sort(sortfun)
+       for command in commands:
+            h = command.__doc__.split('\n')
+            name = command.__name__[3:]
+            usage = h[0]
+            print '''
+<tr><td valign=top><strong>%(name)s</strong></td>
+    <td><tt>%(usage)s</tt><p>
+<pre>'''%locals()
+            indent = indent_re.match(h[3])
+            if indent: indent = len(indent.group(1))
+            for line in h[3:]:
+                if indent:
+                    print line[indent:]
+                else:
+                    print line
+            print '</pre></td></tr>\n'
+
     def help_all(self):
         print '''
 All commands (except help) require an instance specifier. This is just the path
@@ -205,6 +226,8 @@ Command help:
 
         See also initopts help.
         '''
+        if len(args) < 1:
+            raise UsageError, 'Not enough arguments supplied'
         # select template
         import roundup.templates
         templates = roundup.templates.listTemplates()
@@ -243,8 +266,10 @@ Command help:
 
         Retrieves the property value of the nodes specified by the designators.
         '''
+        if len(args) < 2:
+            raise UsageError, 'Not enough arguments supplied'
         propname = args[0]
-        designators = string.split(args[1], ',')
+        designators = args[1].split(',')
         l = []
         for designator in designators:
             # decode the node designator
@@ -279,9 +304,11 @@ Command help:
 
         Sets the property to the value for all designators given.
         '''
+        if len(args) < 2:
+            raise UsageError, 'Not enough arguments supplied'
         from roundup import hyperdb
 
-        designators = string.split(args[0], ',')
+        designators = args[0].split(',')
         props = {}
         for prop in args[1:]:
             if prop.find('=') == -1:
@@ -340,6 +367,8 @@ Command help:
         Find the nodes of the given class with a given link property value. The
         value may be either the nodeid of the linked node, or its key value.
         '''
+        if len(args) < 1:
+            raise UsageError, 'Not enough arguments supplied'
         classname = args[0]
         # get the class
         try:
@@ -369,7 +398,7 @@ Command help:
 
             # make sure it's a link
             if (not isinstance(property, hyperdb.Link) and not
-                    isinstance(proptype, hyperdb.Multilink)):
+                    isinstance(property, hyperdb.Multilink)):
                 raise UsageError, 'You may only "find" link properties'
 
             # get the linked-to class and look up the key property
@@ -401,6 +430,8 @@ Command help:
 
         This lists the properties for a given class.
         '''
+        if len(args) < 1:
+            raise UsageError, 'Not enough arguments supplied'
         classname = args[0]
         # get the class
         try:
@@ -416,6 +447,33 @@ Command help:
             else:
                 print '%s: %s'%(key, value)
 
+    def do_display(self, args):
+        '''Usage: display designator
+        Show the property values for the given node.
+
+        This lists the properties and their associated values for the given
+        node.
+        '''
+        if len(args) < 1:
+            raise UsageError, 'Not enough arguments supplied'
+
+        # decode the node designator
+        try:
+            classname, nodeid = roundupdb.splitDesignator(args[0])
+        except roundupdb.DesignatorError, message:
+            raise UsageError, message
+
+        # get the class
+        try:
+            cl = self.db.getclass(classname)
+        except KeyError:
+            raise UsageError, 'invalid class "%s"'%classname
+
+        # display the values
+        for key in cl.properties.keys():
+            value = cl.get(nodeid, key)
+            print '%s: %s'%(key, value)
+
     def do_create(self, args):
         '''Usage: create classname property=value ...
         Create a new entry of a given class.
@@ -424,6 +482,8 @@ Command help:
         name=value arguments provided on the command line after the "create"
         command.
         '''
+        if len(args) < 1:
+            raise UsageError, 'Not enough arguments supplied'
         from roundup import hyperdb
 
         classname = args[0]
@@ -508,6 +568,8 @@ Command help:
         in order: the key, "name", "title" and then the first property,
         alphabetically.
         '''
+        if len(args) < 1:
+            raise UsageError, 'Not enough arguments supplied'
         classname = args[0]
 
         # get the class
@@ -548,6 +610,8 @@ Command help:
           3  usability 
           4  feature   
         '''
+        if len(args) < 1:
+            raise UsageError, 'Not enough arguments supplied'
         classname = args[0]
 
         # get the class
@@ -559,6 +623,11 @@ Command help:
         # figure the property names to display
         if len(args) > 1:
             prop_names = args[1].split(',')
+            all_props = cl.getprops()
+            for prop_name in prop_names:
+                if not all_props.has_key(prop_name):
+                    raise UsageError, '%s has no property "%s"'%(classname,
+                        prop_name)
         else:
             prop_names = cl.getprops().keys()
 
@@ -575,7 +644,7 @@ Command help:
                 props.append((spec, len(spec)))
 
         # now display the heading
-        print ' '.join([string.capitalize(name) for name, width in props])
+        print ' '.join([name.capitalize() for name, width in props])
 
         # and the table data
         for nodeid in cl.list():
@@ -585,8 +654,10 @@ Command help:
                     try:
                         value = str(cl.get(nodeid, name))
                     except KeyError:
-                        raise UsageError, '%s has no property "%s"'%(classname,
-                            name)
+                        # we already checked if the property is valid - a
+                        # KeyError here means the node just doesn't have a
+                        # value for it
+                        value = ''
                 else:
                     value = str(nodeid)
                 f = '%%-%ds'%width
@@ -600,6 +671,8 @@ Command help:
 
         Lists the journal entries for the node identified by the designator.
         '''
+        if len(args) < 1:
+            raise UsageError, 'Not enough arguments supplied'
         try:
             classname, nodeid = roundupdb.splitDesignator(args[0])
         except roundupdb.DesignatorError, message:
@@ -614,6 +687,32 @@ Command help:
             raise UsageError, 'no such %s node "%s"'%(classname, nodeid)
         return 0
 
+    def do_commit(self, args):
+        '''Usage: commit
+        Commit all changes made to the database.
+
+        The changes made during an interactive session are not
+        automatically written to the database - they must be committed
+        using this command.
+
+        One-off commands on the command-line are automatically committed if
+        they are successful.
+        '''
+        self.db.commit()
+        return 0
+
+    def do_rollback(self, args):
+        '''Usage: rollback
+        Undo all changes that are pending commit to the database.
+
+        The changes made during an interactive session are not
+        automatically written to the database - they must be committed
+        manually. This command undoes all those changes, so a commit
+        immediately after would make no changes to the database.
+        '''
+        self.db.rollback()
+        return 0
+
     def do_retire(self, args):
         '''Usage: retire designator[,designator]*
         Retire the node specified by designator.
@@ -621,7 +720,9 @@ Command help:
         This action indicates that a particular node is not to be retrieved by
         the list or find commands, and its key value may be re-used.
         '''
-        designators = string.split(args[0], ',')
+        if len(args) < 1:
+            raise UsageError, 'Not enough arguments supplied'
+        designators = args[0].split(',')
         for designator in designators:
             try:
                 classname, nodeid = roundupdb.splitDesignator(designator)
@@ -644,9 +745,8 @@ Command help:
         directory. The journals are not exported.
         '''
         if len(args) < 2:
-            print do_export.__doc__
-            return 1
-        classes = string.split(args[0], ',')
+            raise UsageError, 'Not enough arguments supplied'
+        classes = args[0].split(',')
         dir = args[1]
 
         # use the csv parser if we can - it's faster
@@ -660,7 +760,7 @@ Command help:
             except KeyError:
                 raise UsageError, 'no such class "%s"'%classname
             f = open(os.path.join(dir, classname+'.csv'), 'w')
-            f.write(string.join(cl.properties.keys(), ':') + '\n')
+            f.write(':'.join(cl.properties.keys()) + '\n')
 
             # all nodes for this class
             properties = cl.properties.items()
@@ -808,13 +908,13 @@ Command help:
         try:
             instance = roundup.instance.open(self.instance_home)
         except ValueError, message:
+            self.instance_home = ''
             print "Couldn't open instance: %s"%message
             return 1
-        self.db = instance.open('admin')
 
-        if len(args) < 2:
-            print function.__doc__
-            return 1
+        # only open the database once!
+        if not self.db:
+            self.db = instance.open('admin')
 
         # do the command
         ret = 0
@@ -844,13 +944,21 @@ Command help:
             try:
                 command = raw_input('roundup> ')
             except EOFError:
-                print '.. exit'
-                return 0
+                print 'exit...'
+                break
+            if not command: continue
             args = ws_re.split(command)
             if not args: continue
-            if args[0] in ('quit', 'exit'): return 0
+            if args[0] in ('quit', 'exit'): break
             self.run_command(args)
 
+        # exit.. check for transactions
+        if self.db and self.db.transactions:
+            commit = raw_input("There are unsaved changes. Commit them (y/N)? ")
+            if commit[0].lower() == 'y':
+                self.db.commit()
+        return 0
+
     def main(self):
         try:
             opts, args = getopt.getopt(sys.argv[1:], 'i:u:hc')
@@ -882,8 +990,7 @@ Command help:
             self.interactive()
         else:
             ret = self.run_command(args)
-        if self.db:
-            self.db.close()
+            if self.db: self.db.commit()
         return ret
 
 
@@ -893,6 +1000,67 @@ if __name__ == '__main__':
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.54  2001/12/15 23:09:23  richard
+# Some cleanups in roundup-admin, also made it work again...
+#
+# Revision 1.53  2001/12/13 00:20:00  richard
+#  . Centralised the python version check code, bumped version to 2.1.1 (really
+#    needs to be 2.1.2, but that isn't released yet :)
+#
+# Revision 1.52  2001/12/12 21:47:45  richard
+#  . Message author's name appears in From: instead of roundup instance name
+#    (which still appears in the Reply-To:)
+#  . envelope-from is now set to the roundup-admin and not roundup itself so
+#    delivery reports aren't sent to roundup (thanks Patrick Ohly)
+#
+# Revision 1.51  2001/12/10 00:57:38  richard
+# From CHANGES:
+#  . Added the "display" command to the admin tool - displays a node's values
+#  . #489760 ] [issue] only subject
+#  . fixed the doc/index.html to include the quoting in the mail alias.
+#
+# Also:
+#  . fixed roundup-admin so it works with transactions
+#  . disabled the back_anydbm module if anydbm tries to use dumbdbm
+#
+# Revision 1.50  2001/12/02 05:06:16  richard
+# . We now use weakrefs in the Classes to keep the database reference, so
+#   the close() method on the database is no longer needed.
+#   I bumped the minimum python requirement up to 2.1 accordingly.
+# . #487480 ] roundup-server
+# . #487476 ] INSTALL.txt
+#
+# I also cleaned up the change message / post-edit stuff in the cgi client.
+# There's now a clearly marked "TODO: append the change note" where I believe
+# the change note should be added there. The "changes" list will obviously
+# have to be modified to be a dict of the changes, or somesuch.
+#
+# More testing needed.
+#
+# Revision 1.49  2001/12/01 07:17:50  richard
+# . We now have basic transaction support! Information is only written to
+#   the database when the commit() method is called. Only the anydbm
+#   backend is modified in this way - neither of the bsddb backends have been.
+#   The mail, admin and cgi interfaces all use commit (except the admin tool
+#   doesn't have a commit command, so interactive users can't commit...)
+# . Fixed login/registration forwarding the user to the right page (or not,
+#   on a failure)
+#
+# Revision 1.48  2001/11/27 22:32:03  richard
+# typo
+#
+# Revision 1.47  2001/11/26 22:55:56  richard
+# Feature:
+#  . Added INSTANCE_NAME to configuration - used in web and email to identify
+#    the instance.
+#  . Added EMAIL_SIGNATURE_POSITION to indicate where to place the roundup
+#    signature info in e-mails.
+#  . Some more flexibility in the mail gateway and more error handling.
+#  . Login now takes you to the page you back to the were denied access to.
+#
+# Fixed:
+#  . Lots of bugs, thanks Roché and others on the devel mailing list!
+#
 # Revision 1.46  2001/11/21 03:40:54  richard
 # more new property handling
 #