← Back to team overview

gtg team mailing list archive

[Merge] lp:~gtg-contributors/gtg/code-layout-2 into lp:gtg

 

Paul Kishimoto has proposed merging lp:~gtg-contributors/gtg/code-layout-2 into lp:gtg.

Requested reviews:
  Gtg developers (gtg)


This is the second part of the code relayout previously mentioned. The logic behind this branch is: since the GTK UI goes in its own module, so does the CLI.

I asked Bryce about this before starting and he didn't think it was badly needed — but I've gone ahead and done it anyway :)

See the r806 commit message for a description of the changes. The main benefit is that it becomes more clear which imports etc. are dependencies of GTG generally, and which are specific just to the GTK UI. In GTG/gtg.py, the major if statement in main() will eventually also handle a choice to not start any UI at all, instead starting the GTG server.
-- 
https://code.launchpad.net/~gtg-contributors/gtg/code-layout-2/+merge/27538
Your team Gtg developers is requested to review the proposed merge of lp:~gtg-contributors/gtg/code-layout-2 into lp:gtg.
=== modified file 'GTG/__init__.py'
--- GTG/__init__.py	2010-03-12 11:16:15 +0000
+++ GTG/__init__.py	2010-06-14 17:38:27 +0000
@@ -19,76 +19,83 @@
 """
 Getting Things Gnome!  A personal organizer for the GNOME desktop
 """
-
+import locale
 import os
-import locale
-#Fallback to LANG C if unsupported locale
-try:
-    locale.setlocale(locale.LC_ALL, '')
-except:
-    locale.setlocale(locale.LC_ALL, 'C')
-
+from os.path import abspath, dirname, isdir, join, pardir
 import gettext
+
 try:
     from gtk import glade
     loaded_glade = glade
 except:
     #that's not pretty but it looks functional.
     loaded_glade = None
-from os.path import pardir, abspath, dirname, join
-
 try:
     from xdg.BaseDirectory import xdg_config_home
     config_home = xdg_config_home
 except ImportError:
-    config_home = os.path.dirname(__file__)
-
-LOCAL_ROOTDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
-DIST_ROOTDIR_LOCAL = "/usr/local/share/gtg"
-DIST_ROOTDIR = "/usr/share/gtg"
-
-#Translation setup (from pyroom)
+    config_home = dirname(__file__)
+
+
+__all__ = ('DATA_DIR', 'PLUGIN_DIR', '_', 'ngettext',)
+
+
 GETTEXT_DOMAIN = 'gtg'
-LOCALE_PATH = abspath(join(dirname(__file__), pardir, 'locales'))
-if not os.path.isdir(LOCALE_PATH):
-    if os.path.isdir('/usr/local/share/locale') and os.uname()[0] != 'Linux':
+# paths
+DIST_ROOTDIR = '/usr/share/gtg'
+DIST_ROOTDIR_LOCAL = '/usr/local/share/gtg'
+LOCAL_ROOTDIR = abspath(join(dirname(__file__), pardir))
+
+### translation setup (from pyroom)
+
+# fallback to LANG C if unsupported locale
+try:
+    locale.setlocale(locale.LC_ALL, '')
+except:
+    locale.setlocale(locale.LC_ALL, 'C')
+# find the path containing locales
+LOCALE_PATH = join(LOCAL_ROOTDIR, 'locales')
+if not isdir(LOCALE_PATH):
+    if isdir('/usr/local/share/locale') and os.uname()[0] != 'Linux':
         LOCALE_PATH = '/usr/local/share/locale'
     else:
         LOCALE_PATH = '/usr/share/locale'
+# list the languages in use: default locale first
 languages_used = []
 lc, encoding = locale.getdefaultlocale()
 if lc:
-    languages_used = [lc]
+    languages_used.append(lc)
+# add values from the LANGUAGE environment variable
 lang_in_env = os.environ.get('LANGUAGE', None)
 if lang_in_env:
     languages_used.extend(lang_in_env.split(':'))
-
+# set text domains in both gettext and glade. The latter avoids an error in
+# the Fedora build farm
 for module in gettext, loaded_glade:
-    #check if glade is well loaded to avoid error in Fedora build farm
     if module:
         module.bindtextdomain(GETTEXT_DOMAIN, LOCALE_PATH)
         module.textdomain(GETTEXT_DOMAIN)
-
+# finally, get the translation
 translation = gettext.translation(GETTEXT_DOMAIN, LOCALE_PATH,
                                   languages=languages_used,
                                   fallback=True)
-
 _ = translation.gettext
 ngettext = translation.ngettext
 
-#GTG directories setup
-if os.path.isdir(os.path.join(LOCAL_ROOTDIR, 'data')):
-    DATA_DIR = os.path.join(LOCAL_ROOTDIR, 'data')
-elif os.path.isdir(DIST_ROOTDIR_LOCAL):
-    DATA_DIR = DIST_ROOTDIR_LOCAL
-else:
-    DATA_DIR = DIST_ROOTDIR
-
-#GTG plugin dir setup
-if not os.path.isdir(os.path.join(LOCAL_ROOTDIR, 'GTG/plugins/')):
+### directory paths
+
+# find the path to the data directory
+DATA_DIR = join(LOCAL_ROOTDIR, 'data')
+if not isdir(DATA_DIR):
+    if isdir(DIST_ROOTDIR_LOCAL):
+        DATA_DIR = DIST_ROOTDIR_LOCAL
+    else:
+        DATA_DIR = DIST_ROOTDIR
+
+# find the path(s) to the plugin directory or directories
+PLUGIN_DIR = [join(LOCAL_ROOTDIR, 'GTG', 'plugins')]
+if not isdir(PLUGIN_DIR[0]):
     PLUGIN_DIR = [DIST_ROOTDIR]
-else:
-    PLUGIN_DIR = [os.path.join(LOCAL_ROOTDIR, 'GTG/plugins/')]
+if isdir(join(config_home, 'gtg', 'plugins')):
+    PLUGIN_DIR.append(abspath(join(config_home, 'gtg', 'plugins')))
 
-if os.path.isdir(os.path.join(config_home, 'gtg/plugins')):
-    PLUGIN_DIR.append(os.path.join(config_home, 'gtg/plugins'))

=== added directory 'GTG/cli'
=== renamed file 'gtcli' => 'GTG/cli/__init__.py' (properties changed: +x to -x)
--- gtcli	2010-06-11 01:23:57 +0000
+++ GTG/cli/__init__.py	2010-06-14 17:38:27 +0000
@@ -26,31 +26,24 @@
     return text
 
 def usage():
-    f = "  %-30s %s\n"
-    progname = sys.argv[0]
-
-    text = _("gtcli -- a command line interface to gtg\n")
-    text += "\n"
-
-    text += _("Options:\n")
-    text += f%( "-h, --help", _("This help") )
-    text += "\n"
-
-    text += _("Basic commands:\n")
-    text += f%( "gtcli new", _("Create a new task") )
-    text += f%( "gtcli show <tid>", _("Display detailed information on given task id") )
-    text += f%( "gtcli edit <tid>", _("Opens the GUI editor for the given task id") )
-    text += f%( "gtcli delete <tid>", _("Removes task identified by tid") )
-    text += f%( "gtcli list [all|today|<filter>|<tag>]...", _("List tasks") )
-    text += f%( "gtcli count [all|today|<filter>|<tag>]...", _("Number of tasks") )
-    text += f%( "gtcli summary [all|today|<filter>|<tag>]...", _("Report how many tasks starting/due each day") )
-    text += f%( "gtcli postpone <tid> <date>", _("Updates the start date of task") )
-    text += f%( "gtcli close <tid>", _("Sets state of task identified by tid to closed") )
-    text += f%( "gtcli browse [hide|show]", _("Hides or shows the task browser window"))
-
-    text += "\n"
-    text += "http://gtg.fritalk.com/\n";
-    sys.stderr.write( text )
+    commands = [
+      ('new', _("Create a new task") ),
+      ('show <tid>', _("Display detailed information on given task id") ),
+      ('edit <tid>', _("Opens the GUI editor for the given task id") ),
+      ('delete <tid>', _("Removes task identified by tid") ),
+      ('list [SPEC]', _("List tasks") ),
+      ('count [SPEC]', _("Number of tasks") ),
+      ('summary [SPEC]', _("Report how many tasks starting/due each day") ),
+      ('postpone <tid> <date>', _("Updates the start date of task") ),
+      ('close <tid>', _("Sets state of task identified by tid to closed") ),
+      ('browse [hide|show]', _("Hides or shows the task browser window")),
+      ]
+
+    text = 'Command-line options (with "gtg -u cli" or "gtcli"):\n'
+    text += '\n'.join(['  %-30s %s' % (cmd, help) for (cmd, help) in commands])
+    text += '\n\nSPEC is: all|today|<filter>|<tag>'
+    return text
+
 
 def die(code=1, err=None):
     if err:
@@ -260,24 +253,7 @@
             print "  %-8s  %s" %(task['id'], text)
 
 
-if __name__ == '__main__':
-    try:
-        opts, args = getopt.gnu_getopt(sys.argv[1:], "h", ["help"])
-    except getopt.GetoptError, err:
-        sys.stderr.write("Error: " + str(err) + "\n\n")
-        usage()
-        sys.exit(2)
-    for o, a in opts:
-        if o in ("-h", "--help"):
-            usage()
-            sys.exit(0)
-        else:
-            assert False, "unhandled option"
-
-    if len(args) < 1:
-        usage()
-        sys.exit(2)
-
+def main(options=None, args=None):
     command = args[0]
 
     if command == "new" or command == "add":
@@ -352,4 +328,3 @@
     else:
         die("Unknown command '%s'\n" %(command))
 
-

=== modified file 'GTG/gtg.py' (properties changed: +x to -x)
--- GTG/gtg.py	2010-06-07 21:14:45 +0000
+++ GTG/gtg.py	2010-06-14 17:38:27 +0000
@@ -46,104 +46,110 @@
 
 #=== IMPORT ===================================================================
 from __future__ import with_statement
-from contextlib import contextmanager
 import os
-import logging
-import signal
 
 import dbus
 
-#our own imports
-from GTG                import _, info
-from GTG.core           import CoreConfig
-from GTG.core.datastore import DataStore
-from GTG.gtk            import crashhandler
-from GTG.gtk.manager    import Manager
-from GTG.tools.logger   import Log
-
-#=== OBJECTS ==================================================================
-
-#code borrowed from Specto. Avoid having multiples instances of gtg
-#reading the same tasks
-#that's why we put the pid file in the data directory :
-#we allow one instance of gtg by data directory.
-
-def check_instance(directory):
-    """Check if gtg is already running."""
-    pidfile = os.path.join(directory, "gtg.pid")
-    if not os.path.exists(pidfile):
-        open(pidfile, "w").close()
-        os.chmod(pidfile, 0600)
-
-    #see if gtg is already running
-    pid = open(pidfile, "r").readline()
-    if pid:
-        p = os.system("/bin/ps %s >/dev/null" % pid)
-        p_name = os.popen("/bin/ps -f %s" % pid).read()
-        if p == 0 and "gtg" in p_name:
-            print _("gtg is already running!")
-            d=dbus.SessionBus().get_object(CoreConfig.BUSNAME,\
-                                           CoreConfig.BUSINTERFACE)
-            d.show_task_browser()
-            raise SystemExit
-            
-    #write the pid file
-    with open(pidfile, "w") as f:
-        f.write(`os.getpid()`)
-
-#=== MAIN CLASS ===============================================================
+from GTG import info
+
 
 def main(options=None, args=None):
-    # Debugging subsystem initialization
+    '''Run GTG according to user options & arguments.'''
+    # debugging system: start, if the user asked for it
     if options.debug:
+        import logging
         Log.setLevel(logging.DEBUG)
-        Log.debug("Debug output enabled.")
-    
-    config = CoreConfig()
-    check_instance(config.DATA_DIR)
-    backends_list = config.get_backends_list()
-
-    #initialize Apport hook for crash handling
-    crashhandler.initialize(app_name = "Getting Things GNOME!", message="GTG"
-      + info.VERSION + _(" has crashed. Please report the bug on <a href=\""
-      "http://bugs.edge.launchpad.net/gtg\";>our Launchpad page</a>. If you "
-      "have Apport installed, it will be started for you."), use_apport = True)
-    
-    # Load data store
-    ds = DataStore()
-    
-    for backend_dic in backends_list:
-        ds.register_backend(backend_dic)
-    
-    #save directly the backends to be sure to write projects.xml
-    config.save_datastore(ds,initial_save=True)
-        
-    # Launch task browser
-    req = ds.get_requester()
-    manager = Manager(req, config)
- 
-    #we listen for signals from the system in order to save our configuration
-    # if GTG is forcefully terminated (e.g.: on shutdown).
-    @contextmanager
-    def signal_catcher():
-        #if TERM or ABORT are caught, we close the browser
-        for s in [signal.SIGABRT, signal.SIGTERM]:
-            signal.signal(s, lambda a,b: manager.close_browser())
-        yield
-
-    #main loop
-    with signal_catcher():
-        manager.main()
-      
-    # Ideally we should load window geometry configuration from a config.
-    # backend like gconf at some point, and restore the appearance of the
-    # application as the user last exited it.
-
-    # Ending the application: we save configuration
-    config.save_config()
-    config.save_datastore(ds)
-
-#=== EXECUTION ================================================================
-
-if __name__ == "__main__":
-    main()
+        Log.debug('Debug output enabled.')
+    # launch one or another of the user interfaces
+    if options.interface == 'cli':
+        # the command-line interface
+        import GTG.cli
+        return GTG.cli.main(options, args)
+    elif options.interface == 'gtk':
+        # TODO: separate the backend and GTK parts of this code block
+        # import a lot of stuff not needed for the CLI
+        from contextlib import contextmanager
+        from GTG                import _
+        from GTG.core           import CoreConfig
+        from GTG.core.datastore import DataStore
+        from GTG.gtk            import crashhandler
+        from GTG.gtk.manager    import Manager
+        from GTG.tools.logger   import Log
+
+        def check_instance(config):
+            '''Check if gtg is already running.
+            
+            Code borrowed from Specto. Avoid having multiples instances of gtg
+            reading the same tasks. That's why we put the pid file in the data
+            directory; we allow one instance of gtg by data directory.
+            
+            '''
+            # file containing the process ID
+            pidfile = os.path.join(config.DATA_DIR, 'gtg.pid')
+            # create the pidfile if necessary
+            if not os.path.exists(pidfile):
+                open(pidfile, 'w').close()
+                os.chmod(pidfile, 0600)
+            # see if gtg is already running
+            pid = open(pidfile, 'r').readline()
+            if pid:
+                # the file contained a PID! Check if it's stale or not
+                p = os.system('/bin/ps %s >/dev/null' % pid)
+                p_name = os.popen('/bin/ps -f %s' % pid).read()
+                if p == 0 and 'gtg' in p_name:
+                    print _('gtg is already running!')
+                    # raise the TaskBrowser for the user
+                    d = dbus.SessionBus().get_object(config.BUSNAME,
+                                                     config.BUSINTERFACE)
+                    d.show_task_browser()
+                    raise SystemExit
+            # write the current PID
+            with open(pidfile, 'w') as f:
+                f.write(`os.getpid()`)
+
+        # check that the X server is running
+        if 'DISPLAY' not in os.environ:
+            raise SystemExit(1)
+        # load configuration information
+        config = CoreConfig()
+        # check that GTG is not already running
+        check_instance(config)
+        # initialize Apport hook for crash handling
+        crashhandler.initialize(app_name='Getting Things GNOME!', message='GTG'
+          + info.VERSION + _(' has crashed. Please report the bug on <a href='
+          '\"http://bugs.edge.launchpad.net/gtg\";>our Launchpad page</a>. If '
+          'you have Apport installed, it will be started for you.'),
+          use_apport=True)
+        # load data store and register configured backends
+        ds = DataStore()
+        for backend_dic in config.get_backends_list():
+            ds.register_backend(backend_dic)
+        # save directly the backends to be sure to write projects.xml
+        config.save_datastore(ds, initial_save=True)  
+        # get a Requester object and start the ViewManager
+        req = ds.get_requester()
+        manager = Manager(req, config)
+        # TODO: ideally we should load window geometry configuration from a
+        # config.backend like gconf at some point, and restore the appearance
+        # of the application as the user last exited it.
+
+        # we listen for signals from the system in order to save our 
+        # configuration if GTG is forcefully terminated (e.g.: on shutdown).
+        @contextmanager
+        def signal_catcher():
+            import signal
+            #if TERM or ABORT are caught, we close the browser
+            for s in (signal.SIGABRT, signal.SIGTERM,):
+                signal.signal(s, lambda a, b: manager.close_browser())
+            yield
+        # main loop
+        with signal_catcher():
+            try:
+                retcode = manager.main()
+            except KeyboardInterrupt:
+                retcode = 0
+        # Ending the application: we save configuration
+        config.save_config()
+        config.save_datastore(ds)
+        return retcode
+

=== added file 'gtcli'
--- gtcli	1970-01-01 00:00:00 +0000
+++ gtcli	2010-06-14 17:38:27 +0000
@@ -0,0 +1,12 @@
+#!/bin/sh -e
+
+# Copyright (c) 2010 Paul Kishimoto
+#
+# This file is part of Getting Things GNOME!. Getting Things GNOME! is free
+# software; you can redistribute it and/or modify it under the terms of the GNU
+# General Public License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version. See the file
+# LICENSE or <http://www.gnu.org/licenses/> for the full text of the license.
+
+exec gtg -u cli "$@"
+

=== modified file 'gtg'
--- gtg	2010-06-02 18:12:23 +0000
+++ gtg	2010-06-14 17:38:27 +0000
@@ -26,31 +26,26 @@
 :copyright: 2008,2009 Lionel Dricot & Bertrand Rousseau
 :license: GNU General Public License, version 3 or later
 """
-
-import sys
-from optparse import OptionParser
-
-
-def X_is_running():
-    from subprocess import Popen, PIPE
-    p = Popen(["xset", "-q"], stdout=PIPE, stderr=PIPE)
-    p.communicate()
-    return p.returncode == 0
-
-
-try:
-    parser = OptionParser()
-    parser.add_option('-d', '--debug', action='store_true', dest='debug',
-      help="enable debug output", default=False)
-    (options, args) = parser.parse_args()
-
-    if not X_is_running():
-        print "Could not open X display"
-        sys.exit(1)
-
-    else:
-        import GTG.gtg
-        sys.exit(GTG.gtg.main(options, args))
-
-except KeyboardInterrupt:
-    sys.exit(1)
+from optparse import OptionGroup, OptionParser
+import os # capture DISPLAY: http://docs.python.org/library/os.html#os.environ
+
+from GTG.cli import usage
+
+
+parser = OptionParser(usage='%prog [options] [[command] [command_opts]] \n\n' +
+  usage())
+# options common to all invocations of 'gtg'
+parser.add_option('-d', '--debug', action='store_true', dest='debug',
+  help="enable debug output", default=False)
+parser.add_option('-u', '--interface', dest='interface', choices=('cli',
+  'gtk', 'none',), default='gtk', metavar='IFACE', help='Use the IFACE user '
+  'interface. Available UIs: gtk (default), cli, none.')
+# parse command-line options
+(options, args) = parser.parse_args()
+if options.interface == 'cli' and len(args) < 1:
+    parser.print_help()
+    raise SystemExit(2)
+# if that wasn't a problem, run the application!
+import GTG.gtg
+raise SystemExit(GTG.gtg.main(options, args))
+

=== modified file 'gtg_new_task'
--- gtg_new_task	2010-02-08 11:08:25 +0000
+++ gtg_new_task	2010-06-14 17:38:27 +0000
@@ -1,77 +1,12 @@
-#!/usr/bin/env python
-# -*- coding:utf-8 -*-
-
-# -----------------------------------------------------------------------------
-# Getting Things Gnome! - A personal organizer for the GNOME desktop
-# Copyright (c) 2008,2009 Lionel Dricot & Bertrand Rousseau
-#
-# This program is free software: you can redistribute it and/or modify it under
-# the terms of the GNU General Public License as published by the Free Software
-# Foundation, either version 3 of the License, or (at your option) any later
-# version.
-#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-# details.
-#
-# You should have received a copy of the GNU General Public License along with
-# this program.  If not, see <http://www.gnu.org/licenses/>.
-# -----------------------------------------------------------------------------
-
-"""
-This script create a new task and launch the editor to display it. GTG should be running
-"""
-
-import re
-import sys
-import dbus
-import cgi
-import getopt
-from GTG import _
-
-def get_task(title, body) :
-    #We will connect on the session bus
-    bus = dbus.SessionBus()
-    liste = bus.list_names()
-    busname = "org.GTG"
-    remote_object = bus.get_object(busname,"/org/GTG")
-    timi = dbus.Interface(remote_object,dbus_interface="org.GTG")
-    #Calling the method
-    timi.open_new_task(title, body)
-
-def usage():
-    print _("Usage: %s [-i | --interactive] [-h | --help]") % sys.argv[0]
-        
-if __name__ == '__main__':
-    interactive = False
-    #Command line options handling
-    try:
-        opts, args = getopt.getopt(sys.argv[1:], "hi", ["help", "interactive"])
-    except getopt.GetoptError, err:
-        # print help information and exit:
-        print str(err) # will print something like "option -a not recognized"
-        usage()
-        sys.exit(2)
-    for o, a in opts:
-        if o in ("-i", "--interactive"):
-            interactive = True
-        elif o in ("-h", "--help"):
-            usage()
-            sys.exit()
-        else:
-            assert False, "unhandled option"
-
-    title = " ".join(args)
-    if interactive:
-        optlist, args = getopt.getopt(args, 'i::')
-        body = sys.stdin.read()
-        subject_regex = re.compile("^Subject: (.*)$", re.M | re.I)
-        if subject_regex.search(body):
-            subject = subject_regex.findall(body)[0]
-            title = title + ": " + subject
-    else:
-        body = ""
-    get_task(title, cgi.escape(body))
-
+#!/bin/sh -e
+
+# Copyright (c) 2010 Paul Kishimoto
+#
+# This file is part of Getting Things GNOME!. Getting Things GNOME! is free
+# software; you can redistribute it and/or modify it under the terms of the GNU
+# General Public License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version. See the file
+# LICENSE or <http://www.gnu.org/licenses/> for the full text of the license.
+
+exec gtg -u cli new "$@"