← Back to team overview

gtg team mailing list archive

[Merge] lp:~izidor/gtg/documentation into lp:gtg

 

Izidor Matušov has proposed merging lp:~izidor/gtg/documentation into lp:gtg.

Requested reviews:
  Gtg developers (gtg)

For more details, see:
https://code.launchpad.net/~izidor/gtg/documentation/+merge/210111

Introduce developer documentation using Sphinx. It is replacement for Pydoctor. Because readthedocs.org expects documentation to be in docs/ folder, I had to move doc to docs. By that occasion I fixed setup.py:

 - it generates lists of files and packages instead of listing them (no more dead files there)
 - I looked into upgrading to setuptools but it does not support installation in /usr/share :(
 - generate man files from sphinx documentation (RST is much friendlier format)
 - allow installation of GTG on case insensitive file systems

I also removed special handling for liblarch. Everybody should have installed it already. For development we should use virtualenv or virtualenvwrapper :)
-- 
https://code.launchpad.net/~izidor/gtg/documentation/+merge/210111
Your team Gtg developers is requested to review the proposed merge of lp:~izidor/gtg/documentation into lp:gtg.
=== modified file 'GTG/backends/backend_gnote.py'
--- GTG/backends/backend_gnote.py	2013-11-23 14:40:23 +0000
+++ GTG/backends/backend_gnote.py	2014-03-10 01:04:59 +0000
@@ -54,6 +54,6 @@
             GenericBackend.PARAM_DEFAULT_VALUE: ["@GTG-Gnote"]},
     }
 
-    _BUS_ADDRESS = ("org.gnome.Gnote",
-                    "/org/gnome/Gnote/RemoteControl",
-                    "org.gnome.Gnote.RemoteControl")
+    BUS_ADDRESS = ("org.gnome.Gnote",
+                   "/org/gnome/Gnote/RemoteControl",
+                   "org.gnome.Gnote.RemoteControl")

=== modified file 'GTG/backends/backend_launchpad.py'
--- GTG/backends/backend_launchpad.py	2014-03-09 12:59:59 +0000
+++ GTG/backends/backend_launchpad.py	2014-03-10 01:04:59 +0000
@@ -26,12 +26,9 @@
 import uuid
 import datetime
 from xdg.BaseDirectory import xdg_cache_home
-from launchpadlib.launchpad import Launchpad, \
-    STAGING_SERVICE_ROOT, \
-    EDGE_SERVICE_ROOT
+from launchpadlib.launchpad import Launchpad, EDGE_SERVICE_ROOT
 
 from GTG.core.task import Task
-from GTG.tools.testingmode import TestingMode
 from GTG import _
 from GTG.backends.genericbackend import GenericBackend
 from GTG.backends.backendsignals import BackendSignals
@@ -122,15 +119,10 @@
         # Connecting to Launchpad
         CACHE_DIR = os.path.join(xdg_cache_home, 'gtg/backends/',
                                  self.get_id())
-        if TestingMode().get_testing_mode():
-            SERVICE_ROOT = STAGING_SERVICE_ROOT
-        else:
-            SERVICE_ROOT = EDGE_SERVICE_ROOT
         try:
             self.cancellation_point()
-            self.launchpad = Launchpad.login_anonymously(GTG_NAME,
-                                                         SERVICE_ROOT,
-                                                         CACHE_DIR)
+            self.launchpad = Launchpad.login_anonymously(
+                GTG_NAME, EDGE_SERVICE_ROOT, CACHE_DIR)
         except:
             # The connection is not working (the exception type can be
             # anything)

=== modified file 'GTG/backends/backend_tomboy.py'
--- GTG/backends/backend_tomboy.py	2013-11-23 14:40:23 +0000
+++ GTG/backends/backend_tomboy.py	2014-03-10 01:04:59 +0000
@@ -53,6 +53,6 @@
             GenericBackend.PARAM_DEFAULT_VALUE: ["@GTG-Tomboy"]},
     }
 
-    _BUS_ADDRESS = ("org.gnome.Tomboy",
-                    "/org/gnome/Tomboy/RemoteControl",
-                    "org.gnome.Tomboy.RemoteControl")
+    BUS_ADDRESS = ("org.gnome.Tomboy",
+                   "/org/gnome/Tomboy/RemoteControl",
+                   "org.gnome.Tomboy.RemoteControl")

=== modified file 'GTG/backends/generictomboy.py'
--- GTG/backends/generictomboy.py	2014-01-12 07:15:09 +0000
+++ GTG/backends/generictomboy.py	2014-03-10 01:04:59 +0000
@@ -30,7 +30,6 @@
 import datetime
 import unicodedata
 
-from GTG.tools.testingmode import TestingMode
 from GTG.tools.borg import Borg
 from GTG.backends.genericbackend import GenericBackend
 from GTG.backends.backendsignals import BackendSignals
@@ -57,14 +56,6 @@
                                       "sync_engine-" + self.get_id())
         self.sync_engine = self._load_pickled_file(self.data_path,
                                                    SyncEngine())
-        # if the backend is being tested, we connect to a different DBus
-        # interface to avoid clashing with a running instance of Tomboy
-        if TestingMode().get_testing_mode():
-            # just used for testing purposes
-            self.BUS_ADDRESS = \
-                self._parameters["use this fake connection instead"]
-        else:
-            self.BUS_ADDRESS = self._BUS_ADDRESS
         # we let some time pass before considering a tomboy task for importing,
         # as the user may still be editing it. Here, we store the Timer objects
         # that will execute after some time after each tomboy signal.

=== modified file 'GTG/core/__init__.py'
--- GTG/core/__init__.py	2013-11-25 02:37:46 +0000
+++ GTG/core/__init__.py	2014-03-10 01:04:59 +0000
@@ -43,7 +43,6 @@
 import os
 
 from GTG.tools.borg import Borg
-from GTG.tools.testingmode import TestingMode
 import GTG
 
 DEFAULTS = {
@@ -219,13 +218,8 @@
         if hasattr(self, 'data_dir'):
             # Borg has already been initialized
             return
-        if TestingMode().get_testing_mode():
-            # we avoid running tests in the user data dir
-            self.data_dir = '/tmp/GTG_TESTS/data'
-            self.conf_dir = '/tmp/GTG_TESTS/conf'
-        else:
-            self.data_dir = os.path.join(xdg_data_home, 'gtg/')
-            self.conf_dir = os.path.join(xdg_config_home, 'gtg/')
+        self.data_dir = os.path.join(xdg_data_home, 'gtg/')
+        self.conf_dir = os.path.join(xdg_config_home, 'gtg/')
         if not os.path.exists(self.conf_dir):
             os.makedirs(self.conf_dir)
         if not os.path.exists(self.data_dir):

=== modified file 'GTG/core/tag.py'
--- GTG/core/tag.py	2013-11-25 02:37:46 +0000
+++ GTG/core/tag.py	2014-03-10 01:04:59 +0000
@@ -120,7 +120,7 @@
         """
         modified = False
         if att_name == "name":
-            raise Set_Name_Attribute_Error(
+            raise KeyError(
                 "The name of tag cannot be set manually")
         elif att_name == "parent":
             self.add_parent(att_value)
@@ -246,8 +246,3 @@
 
     def __str__(self):
         return "Tag: %s" % self.get_name()
-
-
-class Set_Name_Attribute_Error(Exception):
-    """Exception raised when try to set attribute to name"""
-    pass

=== renamed file 'gtcli' => 'GTG/gtcli'
=== renamed file 'GTG/gtg.py' => 'GTG/gtg' (properties changed: -x to +x)
--- GTG/gtg.py	2013-11-25 02:37:46 +0000
+++ GTG/gtg	2014-03-10 01:04:59 +0000
@@ -50,9 +50,13 @@
 import logging
 import dbus
 
+from optparse import OptionParser
+from gi.repository.Gdk import Screen
+
 # our own imports
+from GTG import _
+from GTG import info
 from GTG.backends import BackendFactory
-from GTG import _
 from GTG.core import CoreConfig
 from GTG.core.datastore import DataStore
 from GTG.gtk.manager import Manager
@@ -111,13 +115,58 @@
         # Ignore missing PID file
         pass
 
+
+def x_is_running():
+    """ Return True if GTG could be displayed on the current XServer """
+    try:
+        if Screen().get_default().get_display():
+            return True
+    except RuntimeError as exc:
+        print(exc)
+    return False
+
 #=== MAIN CLASS ===============================================================
 
 
-def main(options=None, args=None):
-    '''
-    Calling this starts the full GTG experience  ( :-D )
-    '''
+def parse_args():
+    """ Parse arguments """
+    parser = OptionParser()
+    parser.add_option('-b', '--boot-test', action='store_true',
+                      dest='boot_test',
+                      help="Exit after completing boot-up actions",
+                      default=False)
+    parser.add_option('-c', '--no-crash-handler', action='store_true',
+                      dest='no_crash_handler',
+                      help="Disable the automatic crash handler",
+                      default=False)
+    parser.add_option('-d', '--debug', action='store_true', dest='debug',
+                      help="Enable debug output", default=False)
+    parser.add_option('-t', '--title', action='store',
+                      help="Use special title for windows' title")
+    parser.add_option('-v', '--version', action='store_true',
+                      dest='print_version', help="Print GTG's version number",
+                      default=False)
+    return parser.parse_args()
+
+
+def main():
+    '''
+    Calling this starts the full GTG experience
+    '''
+    options, args = parse_args()
+    if options.print_version:
+        print("GTG (Getting Things GNOME!)", info.VERSION)
+        print()
+        print("For more information:", info.URL)
+        sys.exit(0)
+
+    elif not x_is_running():
+        print("Could not open X display")
+        sys.exit(1)
+
+    if options.title is not None:
+        info.NAME = options.title
+
     ds, req = core_main_init(options, args)
     # Launch task browser
     manager = Manager(req)
@@ -180,4 +229,7 @@
 #=== EXECUTION ================================================================
 
 if __name__ == "__main__":
-    main()
+    try:
+        main()
+    except KeyboardInterrupt:
+        sys.exit(1)

=== renamed file 'gtg_new_task' => 'GTG/gtg_new_task'
=== removed directory 'GTG/tests'
=== removed file 'GTG/tests/signals_testing.py'
--- GTG/tests/signals_testing.py	2014-03-09 12:59:59 +0000
+++ GTG/tests/signals_testing.py	1970-01-01 00:00:00 +0000
@@ -1,149 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 - 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/>.
-# -----------------------------------------------------------------------------
-
-import threading
-from gi.repository import GObject
-import time
-
-from GTG.tools.watchdog import Watchdog
-
-
-class SignalCatcher(object):
-    """ A class to test signals """
-
-    def __init__(self, unittest, generator, signal_name,
-                 should_be_caught=True, how_many_signals=1,
-                 error_code="No error code set"):
-        self.signal_catched_event = threading.Event()
-        self.generator = generator
-        self.signal_name = signal_name
-        self.signal_arguments = []
-        self.unittest = unittest
-        self.how_many_signals = how_many_signals
-        self.should_be_caught = should_be_caught
-        self.error_code = error_code
-
-        def _on_failure():
-            # we need to release the waiting thread
-            self.signal_catched_event.set()
-            self.missed = True
-            # then we notify the error
-            # if the error_code is set to None, we're expecting it to fail.
-            if error_code is not None:
-                print("An expected signal wasn't received %s" %
-                      str(error_code))
-            self.unittest.assertFalse(should_be_caught)
-
-        self.watchdog = Watchdog(3, _on_failure)
-
-    def __enter__(self):
-
-        def __signal_callback(*args):
-            self.signal_arguments.append(args[1:])
-            if len(self.signal_arguments) >= self.how_many_signals:
-                self.signal_catched_event.set()
-
-        self.handler = \
-            self.generator.connect(self.signal_name, __signal_callback)
-        self.watchdog.__enter__()
-        return [self.signal_catched_event, self.signal_arguments]
-
-    def __exit__(self, err_type, value, traceback):
-        self.generator.disconnect(self.handler)
-        if not self.should_be_caught and not hasattr(self, 'missed'):
-            self.assertFalse(True)
-        return not isinstance(value, Exception) and \
-            self.watchdog.__exit__(err_type, value, traceback)
-
-
-class CallbackCatcher(object):
-    """ A class to test callbacks """
-
-    def __init__(self, unittest, generator, signal_name,
-                 should_be_caught=True, how_many_signals=1,
-                 error_code="No error code set"):
-        self.signal_catched_event = threading.Event()
-        self.generator = generator
-        self.signal_name = signal_name
-        self.signal_arguments = []
-        self.unittest = unittest
-        self.how_many_signals = how_many_signals
-        self.should_be_caught = should_be_caught
-        self.error_code = error_code
-
-        def _on_failure():
-            # we need to release the waiting thread
-            self.signal_catched_event.set()
-            self.missed = True
-            # then we notify the error
-            # if the error_code is set to None, we're expecting it to fail.
-            if error_code is not None:
-                print("An expected signal wasn't received %s" %
-                      str(error_code))
-            self.unittest.assertFalse(should_be_caught)
-
-        self.watchdog = Watchdog(3, _on_failure)
-
-    def __enter__(self):
-
-        def __signal_callback(*args):
-            """ Difference to SignalCatcher is that we do not skip
-            the first argument. The first argument by signals is widget
-            which sends the signal -- we omit this feature when
-            using callbacks """
-            self.signal_arguments.append(args)
-            if len(self.signal_arguments) >= self.how_many_signals:
-                self.signal_catched_event.set()
-
-        self.handler = self.generator.register_cllbck(self.signal_name,
-                                                      __signal_callback)
-        self.watchdog.__enter__()
-        return [self.signal_catched_event, self.signal_arguments]
-
-    def __exit__(self, err_type, value, traceback):
-        self.generator.deregister_cllbck(self.signal_name, self.handler)
-        if not self.should_be_caught and not hasattr(self, 'missed'):
-            self.assertFalse(True)
-        return not isinstance(value, Exception) and \
-            self.watchdog.__exit__(err_type, value, traceback)
-
-
-class GobjectSignalsManager(object):
-
-    def init_signals(self):
-        """
-        Initializes the gobject main loop so that signals can be used.
-        This function returns only when the gobject main loop is running
-        """
-
-        def gobject_main_loop():
-            GObject.threads_init()
-            self.main_loop = GObject.MainLoop()
-            self.main_loop.run()
-
-        threading.Thread(target=gobject_main_loop).start()
-        while not hasattr(self, 'main_loop') or \
-                not self.main_loop.is_running():
-            # since running the gobject main loop is a blocking call, we have
-            # to check that it has been started in a polling fashion
-            time.sleep(0.1)
-
-    def terminate_signals(self):
-#        if has_attr(self,'main_loop'):
-        self.main_loop.quit()

=== removed file 'GTG/tests/test_apidocs.py'
--- GTG/tests/test_apidocs.py	2013-11-23 14:40:23 +0000
+++ GTG/tests/test_apidocs.py	1970-01-01 00:00:00 +0000
@@ -1,57 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 - 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/>.
-# -----------------------------------------------------------------------------
-
-""" Tests for the documentation. """
-
-import unittest
-
-import subprocess
-import GTG
-import os.path
-import shutil
-import uuid
-
-
-class TestApiDocs(unittest.TestCase):
-    """ Test if the documentation still builds. """
-
-    def test_pydoctor(self):
-        if int(subprocess.call(['which', 'pydoctor'], stdout=subprocess.PIPE)):
-            # if no pydoctor is present, abort the test w/out giving error
-            return
-        GTG_basedir = os.path.dirname(GTG.__file__)
-        api_dir = os.path.join(GTG_basedir, '..', 'tmp',
-                               'test_build_api-' + str(uuid.uuid4()))
-        if not os.path.isdir(api_dir):
-            os.makedirs(api_dir)
-        args = ['pydoctor',
-                '--add-package', GTG_basedir,
-                '--make-html',
-                '--html-output=' + api_dir,
-                '--project-name=GTG',
-                '--project-url=http://gtg.fritalk.com/']
-        # we suppress printing of errors to keep a clean output
-        assert(int(subprocess.call(args,
-                                   stdout=subprocess.PIPE,
-                                   stderr=subprocess.PIPE)) == 0)
-        shutil.rmtree(api_dir)
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(TestApiDocs)

=== removed file 'GTG/tests/test_backend_tomboy.py'
--- GTG/tests/test_backend_tomboy.py	2014-03-09 12:59:59 +0000
+++ GTG/tests/test_backend_tomboy.py	1970-01-01 00:00:00 +0000
@@ -1,390 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 - 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/>.
-# -----------------------------------------------------------------------------
-
-""" Tests for the tomboy backend """
-
-from datetime import datetime
-from dbus.mainloop.glib import DBusGMainLoop
-import dbus
-import dbus.glib
-import dbus.service
-import errno
-from gi.repository import GObject
-import math
-import os
-import random
-import signal
-import sys
-import tempfile
-import threading
-import time
-import unittest
-import uuid
-
-from GTG.backends import BackendFactory
-from GTG.backends.genericbackend import GenericBackend
-from GTG.core.datastore import DataStore
-
-PID_TOMBOY = False
-
-
-class TestBackendTomboy(unittest.TestCase):
-    """ Tests for the tomboy backend """
-
-    def setUp(self):
-        thread_tomboy = threading.Thread(target=self.spawn_fake_tomboy_server)
-        thread_tomboy.start()
-        thread_tomboy.join()
-        # only the test process should go further, the dbus server one should
-        # stop here
-        if not PID_TOMBOY:
-            return
-        # we create a custom dictionary listening to the server, and register
-        # it in GTG.
-        additional_dic = {}
-        additional_dic["use this fake connection instead"] = (
-            FakeTomboy.BUS_NAME, FakeTomboy.BUS_PATH, FakeTomboy.BUS_INTERFACE)
-        additional_dic[GenericBackend.KEY_ATTACHED_TAGS] = \
-            [GenericBackend.ALLTASKS_TAG]
-        additional_dic[GenericBackend.KEY_DEFAULT_BACKEND] = True
-        dic = BackendFactory().get_new_backend_dict('backend_tomboy',
-                                                    additional_dic)
-        self.datastore = DataStore()
-        self.backend = self.datastore.register_backend(dic)
-        # waiting for the "start_get_tasks" to settle
-        time.sleep(1)
-        # we create a dbus session to speak with the server
-        self.bus = dbus.SessionBus()
-        obj = self.bus.get_object(FakeTomboy.BUS_NAME, FakeTomboy.BUS_PATH)
-        self.tomboy = dbus.Interface(obj, FakeTomboy.BUS_INTERFACE)
-
-    def spawn_fake_tomboy_server(self):
-        # the fake tomboy server has to be in a different process,
-        # otherwise it will lock on the GIL.
-        # For details, see
-        # http://lists.freedesktop.org/archives/dbus/2007-January/006921.html
-
-        # we use a lockfile to make sure the server is running before we start
-        # the test
-        global PID_TOMBOY
-        lockfile_fd, lockfile_path = tempfile.mkstemp()
-        PID_TOMBOY = os.fork()
-        if PID_TOMBOY:
-            # we wait in polling that the server has been started
-            while True:
-                try:
-                    fd = os.open(lockfile_path,
-                                 os.O_CREAT | os.O_EXCL | os.O_RDWR)
-                except OSError as e:
-                    if e.errno != errno.EEXIST:
-                        raise
-                    time.sleep(0.3)
-                    continue
-                os.close(fd)
-                break
-        else:
-            FakeTomboy()
-            os.close(lockfile_fd)
-            os.unlink(lockfile_path)
-
-    def tearDown(self):
-        if not PID_TOMBOY:
-            return
-        self.datastore.save(quit=True)
-        time.sleep(0.5)
-        self.tomboy.FakeQuit()
-        # FIXME: self.bus.close()
-        os.kill(PID_TOMBOY, signal.SIGKILL)
-        os.waitpid(PID_TOMBOY, 0)
-
-    def test_everything(self):
-        # we cannot use separate test functions because we only want a single
-        # FakeTomboy dbus server running
-        if not PID_TOMBOY:
-            return
-        for function in dir(self):
-            if function.startswith("TEST_"):
-                getattr(self, function)()
-                self.tomboy.Reset()
-                for tid in self.datastore.get_all_tasks():
-                    self.datastore.request_task_deletion(tid)
-                time.sleep(0.1)
-
-    def TEST_processing_tomboy_notes(self):
-        self.backend.set_attached_tags([GenericBackend.ALLTASKS_TAG])
-        # adding a note
-        note = self.tomboy.CreateNamedNote(str(uuid.uuid4()))
-        self.backend._process_tomboy_note(note)
-        self.assertEqual(len(self.datastore.get_all_tasks()), 1)
-        tid = self.backend.sync_engine.sync_memes.get_local_id(note)
-        task = self.datastore.get_task(tid)
-        # re-adding that (should not change anything)
-        self.backend._process_tomboy_note(note)
-        self.assertEqual(len(self.datastore.get_all_tasks()), 1)
-        self.assertEqual(
-            self.backend.sync_engine.sync_memes.get_local_id(note), tid)
-        # removing the note and updating gtg
-        self.tomboy.DeleteNote(note)
-        self.backend.set_task(task)
-        self.assertEqual(len(self.datastore.get_all_tasks()), 0)
-
-    def TEST_set_task(self):
-        self.backend.set_attached_tags([GenericBackend.ALLTASKS_TAG])
-        # adding a task
-        task = self.datastore.requester.new_task()
-        task.set_title("title")
-        self.backend.set_task(task)
-        self.assertEqual(len(self.tomboy.ListAllNotes()), 1)
-        note = self.tomboy.ListAllNotes()[0]
-        self.assertEqual(str(self.tomboy.GetNoteTitle(note)), task.get_title())
-        # re-adding that (should not change anything)
-        self.backend.set_task(task)
-        self.assertEqual(len(self.tomboy.ListAllNotes()), 1)
-        self.assertEqual(note, self.tomboy.ListAllNotes()[0])
-        # removing the task and updating tomboy
-        self.datastore.request_task_deletion(task.get_id())
-        self.backend._process_tomboy_note(note)
-        self.assertEqual(len(self.tomboy.ListAllNotes()), 0)
-
-    def TEST_update_newest(self):
-        self.backend.set_attached_tags([GenericBackend.ALLTASKS_TAG])
-        task = self.datastore.requester.new_task()
-        task.set_title("title")
-        self.backend.set_task(task)
-        note = self.tomboy.ListAllNotes()[0]
-        gtg_modified = task.get_modified()
-        tomboy_modified = self._modified_string_to_datetime(
-            self.tomboy.GetNoteChangeDate(note))
-        # no-one updated, nothing should happen
-        self.backend.set_task(task)
-        self.assertEqual(gtg_modified, task.get_modified())
-        actual_modified = self._modified_string_to_datetime(
-            self.tomboy.GetNoteChangeDate(note))
-        self.assertEqual(tomboy_modified, actual_modified)
-        # we update the GTG task
-        UPDATED_GTG_TITLE = "UPDATED_GTG_TITLE"
-        task.set_title(UPDATED_GTG_TITLE)
-        self.backend.set_task(task)
-        self.assertTrue(gtg_modified < task.get_modified())
-        actual_modified = self._modified_string_to_datetime(
-            self.tomboy.GetNoteChangeDate(note))
-        self.assertTrue(tomboy_modified <= actual_modified)
-        self.assertEqual(task.get_title(), UPDATED_GTG_TITLE)
-        self.assertEqual(self.tomboy.GetNoteTitle(note), UPDATED_GTG_TITLE)
-        gtg_modified = task.get_modified()
-        tomboy_modified = self._modified_string_to_datetime(
-            self.tomboy.GetNoteChangeDate(note))
-        # we update the TOMBOY task
-        UPDATED_TOMBOY_TITLE = "UPDATED_TOMBOY_TITLE"
-        # the resolution of tomboy notes changed time is 1 second, so we need
-        # to wait. This *shouldn't* be needed in the actual code because
-        # tomboy signals are always a few seconds late.
-        time.sleep(1)
-        self.tomboy.SetNoteContents(note, UPDATED_TOMBOY_TITLE)
-        self.backend._process_tomboy_note(note)
-        self.assertTrue(gtg_modified <= task.get_modified())
-        actual_modified = self._modified_string_to_datetime(
-            self.tomboy.GetNoteChangeDate(note))
-        self.assertTrue(tomboy_modified <= actual_modified)
-        self.assertEqual(task.get_title(), UPDATED_TOMBOY_TITLE)
-        self.assertEqual(self.tomboy.GetNoteTitle(note), UPDATED_TOMBOY_TITLE)
-
-    def TEST_processing_tomboy_notes_with_tags(self):
-        self.backend.set_attached_tags(['@a'])
-        # adding a not syncable note
-        note = self.tomboy.CreateNamedNote("title" + str(uuid.uuid4()))
-        self.backend._process_tomboy_note(note)
-        self.assertEqual(len(self.datastore.get_all_tasks()), 0)
-        # re-adding that (should not change anything)
-        self.backend._process_tomboy_note(note)
-        self.assertEqual(len(self.datastore.get_all_tasks()), 0)
-        # adding a tag to that note
-        self.tomboy.SetNoteContents(note, "something with @a")
-        self.backend._process_tomboy_note(note)
-        self.assertEqual(len(self.datastore.get_all_tasks()), 1)
-        # removing the tag and resyncing
-        self.tomboy.SetNoteContents(note, "something with no tags")
-        self.backend._process_tomboy_note(note)
-        self.assertEqual(len(self.datastore.get_all_tasks()), 0)
-        # adding a syncable note
-        note = self.tomboy.CreateNamedNote("title @a" + str(uuid.uuid4()))
-        self.backend._process_tomboy_note(note)
-        self.assertEqual(len(self.datastore.get_all_tasks()), 1)
-        tid = self.backend.sync_engine.sync_memes.get_local_id(note)
-        task = self.datastore.get_task(tid)
-        # re-adding that (should not change anything)
-        self.backend._process_tomboy_note(note)
-        self.assertEqual(len(self.datastore.get_all_tasks()), 1)
-        self.assertEqual(
-            self.backend.sync_engine.sync_memes.get_local_id(note), tid)
-        # removing the note and updating gtg
-        self.tomboy.DeleteNote(note)
-        self.backend.set_task(task)
-        self.assertEqual(len(self.datastore.get_all_tasks()), 0)
-
-    def TEST_set_task_with_tags(self):
-        self.backend.set_attached_tags(['@a'])
-        # adding a not syncable task
-        task = self.datastore.requester.new_task()
-        task.set_title("title")
-        self.backend.set_task(task)
-        self.assertEqual(len(self.tomboy.ListAllNotes()), 0)
-        # making that task  syncable
-        task.set_title("something else")
-        task.add_tag("@a")
-        self.backend.set_task(task)
-        self.assertEqual(len(self.tomboy.ListAllNotes()), 1)
-        note = self.tomboy.ListAllNotes()[0]
-        self.assertEqual(str(self.tomboy.GetNoteTitle(note)), task.get_title())
-        # re-adding that (should not change anything)
-        self.backend.set_task(task)
-        self.assertEqual(len(self.tomboy.ListAllNotes()), 1)
-        self.assertEqual(note, self.tomboy.ListAllNotes()[0])
-        # removing the syncable property and updating tomboy
-        task.remove_tag("@a")
-        self.backend.set_task(task)
-        self.assertEqual(len(self.tomboy.ListAllNotes()), 0)
-
-    def TEST_multiple_task_same_title(self):
-        self.backend.set_attached_tags(['@a'])
-        how_many_tasks = int(math.ceil(20 * random.random()))
-        for iteration in range(0, how_many_tasks):
-            task = self.datastore.requester.new_task()
-            task.set_title("title")
-            task.add_tag('@a')
-            self.backend.set_task(task)
-        self.assertEqual(len(self.tomboy.ListAllNotes()), how_many_tasks)
-
-    def _modified_string_to_datetime(self, modified_string):
-        return datetime.fromtimestamp(modified_string)
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(TestBackendTomboy)
-
-
-class FakeTomboy(dbus.service.Object):
-    """
-    D-Bus service object that mimics TOMBOY
-    """
-
-    # We don't directly use the tomboy dbus path to avoid conflicts
-    # if tomboy is running during the test
-    BUS_NAME = "Fake.Tomboy"
-    BUS_PATH = "/Fake/Tomboy"
-    BUS_INTERFACE = "Fake.Tomboy.RemoteControl"
-
-    def __init__(self):
-        # Attach the object to D-Bus
-        DBusGMainLoop(set_as_default=True)
-        self.bus = dbus.SessionBus()
-        bus_name = dbus.service.BusName(self.BUS_NAME, bus=self.bus)
-        dbus.service.Object.__init__(self, bus_name, self.BUS_PATH)
-        self.notes = {}
-        threading.Thread(target=self.fake_main_loop).start()
-
-    @dbus.service.method(BUS_INTERFACE, in_signature="s", out_signature="s")
-    def GetNoteContents(self, note):
-        return self.notes[note]['content']
-
-    @dbus.service.method(BUS_INTERFACE, in_signature="s", out_signature="b")
-    def NoteExists(self, note):
-        return note in self.notes
-
-    @dbus.service.method(BUS_INTERFACE, in_signature="s", out_signature="d")
-    def GetNoteChangeDate(self, note):
-        return self.notes[note]['changed']
-
-    @dbus.service.method(BUS_INTERFACE, in_signature="ss")
-    def SetNoteContents(self, note, text):
-        self.fake_update_note(note)
-        self.notes[note]['content'] = text
-
-    @dbus.service.method(BUS_INTERFACE, in_signature="s", out_signature="s")
-    def GetNoteTitle(self, note):
-        return self._GetNoteTitle(note)
-
-    def _GetNoteTitle(self, note):
-        content = self.notes[note]['content']
-        try:
-            end_of_title = content.index('\n')
-        except ValueError:
-            return content
-        return content[:end_of_title]
-
-    @dbus.service.method(BUS_INTERFACE, in_signature="s")
-    def DeleteNote(self, note):
-        del self.notes[note]
-
-    @dbus.service.method(BUS_INTERFACE, in_signature="s", out_signature="s")
-    def CreateNamedNote(self, title):
-        # this is to mimic the way tomboy handles title clashes
-        if self._FindNote(title) != '':
-            return ''
-        note = str(uuid.uuid4())
-        self.notes[note] = {'content': title}
-        self.fake_update_note(note)
-        return note
-
-    @dbus.service.method(BUS_INTERFACE, in_signature="s", out_signature="s")
-    def FindNote(self, title):
-        return self._FindNote(title)
-
-    def _FindNote(self, title):
-        for note in self.notes:
-            if self._GetNoteTitle(note) == title:
-                return note
-        return ''
-
-    @dbus.service.method(BUS_INTERFACE, out_signature="as")
-    def ListAllNotes(self):
-        return list(self.notes)
-
-    @dbus.service.signal(BUS_INTERFACE, signature='s')
-    def NoteSaved(self, note):
-        pass
-
-    @dbus.service.signal(BUS_INTERFACE, signature='s')
-    def NoteDeleted(self, note):
-        pass
-
-###############################################################################
-### Function with the fake_ prefix are here to assist in testing, they do not
-### need to be present in the real class
-###############################################################################
-    def fake_update_note(self, note):
-        self.notes[note]['changed'] = time.mktime(datetime.now().timetuple())
-
-    def fake_main_loop(self):
-        GObject.threads_init()
-        dbus.glib.init_threads()
-        self.main_loop = GObject.MainLoop()
-        self.main_loop.run()
-
-    @dbus.service.method(BUS_INTERFACE)
-    def Reset(self):
-        self.notes = {}
-
-    @dbus.service.method(BUS_INTERFACE)
-    def FakeQuit(self):
-        threading.Timer(0.2, self._fake_quit).start()
-
-    def _fake_quit(self):
-        self.main_loop.quit()
-        sys.exit(0)

=== removed file 'GTG/tests/test_backends.py'
--- GTG/tests/test_backends.py	2013-11-25 02:37:46 +0000
+++ GTG/tests/test_backends.py	1970-01-01 00:00:00 +0000
@@ -1,139 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 - 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/>.
-# -----------------------------------------------------------------------------
-
-""" Tests for GTG backends.
-
-Some of these tests will generate files in
-xdg.BaseDirectory.xdg_data_home/gtg directory.
-"""
-
-# Standard imports
-import unittest
-import os
-import xdg
-
-# GTG imports
-from GTG.backends import backend_localfile as localfile
-from GTG.tools import cleanxml
-from GTG.core import CoreConfig
-
-
-class GtgBackendsUniTests(unittest.TestCase):
-    """Tests for GTG backends."""
-
-    def __init__(self, test):
-        unittest.TestCase.__init__(self, test)
-        self.taskfile = ''
-        self.datafile = ''
-        self.taskpath = ''
-        self.datapath = ''
-
-    def SetUp(self):
-        CoreConfig().set_data_dir("./test_data")
-        CoreConfig().set_conf_dir("./test_data")
-
-    def test_localfile_get_name(self):
-        """Tests for localfile/get_name function :
-        - a string is expected.
-        """
-        res = localfile.Backend.get_name()
-        expectedres = "backend_localfile"
-        self.assertEqual(res, expectedres)
-
-    def test_localfile_get_description(self):
-        """Tests for localfile/get_description function :
-        - a string is expected.
-        """
-        res = localfile.Backend.get_description()
-        expectedres = "Your tasks are saved"
-        self.assertEqual(res[:len(expectedres)], expectedres)
-
-    def test_localfile_get_static_parameters(self):
-        """Tests for localfile/get_static_parameters function:
-        - a string is expected.
-        """
-        res = localfile.Backend.get_static_parameters()
-        self.assertEqual(res['path']['type'], "string")
-
-    def test_localfile_get_type(self):
-        """Tests for localfile/get_type function:
-        - a string is expected.
-        """
-        res = localfile.Backend.get_type()
-        expectedres = "readwrite"
-        self.assertEqual(res, expectedres)
-
-    def test_localfile_backend_method3(self):
-        """Tests for localfile/Backend/remove_task method:
-        - parse task file to check if task has been removed.
-        """
-        self.create_test_environment()
-        doc, configxml = cleanxml.openxmlfile(self.datapath, 'config')
-        xmlproject = doc.getElementsByTagName('backend')
-        for domobj in xmlproject:
-            dic = {}
-            if domobj.hasAttribute("module"):
-                dic["module"] = str(domobj.getAttribute("module"))
-                dic["pid"] = str(domobj.getAttribute("pid"))
-                dic["xmlobject"] = domobj
-                dic["enabled"] = True
-                dic["path"] = self.taskpath
-        beobj = localfile.Backend(dic)
-        expectedres = True
-        beobj.remove_task("0@1")
-        beobj.quit()
-        dataline = open(self.taskpath, 'r').read()
-        if "0@1" in dataline:
-            res = False
-        else:
-            res = True
-        expectedres = True
-        self.assertEqual(res, expectedres)
-
-    def create_test_environment(self):
-        """Create the test environment"""
-        self.taskfile = 'test.xml'
-        self.datafile = 'projectstest.xml'
-        tasks = [
-            '<?xml version="1.0" ?>\n',
-            '<project>\n',
-            '\t<task id="0@1" status="Active" tags="">\n',
-            '\t\t<title>\n',
-            '\t\t\tCeci est un test\n',
-            '\t\t</title>\n',
-            '\t</task>\n',
-            '</project>\n',
-        ]
-        data = [
-            '<?xml version="1.0" ?>\n',
-            '<config>\n',
-            '\t<backend filename="test.xml" module="localfile" pid="1"/>\n',
-            '</config>\n',
-        ]
-        self.testdir = os.path.join(xdg.BaseDirectory.xdg_data_home, 'gtg')
-        if not os.path.exists(self.testdir):
-            os.makedirs(self.testdir)
-        self.taskpath = os.path.join(self.testdir, self.taskfile)
-        self.datapath = os.path.join(self.testdir, self.datafile)
-        open(self.taskpath, 'w').writelines(tasks)
-        open(self.datapath, 'w').writelines(data)
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromName(__name__)

=== removed file 'GTG/tests/test_bidict.py'
--- GTG/tests/test_bidict.py	2013-11-25 02:37:46 +0000
+++ GTG/tests/test_bidict.py	1970-01-01 00:00:00 +0000
@@ -1,69 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 - 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/>.
-# -----------------------------------------------------------------------------
-
-""" Tests for the BiDict class """
-
-import unittest
-import uuid
-
-from GTG.tools.bidict import BiDict
-
-
-class TestBiDict(unittest.TestCase):
-    """ Tests for the BiDict object."""
-
-    def test_add_and_gets(self):
-        """ Test for the __init__, _get_by_first, _get_by_second function """
-        pairs = [(uuid.uuid4(), uuid.uuid4()) for a in range(10)]
-        bidict = BiDict(*pairs)
-        for pair in pairs:
-            self.assertEqual(bidict._get_by_first(pair[0]), pair[1])
-            self.assertEqual(bidict._get_by_second(pair[1]), pair[0])
-
-    def test_remove_by_first_or_second(self):
-        """ Tests for removing elements from the biDict """
-        pair_first = (1, 'one')
-        pair_second = (2, 'two')
-        bidict = BiDict(pair_first, pair_second)
-        bidict._remove_by_first(pair_first[0])
-        bidict._remove_by_second(pair_second[1])
-        missing_first = 0
-        missing_second = 0
-        try:
-            bidict._get_by_first(pair_first[0])
-        except KeyError:
-            missing_first += 1
-        try:
-            bidict._get_by_first(pair_second[0])
-        except KeyError:
-            missing_first += 1
-        try:
-            bidict._get_by_second(pair_first[1])
-        except KeyError:
-            missing_second += 1
-        try:
-            bidict._get_by_second(pair_second[1])
-        except KeyError:
-            missing_second += 1
-        self.assertEqual(missing_first, 2)
-        self.assertEqual(missing_second, 2)
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(TestBiDict)

=== removed file 'GTG/tests/test_datastore.py'
--- GTG/tests/test_datastore.py	2013-11-25 02:37:46 +0000
+++ GTG/tests/test_datastore.py	1970-01-01 00:00:00 +0000
@@ -1,346 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 - 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/>.
-# -----------------------------------------------------------------------------
-
-""" Tests for the datastore """
-
-import unittest
-import uuid
-import time
-from random import randint
-from gi.repository import GObject
-
-import GTG
-from GTG.core.datastore import DataStore
-from GTG.backends.genericbackend import GenericBackend
-from GTG.core import CoreConfig
-from liblarch import Tree
-
-
-def sleep_within_loop(duration):
-    main_loop = GObject.MainLoop()
-    GObject.timeout_add(duration * 1000, main_loop.quit)
-    # NOTE: I am not sure why, but I need add this
-    # dumb thing to run _process method of LibLarch
-    GObject.idle_add(lambda: True)
-    main_loop.run()
-
-
-class TestDatastore(unittest.TestCase):
-    """ Tests for the DataStore object.  """
-
-    def setUp(self):
-        """
-        Creates the environment for the tests
-        @returns: None
-        """
-        self.datastore = DataStore()
-        self.requester = self.datastore.get_requester()
-
-    def test_task_factory(self):
-        """ Test for the task_factory function """
-        # generate a Task with a random id
-        tid = str(uuid.uuid4())
-        task = self.datastore.task_factory(tid, newtask=True)
-        self.assertTrue(isinstance(task, GTG.core.task.Task))
-        self.assertEqual(task.get_id(), tid)
-        self.assertEqual(task.is_new(), True)
-        tid = str(uuid.uuid4())
-        task = self.datastore.task_factory(tid, newtask=False)
-        self.assertEqual(task.is_new(), False)
-
-    def test_new_task_and_has_task(self):
-        """ Tests the new_task function """
-        task = self.datastore.new_task()
-        tid = task.get_id()
-        self.assertTrue(isinstance(tid, str))
-        self.assertTrue(tid != '')
-        self.assertTrue(task.is_new())
-        self.assertTrue(self.datastore.has_task(tid))
-        self.assertTrue(len(self.datastore.get_all_tasks()) == 1)
-
-    def test_get_all_tasks(self):
-        """ Tests the get_all_tasks function """
-        task_ids = []
-        for i in range(1, 10):
-            task = self.datastore.new_task()
-            task_ids.append(task.get_id())
-            return_list = self.datastore.get_all_tasks()
-            self.assertEqual(len(return_list), i)
-            task_ids.sort()
-            return_list.sort()
-            self.assertEqual(task_ids, return_list)
-
-    def test_get_task(self):
-        '''
-        Tests the get_task function
-        '''
-        task = self.datastore.new_task()
-        self.assertTrue(isinstance(self.datastore.get_task(task.get_id()),
-                                   GTG.core.task.Task))
-        self.assertEqual(self.datastore.get_task(task.get_id()), task)
-
-    def test_get_requester(self):
-        '''
-        Tests the get_requester function
-        '''
-        requester = self.datastore.get_requester()
-        self.assertTrue(isinstance(requester, GTG.core.requester.Requester))
-
-    def test_get_tasks_tree(self):
-        '''
-        Tests the get_tasks_tree function
-        '''
-        tasks_tree = self.datastore.get_tasks_tree()
-        self.assertTrue(isinstance(tasks_tree, Tree))
-
-    def test_push_task(self):
-        '''
-        Tests the push_task function
-        '''
-        task_ids = []
-        for i in range(1, 10):
-            tid = str(uuid.uuid4())
-            if tid not in task_ids:
-                task_ids.append(tid)
-            task = self.datastore.task_factory(tid)
-            return_value1 = self.datastore.push_task(task)
-            self.assertTrue(return_value1)
-            # we do it twice, but it should be pushed only once if it's
-            # working correctly (the second should be discarded)
-            return_value2 = self.datastore.push_task(task)
-            self.assertFalse(return_value2)
-            stored_tasks = self.datastore.get_all_tasks()
-            task_ids.sort()
-            stored_tasks.sort()
-            self.assertEqual(task_ids, stored_tasks)
-
-    def test_register_backend(self):
-        '''
-        Tests the register_backend function. It also tests the
-        get_all_backends and get_backend function as a side effect
-        '''
-        # create a simple backend dictionary
-        backend = FakeBackend(enabled=True)
-        tasks_in_backend_count = randint(1, 20)
-        for temp in range(0, tasks_in_backend_count):
-            backend.fake_add_random_task()
-        backend_dic = {'backend': backend, 'pid': 'a'}
-        self.datastore.register_backend(backend_dic)
-        all_backends = self.datastore.get_all_backends(disabled=True)
-        self.assertEqual(len(all_backends), 1)
-        registered_backend = self.datastore.get_backend(backend.get_id())
-        self.assertEqual(backend.get_id(), registered_backend.get_id())
-        self.assertTrue(isinstance(registered_backend,
-                                   GTG.core.datastore.TaskSource))
-        self.assertTrue(registered_backend.is_enabled())
-        self.assertEqual(registered_backend.fake_get_initialized_count(), 1)
-        # we give some time for the backend to push all its tasks
-        sleep_within_loop(1)
-        self.assertEqual(len(self.datastore.get_all_tasks()),
-                         tasks_in_backend_count)
-
-        # same test, disabled backend
-        backend = FakeBackend(enabled=False)
-        for temp in range(1, randint(2, 20)):
-            backend.fake_add_random_task()
-        backend_dic = {'backend': backend, 'pid': 'b'}
-        self.datastore.register_backend(backend_dic)
-        all_backends = self.datastore.get_all_backends(disabled=True)
-        self.assertEqual(len(all_backends), 2)
-        all_backends = self.datastore.get_all_backends(disabled=False)
-        self.assertEqual(len(all_backends), 1)
-        registered_backend = self.datastore.get_backend(backend.get_id())
-        self.assertEqual(backend.get_id(), registered_backend.get_id())
-        self.assertTrue(isinstance(registered_backend,
-                                   GTG.core.datastore.TaskSource))
-        self.assertFalse(registered_backend.is_enabled())
-        self.assertEqual(registered_backend.fake_get_initialized_count(), 0)
-        # we give some time for the backend to push all its tasks (is
-        # shouldn't, since it's disabled, but we give time anyway
-        time.sleep(1)
-        self.assertEqual(len(self.datastore.get_all_tasks()),
-                         tasks_in_backend_count)
-
-    def test_set_backend_enabled(self):
-        '''
-        Tests the set_backend_enabled function
-        '''
-        enabled_backend = FakeBackend(enabled=True)
-        disabled_backend = FakeBackend(enabled=False)
-        self.datastore.register_backend({'backend': enabled_backend,
-                                         'pid': str(uuid.uuid4()),
-                                         GenericBackend.KEY_DEFAULT_BACKEND:
-                                         False})
-        self.datastore.register_backend({'backend': disabled_backend,
-                                         'pid': str(uuid.uuid4()),
-                                         GenericBackend.KEY_DEFAULT_BACKEND:
-                                         False})
-        # enabling an enabled backend
-        self.datastore.set_backend_enabled(enabled_backend.get_id(), True)
-        self.assertEqual(enabled_backend.fake_get_initialized_count(), 1)
-        self.assertTrue(enabled_backend.is_enabled())
-        # disabling a disabled backend
-        self.datastore.set_backend_enabled(disabled_backend.get_id(), False)
-        self.assertEqual(disabled_backend.fake_get_initialized_count(), 0)
-        self.assertFalse(disabled_backend.is_enabled())
-        # disabling an enabled backend
-        self.datastore.set_backend_enabled(enabled_backend.get_id(), False)
-        self.assertEqual(enabled_backend.fake_get_initialized_count(), 1)
-        countdown = 10
-        while countdown >= 0 and enabled_backend.is_enabled():
-            time.sleep(0.1)
-        self.assertFalse(enabled_backend.is_enabled())
-#        #enabling a disabled backend
-#        self.datastore.set_backend_enabled(disabled_backend.get_id(), True)
-#        self.assertEqual(disabled_backend.fake_get_initialized_count(), 1)
-#        self.assertTrue(disabled_backend.is_enabled())
-
-    def test_remove_backend(self):
-        """ Tests the remove_backend function """
-        enabled_backend = FakeBackend(enabled=True)
-        disabled_backend = FakeBackend(enabled=False)
-        self.datastore.register_backend({'backend': enabled_backend,
-                                         'pid': str(uuid.uuid4()),
-                                         GenericBackend.KEY_DEFAULT_BACKEND:
-                                         False})
-        self.datastore.register_backend({'backend': disabled_backend,
-                                         'pid': str(uuid.uuid4()),
-                                         GenericBackend.KEY_DEFAULT_BACKEND:
-                                         False})
-        # removing an enabled backend
-        self.datastore.remove_backend(enabled_backend.get_id())
-        # waiting
-        countdown = 10
-        while countdown >= 0 and enabled_backend.is_enabled():
-            time.sleep(0.1)
-        self.assertFalse(enabled_backend.is_enabled())
-        self.assertEqual(
-            len(self.datastore.get_all_backends(disabled=True)), 1)
-        # removing a disabled backend
-        self.datastore.remove_backend(disabled_backend.get_id())
-        self.assertFalse(disabled_backend.is_enabled())
-        self.assertEqual(
-            len(self.datastore.get_all_backends(disabled=True)), 0)
-
-    def test_flush_all_tasks(self):
-        '''
-        Tests the flush_all_tasks function
-        '''
-        # we add some tasks in the datastore
-        tasks_in_datastore_count = 10  # randint(1, 20)
-        for temp in range(0, tasks_in_datastore_count):
-            self.datastore.new_task()
-        datastore_stored_tids = self.datastore.get_all_tasks()
-        self.assertEqual(tasks_in_datastore_count, len(datastore_stored_tids))
-
-        # we enable a backend
-        backend = FakeBackend(enabled=True)
-        self.datastore.register_backend({'backend': backend, 'pid': 'a'})
-        # we wait for the signal storm to wear off
-        sleep_within_loop(2)
-        # we sync
-        self.datastore.get_backend(backend.get_id()).sync()
-        # and we inject task in the backend
-        tasks_in_backend_count = 5  # randint(1, 20)
-        for temp in range(0, tasks_in_backend_count):
-            backend.fake_add_random_task()
-        backend_stored_tids = backend.fake_get_task_ids()
-        self.assertEqual(tasks_in_backend_count, len(backend_stored_tids))
-        self.datastore.flush_all_tasks(backend.get_id())
-        # we wait for the signal storm to wear off
-        sleep_within_loop(2)
-        # we sync
-        self.datastore.get_backend(backend.get_id()).sync()
-        all_tasks_count = tasks_in_backend_count + tasks_in_datastore_count
-        new_datastore_stored_tids = self.datastore.get_all_tasks()
-        new_backend_stored_tids = backend.fake_get_task_ids()
-        self.assertEqual(len(new_backend_stored_tids), all_tasks_count)
-        self.assertEqual(len(new_datastore_stored_tids), all_tasks_count)
-        new_datastore_stored_tids.sort()
-        new_backend_stored_tids.sort()
-        self.assertEqual(new_backend_stored_tids, new_datastore_stored_tids)
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(TestDatastore)
-
-
-class FakeBackend(unittest.TestCase):
-    '''
-    Mimics the behavior of a simple backend. Just used for testing
-    '''
-
-    def __init__(self, enabled=True):
-        self.enabled = enabled
-        self.initialized_count = 0
-        self.tasks_ids = []
-        self.backend_id = str(uuid.uuid4())
-        self.purged = False
-
-    def is_enabled(self):
-        return self.enabled
-
-    def initialize(self):
-        self.initialized_count += 1
-        self.enabled = True
-
-    def queue_set_task(self, task):
-        if task.get_id() not in self.tasks_ids:
-            self.tasks_ids.append(task.get_id())
-
-    def has_task(self, task_id):
-        return task_id in self.tasks_ids
-
-    def queue_remove_task(self, task_id):
-        self.tasks_ids.remove(task_id)
-
-    def get_id(self):
-        return self.backend_id
-
-    def start_get_tasks(self):
-        for task_id in self.tasks_ids:
-            self.datastore.push_task(self.datastore.task_factory(task_id))
-
-    def quit(self, disabled=False):
-        self.enabled = not disabled
-
-    def is_default(self):
-        return True
-
-    def set_parameter(self, param_name, param_value):
-        pass
-
-    def get_attached_tags(self):
-        return [CoreConfig.ALLTASKS_TAG]
-
-    def register_datastore(self, datastore):
-        self.datastore = datastore
-
-    ##########################################################################
-    # The following are used just for testing, they're not present inside a
-    # normal backend
-    ##########################################################################
-    def fake_get_initialized_count(self):
-        return self.initialized_count
-
-    def fake_get_task_ids(self):
-        return self.tasks_ids
-
-    def fake_add_random_task(self):
-        self.tasks_ids.append(str(uuid.uuid4()))

=== removed file 'GTG/tests/test_signal_testing.py'
--- GTG/tests/test_signal_testing.py	2013-11-25 02:37:46 +0000
+++ GTG/tests/test_signal_testing.py	1970-01-01 00:00:00 +0000
@@ -1,60 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 - 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/>.
-# -----------------------------------------------------------------------------
-
-from gi.repository import GObject
-import unittest
-import uuid
-
-from GTG.tests.signals_testing import SignalCatcher, GobjectSignalsManager
-
-
-class TestSignalTesting(unittest.TestCase):
-
-    def setUp(self):
-        self.gobject_signal_manager = GobjectSignalsManager()
-        self.gobject_signal_manager.init_signals()
-
-    def tearDown(self):
-        self.gobject_signal_manager.terminate_signals()
-
-    def test_signal_catching(self):
-        generator = FakeGobject()
-        arg = str(uuid.uuid4())
-        with SignalCatcher(self, generator, 'one') \
-                as [signal_catched_event, signal_arguments]:
-            generator.emit_signal('one', arg)
-            signal_catched_event.wait()
-        self.assertEqual(len(signal_arguments), 1)
-        self.assertEqual(len(signal_arguments[0]), 1)
-        one_signal_arguments = signal_arguments[0]
-        self.assertEqual(arg, one_signal_arguments[0])
-
-
-class FakeGobject(GObject.GObject):
-    __gsignals__ = {'one': (GObject.SignalFlags.RUN_FIRST,
-                            None, (str, )),
-                    'two': (GObject.SignalFlags.RUN_FIRST,
-                            None, (str, ))}
-
-    def emit_signal(self, signal_name, argument):
-        GObject.idle_add(self.emit, signal_name, argument)
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(TestSignalTesting)

=== removed file 'GTG/tests/test_syncengine.py'
--- GTG/tests/test_syncengine.py	2013-11-23 14:40:23 +0000
+++ GTG/tests/test_syncengine.py	1970-01-01 00:00:00 +0000
@@ -1,206 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 - 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/>.
-# -----------------------------------------------------------------------------
-
-""" Tests for the SyncEngine class """
-
-import unittest
-import uuid
-
-from GTG.backends.syncengine import SyncEngine
-
-
-class TestSyncEngine(unittest.TestCase):
-    """ Tests for the SyncEngine object. """
-
-    def setUp(self):
-        self.ftp_local = FakeTaskProvider()
-        self.ftp_remote = FakeTaskProvider()
-        self.sync_engine = SyncEngine()
-
-    def test_analyze_element_and_record_and_break_relationship(self):
-        """ Test for the _analyze_element, analyze_remote_id, analyze_local_id,
-        record_relationship, break_relationship """
-        # adding a new local task
-        has_local_task = self.ftp_local.has_task
-        has_remote_task = self.ftp_remote.has_task
-        local_id = uuid.uuid4()
-        self.ftp_local.fake_add_task(local_id)
-        self.assertEqual(self.sync_engine.analyze_local_id(local_id,
-                                                           has_local_task,
-                                                           has_remote_task),
-                         (SyncEngine.ADD, None))
-        # creating the related remote task
-        remote_id = uuid.uuid4()
-        self.ftp_remote.fake_add_task(remote_id)
-        # informing the sync_engine about that
-        self.sync_engine.record_relationship(local_id, remote_id, object())
-        # verifying that it understood that
-        self.assertEqual(self.sync_engine.analyze_local_id(local_id,
-                                                           has_local_task,
-                                                           has_remote_task),
-                         (SyncEngine.UPDATE, remote_id))
-        self.assertEqual(self.sync_engine.analyze_remote_id(remote_id,
-                                                            has_local_task,
-                                                            has_remote_task),
-                         (SyncEngine.UPDATE, local_id))
-        # and not the reverse
-        self.assertEqual(self.sync_engine.analyze_remote_id(local_id,
-                                                            has_local_task,
-                                                            has_remote_task),
-                         (SyncEngine.ADD, None))
-        self.assertEqual(self.sync_engine.analyze_local_id(remote_id,
-                                                           has_local_task,
-                                                           has_remote_task),
-                         (SyncEngine.ADD, None))
-        # now we remove the remote task
-        self.ftp_remote.fake_remove_task(remote_id)
-        self.assertEqual(self.sync_engine.analyze_local_id(local_id,
-                                                           has_local_task,
-                                                           has_remote_task),
-                         (SyncEngine.REMOVE, None))
-        self.sync_engine.break_relationship(local_id=local_id)
-        self.assertEqual(self.sync_engine.analyze_local_id(local_id,
-                                                           has_local_task,
-                                                           has_remote_task),
-                         (SyncEngine.ADD, None))
-        self.assertEqual(self.sync_engine.analyze_remote_id(remote_id,
-                                                            has_local_task,
-                                                            has_remote_task),
-                         (SyncEngine.ADD, None))
-        # we add them back and remove giving the remote id as key to find
-        # what to delete
-        self.ftp_local.fake_add_task(local_id)
-        self.ftp_remote.fake_add_task(remote_id)
-        self.ftp_remote.fake_remove_task(remote_id)
-        self.sync_engine.record_relationship(local_id, remote_id, object)
-        self.sync_engine.break_relationship(remote_id=remote_id)
-        self.assertEqual(self.sync_engine.analyze_local_id(local_id,
-                                                           has_local_task,
-                                                           has_remote_task),
-                         (SyncEngine.ADD, None))
-        self.assertEqual(self.sync_engine.analyze_remote_id(remote_id,
-                                                            has_local_task,
-                                                            has_remote_task),
-                         (SyncEngine.ADD, None))
-
-    def test_syncability(self):
-        """ Test for the _analyze_element, analyze_remote_id, analyze_local_id.
-        Checks that the is_syncable parameter is used correctly """
-        # adding a new local task unsyncable
-        has_local_task = self.ftp_local.has_task
-        has_remote_task = self.ftp_remote.has_task
-        local_id = uuid.uuid4()
-        self.ftp_local.fake_add_task(local_id)
-        self.assertEqual(self.sync_engine.analyze_local_id(local_id,
-                                                           has_local_task,
-                                                           has_remote_task,
-                                                           False),
-                         (None, None))
-        # adding a new local task, syncable
-        local_id = uuid.uuid4()
-        self.ftp_local.fake_add_task(local_id)
-        self.assertEqual(self.sync_engine.analyze_local_id(local_id,
-                                                           has_local_task,
-                                                           has_remote_task),
-                         (SyncEngine.ADD, None))
-        # creating the related remote task
-        remote_id = uuid.uuid4()
-        self.ftp_remote.fake_add_task(remote_id)
-        # informing the sync_engine about that
-        self.sync_engine.record_relationship(local_id, remote_id, object())
-        # checking that it behaves correctly with established relationships
-        self.assertEqual(self.sync_engine.analyze_local_id(local_id,
-                                                           has_local_task,
-                                                           has_remote_task,
-                                                           True),
-                         (SyncEngine.UPDATE, remote_id))
-        self.assertEqual(self.sync_engine.analyze_local_id(local_id,
-                                                           has_local_task,
-                                                           has_remote_task,
-                                                           False),
-                         (SyncEngine.LOST_SYNCABILITY, remote_id))
-        self.assertEqual(self.sync_engine.analyze_remote_id(remote_id,
-                                                            has_local_task,
-                                                            has_remote_task,
-                                                            True),
-                         (SyncEngine.UPDATE, local_id))
-        self.assertEqual(self.sync_engine.analyze_remote_id(remote_id,
-                                                            has_local_task,
-                                                            has_remote_task,
-                                                            False),
-                         (SyncEngine.LOST_SYNCABILITY, local_id))
-        # now we remove the remote task
-        self.ftp_remote.fake_remove_task(remote_id)
-        self.assertEqual(self.sync_engine.analyze_local_id(local_id,
-                                                           has_local_task,
-                                                           has_remote_task,
-                                                           True),
-                         (SyncEngine.REMOVE, None))
-        self.assertEqual(self.sync_engine.analyze_local_id(local_id,
-                                                           has_local_task,
-                                                           has_remote_task,
-                                                           False),
-                         (SyncEngine.REMOVE, None))
-        self.sync_engine.break_relationship(local_id=local_id)
-        self.assertEqual(self.sync_engine.analyze_local_id(local_id,
-                                                           has_local_task,
-                                                           has_remote_task,
-                                                           True),
-                         (SyncEngine.ADD, None))
-        self.assertEqual(self.sync_engine.analyze_local_id(local_id,
-                                                           has_local_task,
-                                                           has_remote_task,
-                                                           False),
-                         (None, None))
-        self.assertEqual(self.sync_engine.analyze_remote_id(remote_id,
-                                                            has_local_task,
-                                                            has_remote_task,
-                                                            True),
-                         (SyncEngine.ADD, None))
-        self.assertEqual(self.sync_engine.analyze_remote_id(remote_id,
-                                                            has_local_task,
-                                                            has_remote_task,
-                                                            False),
-                         (None, None))
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(TestSyncEngine)
-
-
-class FakeTaskProvider(object):
-
-    def __init__(self):
-        self.dic = {}
-
-    def has_task(self, tid):
-        return tid in self.dic
-
-##############################################################################
-### Function with the fake_ prefix are here to assist in testing, they do not
-### need to be present in the real class
-##############################################################################
-    def fake_add_task(self, tid):
-        self.dic[tid] = "something"
-
-    def fake_get_task(self, tid):
-        return self.dic[tid]
-
-    def fake_remove_task(self, tid):
-        del self.dic[tid]

=== removed file 'GTG/tests/test_syncmeme.py'
--- GTG/tests/test_syncmeme.py	2013-11-23 14:40:23 +0000
+++ GTG/tests/test_syncmeme.py	1970-01-01 00:00:00 +0000
@@ -1,55 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 - 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/>.
-# -----------------------------------------------------------------------------
-
-""" Tests for the SyncMeme class """
-
-import unittest
-import datetime
-
-from GTG.backends.syncengine import SyncMeme
-
-
-class TestSyncMeme(unittest.TestCase):
-    """ Tests for the SyncEngine object. """
-
-    def test_which_is_newest(self):
-        """ test the which_is_newest function """
-        meme = SyncMeme()
-        # tasks have not changed
-        local_modified = datetime.datetime.now()
-        remote_modified = datetime.datetime.now()
-        meme.set_local_last_modified(local_modified)
-        meme.set_remote_last_modified(remote_modified)
-        self.assertEqual(
-            meme.which_is_newest(local_modified, remote_modified),
-            None)
-        # we update the local
-        local_modified = datetime.datetime.now()
-        self.assertEqual(
-            meme.which_is_newest(local_modified, remote_modified),
-            'local')
-        # we update the remote
-        remote_modified = datetime.datetime.now()
-        self.assertEqual(
-            meme.which_is_newest(local_modified, remote_modified),
-            'remote')
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(TestSyncMeme)

=== removed file 'GTG/tests/test_tool_tags.py'
--- GTG/tests/test_tool_tags.py	2013-11-23 14:40:23 +0000
+++ GTG/tests/test_tool_tags.py	1970-01-01 00:00:00 +0000
@@ -1,82 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 - 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/>.
-# -----------------------------------------------------------------------------
-""" Tests for the tags utilities """
-
-import unittest
-
-from GTG.tools.tags import extract_tags_from_text
-from GTG.tools.tags import parse_tag_list
-
-
-class TestToolTags(unittest.TestCase):
-    """ Tests for the tags utilities """
-
-    def test_extract_tags_from_text(self):
-        """ Test for extracting tags from a string """
-        tests = (
-            ("@mamma mia", ["@mamma"]),
-            ("vive le @roy", ["@roy"]),
-            ("hey @mr. jack!", ["@mr"]),
-            ("no @emails allowed: invernizzi.l@xxxxxxxxx", ["@emails"]),
-            ("and no @@diff stuff", []),
-            ("@we @do @love @tags!", ["@we", "@do", "@love", "@tags"]),
-        )
-        for text, tags in tests:
-            self.assertEqual(extract_tags_from_text(text), tags)
-
-    def test_parse_tag_list(self):
-        """ Test parsing tag list"""
-        ptl = parse_tag_list
-
-        self.assertEqual(ptl("tag"), [("@tag", True)])
-        self.assertEqual(ptl("@tag"), [("@tag", True)])
-
-        self.assertEqual(ptl("!tag"), [("@tag", False)])
-        self.assertEqual(ptl("!@tag"), [("@tag", False)])
-
-        self.assertEqual(ptl("a b c"),
-                         [("@a", True), ("@b", True), ("@c", True)])
-        self.assertEqual(ptl("a @b c"),
-                         [("@a", True), ("@b", True), ("@c", True)])
-        self.assertEqual(ptl("@a b @c"),
-                         [("@a", True), ("@b", True), ("@c", True)])
-        self.assertEqual(ptl("@a @b @c"),
-                         [("@a", True), ("@b", True), ("@c", True)])
-
-        self.assertEqual(ptl("!a !b !c"),
-                         [("@a", False), ("@b", False), ("@c", False)])
-        self.assertEqual(ptl("!a !@b !c"),
-                         [("@a", False), ("@b", False), ("@c", False)])
-        self.assertEqual(ptl("!@a !b !@c"),
-                         [("@a", False), ("@b", False), ("@c", False)])
-        self.assertEqual(ptl("!@a !@b !@c"),
-                         [("@a", False), ("@b", False), ("@c", False)])
-
-        self.assertEqual(ptl("add !remove"),
-                         [("@add", True), ("@remove", False)])
-        self.assertEqual(ptl("@add !@remove"),
-                         [("@add", True), ("@remove", False)])
-        self.assertEqual(ptl("!remove add"),
-                         [("@remove", False), ("@add", True)])
-        self.assertEqual(ptl("!@remove @add"),
-                         [("@remove", False), ("@add", True)])
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(TestToolTags)

=== removed file 'GTG/tests/test_tools_tags.py'
--- GTG/tests/test_tools_tags.py	2013-11-23 14:40:23 +0000
+++ GTG/tests/test_tools_tags.py	1970-01-01 00:00:00 +0000
@@ -1,70 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 - 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/>.
-# -----------------------------------------------------------------------------
-
-""" Tests for extract_tags_from_text """
-
-import unittest
-from GTG.tools.tags import extract_tags_from_text as tags
-
-
-class TestExtractTags(unittest.TestCase):
-
-    def test_empty(self):
-        self.assertEqual(tags(""), [])
-
-    def test_tag_at_beginning(self):
-        self.assertEqual(tags("@tag some other text"), ["@tag"])
-
-    def test_tag_at_end(self):
-        self.assertEqual(tags("some text ended with @endtag"), ["@endtag"])
-
-    def test_hypen_in_tag(self):
-        self.assertEqual(
-            tags("@tag, @my-tag, bla bla @do-this-today,\
-                 it has @con--tinuous---hypen-s-"),
-            ["@tag", "@my-tag", "@do-this-today", "@con--tinuous---hypen-s"])
-
-        self.assertEqual(tags("@hypen-at-end- some other text"),
-                         ["@hypen-at-end"])
-        self.assertEqual(tags("@hypen-at-end-, with comma"), ["@hypen-at-end"]
-                         )
-
-    def test_dot(self):
-        self.assertEqual(tags("text @gtg-0.3"), ["@gtg-0.3"])
-        self.assertEqual(
-            tags("@tag., @my.tag, bla bla @do.this.today,\
-                 also contains @hy-pen-.s"),
-            ["@tag", "@my.tag", "@do.this.today", "@hy-pen-.s"])
-
-    def test_slash(self):
-        self.assertEqual(
-            tags("@tag/, @my/tag, bla bla @do/this/today/,\
-                 @hy-p-/ens with @slash/es/"),
-            ["@tag", "@my/tag", "@do/this/today", "@hy-p-/ens", "@slash/es"])
-
-    def test_colon(self):
-        self.assertEqual(
-            tags("@tag:, @my:tag, bla bla @do:this:today:, @co:l-on/s-,\
-                 @:dot/s:, with @com,mas"),
-            ["@tag", "@my:tag", "@do:this:today", "@co:l-on/s", "@:dot/s",
-             "@com"])
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromName(__name__)

=== removed file 'GTG/tests/test_twokeydict.py'
--- GTG/tests/test_twokeydict.py	2013-11-25 02:37:46 +0000
+++ GTG/tests/test_twokeydict.py	1970-01-01 00:00:00 +0000
@@ -1,98 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 - 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/>.
-# -----------------------------------------------------------------------------
-
-""" Tests for the TwoKeyDict class """
-
-import unittest
-import uuid
-
-from GTG.tools.twokeydict import TwoKeyDict
-
-
-class TestTwoKeyDict(unittest.TestCase):
-    """ Tests for the TwoKeyDict object. """
-
-    def test_add_and_gets(self):
-        """ Test for the __init__, _get_by_first, _get_by_second function """
-        triplets = [(uuid.uuid4(), uuid.uuid4(), uuid.uuid4())
-                    for a in range(10)]
-        tw_dict = TwoKeyDict(*triplets)
-        for triplet in triplets:
-            self.assertEqual(tw_dict._get_by_primary(triplet[0]), triplet[2])
-            self.assertEqual(tw_dict._get_by_secondary(triplet[1]), triplet[2])
-
-    def test_remove_by_first_or_second(self):
-        """ Test for removing triplets form the TwoKeyDict """
-        triplet_first = (1, 'I', 'one')
-        triplet_second = (2, 'II', 'two')
-        tw_dict = TwoKeyDict(triplet_first, triplet_second)
-        tw_dict._remove_by_primary(triplet_first[0])
-        tw_dict._remove_by_secondary(triplet_second[1])
-        missing_first = 0
-        missing_second = 0
-        try:
-            tw_dict._get_by_primary(triplet_first[0])
-        except KeyError:
-            missing_first += 1
-        try:
-            tw_dict._get_by_secondary(triplet_second[0])
-        except KeyError:
-            missing_first += 1
-        try:
-            tw_dict._get_by_secondary(triplet_first[1])
-        except KeyError:
-            missing_second += 1
-        try:
-            tw_dict._get_by_secondary(triplet_second[1])
-        except KeyError:
-            missing_second += 1
-        self.assertEqual(missing_first, 2)
-        self.assertEqual(missing_second, 2)
-        # check for memory leaks
-        dict_len = 0
-        for key in tw_dict._primary_to_value.keys():
-            dict_len += 1
-        self.assertEqual(dict_len, 0)
-
-    def test_get_primary_and_secondary_key(self):
-        """ Test for fetching the objects stored in the TwoKeyDict """
-        triplets = [(uuid.uuid4(), uuid.uuid4(), uuid.uuid4())
-                    for a in range(10)]
-        tw_dict = TwoKeyDict(*triplets)
-        for triplet in triplets:
-            self.assertEqual(tw_dict._get_secondary_key(triplet[0]),
-                             triplet[1])
-            self.assertEqual(tw_dict._get_primary_key(triplet[1]),
-                             triplet[0])
-
-    def test_missing_and_then_add(self):
-        # Primary
-        local_id = '3ea957cb-417f-4944-be6c-02a1b0a84bd2'
-        # Secondary
-        remote_id = 'https://api.launchpad.net/1.0/bugs/345808'
-        value = "Hello world"
-
-        tw_dict = TwoKeyDict()
-        self.assertRaises(KeyError, tw_dict._get_secondary_key, remote_id)
-        tw_dict.add((local_id, remote_id, value))
-        self.assertEqual(remote_id, tw_dict._get_secondary_key(local_id))
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(TestTwoKeyDict)

=== removed file 'GTG/tests/tree_testing.py'
--- GTG/tests/tree_testing.py	2013-11-25 02:37:46 +0000
+++ GTG/tests/tree_testing.py	1970-01-01 00:00:00 +0000
@@ -1,230 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 - 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/>.
-# -----------------------------------------------------------------------------
-
-# If True, the TreeTester will automatically reorder node on the same level
-# as a deleted node. If False, it means that Liblarch has the responsability
-# to handle that itself.
-REORDER_ON_DELETE = False
-
-
-class TreeTester:
-    """ A class that will check if a tree implementation is consistent
-    by connecting to emitted signals and crashing on any problem """
-
-    def __init__(self, viewtree):
-        self.tree = viewtree
-        # both dict should always be synchronized
-        # They are the internal representation of the tree,
-        # based only on received signals
-        self.nodes = {}
-        self.paths = {}
-        self.tree.register_cllbck('node-added-inview', self.add)
-        self.tree.register_cllbck('node-deleted-inview', self.delete)
-        self.tree.register_cllbck('node-modified-inview', self.update)
-        self.tree.register_cllbck('node-children-reordered', self.reordered)
-        self.trace = "* * * * * * * *\n"
-
-    def add(self, nid, path):
-        self.trace += "adding %s to path %s\n" % (nid, str(path))
-        currentnode = self.paths.get(path, None)
-        if currentnode and currentnode != nid:
-            raise Exception('path %s is already occupied by %s' % (
-                str(path), nid))
-        if nid in self.nodes:
-            node = self.nodes[nid]
-        else:
-            node = []
-            self.nodes[nid] = node
-        if path not in node:
-            node.append(path)
-        self.paths[path] = nid
-
-    def delete(self, nid, path):
-        self.trace += "removing %s from path %s\n" % (nid, str(path))
-        if nid != self.paths.get(path, None):
-            error = '%s is not assigned to path %s\n' % (nid, str(path))
-            error += self.print_tree()
-            raise Exception(error)
-        if path not in self.nodes.get(nid, []):
-            raise Exception('%s is not a path of node %s' % (str(path), nid))
-        if REORDER_ON_DELETE:
-            index = path[-1:]
-            print("reorder on delete not yet implemented")
-        self.nodes[nid].remove(path)
-        if len(self.nodes[nid]) == 0:
-            self.nodes.pop(nid)
-
-        self.paths.pop(path)
-
-        # Move other paths lower like in real TreeModel
-        path_prefix = path[:-1]
-        index = path[-1]
-
-        assert path_prefix + (index, ) == path, "%s vs %s" % (
-            path_prefix + (index, ), path)
-
-        def check_prefix(path):
-            """ Is this path affected by the change?
-            Conditions:
-              * the same prefix
-                (3, 1, 2, 3) vs (3,1,2,4) OK
-                (3, 1, 2, 3) vs (3,1,2,4,0) OK
-                (3, 1, 2, 3) vs (3,2,2,4) FALSE
-              * higher index
-                (3, 1, 2, 3) vs (3,1,2,2) FALSE
-            """
-            if len(path) <= len(path_prefix):
-                return False
-
-            for i, pos in enumerate(path_prefix):
-                if path[i] != pos:
-                    return False
-
-            return path[len(path_prefix)] > index
-
-        paths = list(self.paths.keys())
-        paths.sort()
-
-        for path in paths:
-            old_path = path
-            if check_prefix(path) and len(path_prefix) > 1:
-                new_path = list(path)
-                print("new_path: %s" % str(new_path))
-                index = len(path_prefix)
-                new_path[index] = str(int(new_path[index]) - 1)
-                new_path = tuple(new_path)
-
-                print("new_path: %s" % str(new_path))
-                print("self.paths: %s" % str(self.paths))
-
-                assert new_path not in self.paths
-
-                nid = self.paths[old_path]
-                self.nodes[nid].remove(old_path)
-                del self.paths[old_path]
-                self.nodes[nid].append(new_path)
-                self.paths[new_path] = nid
-
-    def update(self, nid, path):
-##        self.tree.flush()
-#        self.trace += "updating %s in path %s\n" %(nid, str(path))
-#        error = "updating node %s for path %s\n" %(nid, str(path))
-#        if not self.nodes.has_key(nid):
-#            error += "%s is not in nodes !\n" %nid
-#            error += self.print_tree()
-#            raise Exception(error)
-#        #Nothing to do, we just update.
-#        for p in self.nodes[nid]:
-#            if self.paths[p] != nid:
-#                raise Exception('Mismatching path for %s'%nid)
-#        if not self.paths.has_key(path):
-#            error += '%s is not in stored paths (node %s)\n'%(str(path),nid)
-#            error += self.print_tree()
-#            raise Exception(error)
-#        n = self.paths[path]
-#        if path not in self.nodes[n] or n != nid:
-#            raise Exception('Mismatching node for path %s'%str(p))
-
-        # Because of the asynchronousness of update, this test
-        # doesn't work anymore
-        pass
-
-    def reordered(self, nid, path, neworder):
-        print("reordering")
-        self.trace += "reordering children of %s (%s) : %s\n" % (nid,
-                                                                 str(path),
-                                                                 neworder)
-        self.trace += "VR is %s\n" % self.tree.node_all_children()
-        if not path:
-            path = ()
-        i = 0
-        newpaths = {}
-        toremove = []
-        # we first update self.nodes with the new paths
-        while i < len(neworder):
-            if i != neworder[i]:
-                old = neworder[i]
-                oldp = path + (old, )
-                newp = path + (i, )
-                le = len(newp)
-                for pp in list(self.paths.keys()):
-                    if pp[0:le] == oldp:
-                        n = self.paths[pp]
-                        self.nodes[n].remove(pp)
-                        newpp = newp + pp[le:]
-                        self.nodes[n].append(newpp)
-                        self.trace += "    change %s path from %s to %s\n" % (
-                            n, pp, newpp)
-                        newpaths[newpp] = n
-                        toremove.append(pp)
-            i += 1
-        # now we can update self.paths
-        for p in toremove:
-            self.paths.pop(p)
-        for p in newpaths:
-            self.trace += "    adding %s to paths %s\n" % (newpaths[p], str(p))
-            self.paths[p] = newpaths[p]
-
-    def test_validity(self):
-        for n in list(self.nodes.keys()):
-            paths = self.tree.get_paths_for_node(n)
-            if len(self.nodes[n]) == 0:
-                raise Exception('Node %s is stored without any path' % n)
-            for p in self.nodes[n]:
-                if self.paths[p] != n:
-                    raise Exception('Mismatching path for %s' % n)
-                if p not in paths:
-                    error = 'we have a unknown stored path for %s\n' % n
-                    nn = self.tree.get_node_for_path(p)
-                    parent = self.tree.get_node_for_path(p[:-1])
-                    error += '  path %s is the path of %s\n' % (
-                        str(p), str(nn))
-                    error += '  parent is %s' % parent
-#                    error += self.trace
-                    raise Exception(error)
-                paths.remove(p)
-            if len(paths) > 0:
-                raise Exception('why is this path existing for %s' % n)
-        for p in list(self.paths.keys()):
-            node = self.tree.get_node_for_path(p)
-            n = self.paths[p]
-            if n != node:
-                error = 'Node for path is %s but should be %s' % (node, n)
-                raise Exception(error)
-            if p not in self.nodes[n]:
-                error = 'Mismatching node for path %s\n' % str(p)
-                error += self.print_tree()
-                raise Exception(error)
-            if len(p) == 1 and len(self.nodes[n]) > 1:
-                error = 'Node %s has multiple paths and is in the VR\n' % n
-                error += self.print_tree()
-                raise Exception(error)
-        return True
-
-    def print_tree(self):
-        st = self.trace
-        st += "nodes are %s\n" % self.nodes
-        st += "paths are %s\n" % self.paths
-        return st
-
-    def quit(self):
-        self.tree.deregister_cllbck('node-added-inview', self.add)
-        self.tree.deregister_cllbck('node-deleted-inview', self.delete)
-        self.tree.deregister_cllbck('node-modified-inview', self.update)
-        self.tree.deregister_cllbck('node-children-reordered', self.reordered)

=== removed directory 'GTG/tests/xdg'
=== removed directory 'GTG/tests/xdg/config'
=== removed directory 'GTG/tests/xdg/config/gtg'
=== removed file 'GTG/tests/xdg/xdg.txt'
--- GTG/tests/xdg/xdg.txt	2012-05-23 08:55:31 +0000
+++ GTG/tests/xdg/xdg.txt	1970-01-01 00:00:00 +0000
@@ -1,6 +0,0 @@
-This folder does not contain any code.
-
-This folder is intented to contain a typical configuration example using for debugging purpose (by launching debug.sh).
-
-For informations on the XDG folders, see :
-http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html

=== removed file 'GTG/tools/import_liblarch.py'
--- GTG/tools/import_liblarch.py	2013-11-25 02:37:46 +0000
+++ GTG/tools/import_liblarch.py	1970-01-01 00:00:00 +0000
@@ -1,106 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding:utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things GNOME! - A personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 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/>.
-# -----------------------------------------------------------------------------
-
-""" Check if liblarch is installed """
-
-import sys
-
-REQUIRED_LIBLARCH_API = "2.1"
-GIT_CMD = "git clone https://github.com/liblarch/liblarch ../liblarch"
-
-
-def import_liblarch(use_local=False):
-    """ Check if liblarch is installed and is compatible
-
-    If not, provide information how to obtain the newest version.
-    If use_local, prioritize local (development) liblarch in ../liblarch"""
-
-    def check_liblarch():
-        """
-        Import liblarch and find out which one is missing
-        """
-        has_libraries = True
-        missing = []
-        try:
-            import liblarch
-            assert liblarch
-        except ImportError:
-            has_libraries = False
-            missing.append("liblarch")
-
-        try:
-            import liblarch_gtk
-            assert liblarch_gtk
-        except ImportError:
-            has_libraries = False
-            missing.append("liblarch_gtk")
-
-        return has_libraries, " and ".join(missing)
-
-    if use_local:
-        sys.path.insert(0, "../liblarch")
-
-    has_libraries, missing = check_liblarch()
-
-    if not use_local and not has_libraries:
-        sys.path.append("../liblarch/")
-        has_libraries, missing = check_liblarch()
-
-    if not has_libraries:
-        print("""GTG can't find %s. To install missing libraries,
-run the following command in the current folder:
-
-%s
-
-More information about liblarch: https://live.gnome.org/liblarch/"""; % (
-            missing, GIT_CMD))
-        return False
-
-    import liblarch
-    try:
-        is_liblarch_compatible = liblarch.is_compatible(REQUIRED_LIBLARCH_API)
-    except:
-        print("""I could not recognize your liblarch module. Make sure that
-you don't have stale copies of liblarch in your import path
-""")
-        is_liblarch_compatible = False
-    if not is_liblarch_compatible:
-        try:
-            liblarch_version = liblarch.API
-        except AttributeError:
-            # Liblarch 1.0 has lowercased API variable
-            liblarch_version = liblarch.api
-
-        print("""Your liblarch copy has its API at version %s
-but your GTG copy need liblarch API version %s
-You may fix that by downloading the last version of liblarch with
-
-%s """ % (liblarch_version, REQUIRED_LIBLARCH_API, GIT_CMD))
-        return False
-
-    return True
-
-if __name__ == "__main__":
-    use_local = "-l" in sys.argv[1:] or "--local-liblarch" in sys.argv[1:]
-
-    if import_liblarch(use_local):
-        sys.exit(0)
-    else:
-        sys.exit(1)

=== removed file 'GTG/tools/testingmode.py'
--- GTG/tools/testingmode.py	2013-11-23 14:40:23 +0000
+++ GTG/tools/testingmode.py	1970-01-01 00:00:00 +0000
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 - 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/>.
-# -----------------------------------------------------------------------------
-
-from GTG.tools.borg import Borg
-
-
-class TestingMode(Borg):
-
-    def set_testing_mode(self, value):
-        self._testing_mode = value
-
-    def get_testing_mode(self):
-        try:
-            return self._testing_mode
-        except:
-            return False

=== modified file 'HACKING'
--- HACKING	2013-11-23 14:40:23 +0000
+++ HACKING	2014-03-10 01:04:59 +0000
@@ -161,7 +161,7 @@
 # -*- coding: utf-8 -*-
 # -----------------------------------------------------------------------------
 # Gettings Things Gnome! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 - Lionel Dricot & Bertrand Rousseau
+# Copyright (c) 2008-2014 - 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
@@ -202,40 +202,3 @@
   6. Update ``AUTHORS`` if the patch author is not already in there.
 
   7. Commit your changes and propose a merge request.
-
-
-Documentation
--------------
-
-We'd love it so much if you could improve the docstrings that you find in GTG.
-
-We use the docstring conventions outlined in PEP 8
-<http://www.python.org/dev/peps/pep-0008/> and PEP 257
-<http://www.python.org/dev/peps/pep-0257/>, except modified to support epytext
-markup. Information on epytext markup can be found at
-<http://epydoc.sourceforge.net/epytext.html>.
-
-You can generate the API docs by installing pydoctor
-(``python-pydoctor`` in debian/ubuntu) and then running::
-
-  make apidocs
-
-This builds the API documentation inside ``doc/api`` in your working tree. If
-you are running GNOME, you can open it in your browser with::
-
-  gnome-open doc/api/index.html
-
-You can also run::
-
-  make edit-apidocs
-
-To launch a pydoctor server at http://localhost:8080/ that will let you edit
-API documentation in place. The feature has a few rough edges, but can be
-really useful for editing and extending API docs. To actually apply your edits
-to the tree::
-
-  wget http://localhost:8080/rawBigDiff -q -O- | bzr patch
-
-Remember, docstrings should be written for people who don't know much about
-the code. Focus on ''why'' things are the way they are, and on describing
-''what'' they are in the first place.

=== modified file 'MANIFEST.in'
--- MANIFEST.in	2012-05-23 08:55:31 +0000
+++ MANIFEST.in	2014-03-10 01:04:59 +0000
@@ -1,16 +1,15 @@
-recursive-include GTG *.glade
 recursive-include GTG *.ui
 recursive-include GTG *.gtg-plugin
 recursive-include GTG *.png
 recursive-include GTG *.svg
-include gtg.desktop
+recursive-include GTG/plugins/export *
 include CHANGELOG
 include README
 include LICENSE
 include AUTHORS
-include org.gnome.GTG.service
-recursive-include data/icons *.png
-recursive-include data/icons *.svg
-include doc/*.1
-recursive-include GTG/plugins/export *
+include requirements.txt
+recursive-include data *
 recursive-include po *.po
+include docs/Makefile
+recursive-include docs/source *
+recursive-include docs/userdoc *

=== modified file 'Makefile'
--- Makefile	2014-02-01 14:47:33 +0000
+++ Makefile	2014-03-10 01:04:59 +0000
@@ -18,21 +18,22 @@
 
 PEP8=pep8
 PYFLAKES=pyflakes
-PYDOCTOR=pydoctor
 
 check: tests pep8 pyflakes
 
+install:
+	pip3 install -r requirements.txt
+
 # Run all of the tests.
 tests:
 	./run-tests
 
-# Get rid of stale files or files made during testing.
+# Remove all temporary files
 clean:
 	rm -rf tmp
-	rm -rf doc/api
-	find . -name '*.pyc' -print0 | xargs -0 rm -f
-	find . -name '*~' -print0 | xargs -0 rm -f
-	find . -name '.*.swp' -print0 | xargs -0 rm -f
+	find -type f -name '*~' -or -name '.*.sw*' -print | xargs rm -f
+	find -type f -name '*.pyc' -print | xargs rm -f
+	find -type d -name '__pycache__' -print | xargs rm -rf
 
 # Check for common & easily catchable Python mistakes.
 pyflakes:
@@ -42,19 +43,7 @@
 pep8:
 	$(PEP8) --statistics --count gtg gtcli gtg_new_task GTG
 
-# Build API documentation.
-apidocs:
-	$(PYDOCTOR) --add-package GTG --make-html --html-output=doc/api \
-		--project-name=GTG --project-url=http://gtg.fritalk.com/
-
-edit-apidocs:
-	$(PYDOCTOR) --add-package GTG --make-html --html-output=doc/api \
-		--project-name=GTG --project-url=http://gtg.fritalk.com/ \
-		--verbose-about=epydoc2stan2 --verbose-about=epydoc2stan2 \
-		--verbose-about=server --verbose-about=server --local-only \
-		--server --edit
-
 # Check for coding standard violations & flakes.
 lint: pyflakes pep8
 
-.PHONY: tests check lint pyflakes pep8 apidocs edit-apidocs clean
+.PHONY: install tests check lint pyflakes pep8 clean

=== modified file 'README'
--- README	2013-08-17 06:27:09 +0000
+++ README	2014-03-10 01:04:59 +0000
@@ -20,8 +20,6 @@
  * python-liblarch 
  * yelp (to read GTG documentation)
 
-To generate the API documentation, you'll also need to install 'pydoctor'.
-
 Please refer to your system documentation for information on how to install
 these modules if they're not currently available.
 
@@ -30,9 +28,6 @@
     $ sudo apt-get install python-support python-gtk2 python-gnome2 \
          python-glade2 python-xdg python-dbus python-liblarch yelp
 
-To enable API documentation generation, execute this command:
-    $ sudo apt-get install python-pydoctor
-
 There are additional plugins (modules for extending the user interface) and
 synchronization services (modules for importing/exporting tasks from/to
 external services) which needs additional packages to work correctly.

=== renamed file 'gtcli_bash_completion' => 'data/gtcli_bash_completion' (properties changed: +x to -x)
=== renamed file 'gtg.appdata.xml' => 'data/gtg.appdata.xml'
=== renamed file 'gtg.desktop' => 'data/gtg.desktop'
=== renamed file 'org.gnome.GTG.service' => 'data/org.gnome.GTG.service'
=== renamed directory 'test/data' => 'data/test-data'
=== removed file 'doc/gtcli.1'
--- doc/gtcli.1	2013-12-12 12:43:00 +0000
+++ doc/gtcli.1	1970-01-01 00:00:00 +0000
@@ -1,58 +0,0 @@
-.TH gtgcli 1 2012-08-16 "gtgcli"
-.SH NAME
-gtcli \-  Command-line interface for Getting Things GNOME!
-.SH SYNOPSIS
-.B gtgcli [options] COMMAND [command options]
-.SH DESCRIPTION
-gtgcli provides a handy command-line interface to GTG. It allows one to list
-and modify your task directly from the command line. It also allows one to
-interact with GTG using shell scripts.
-.SH OPTIONS
-.TP
-\fB\-h, \-\-help\fB
-Prints some information about gtg's usage and options.
-.SH COMMAND OPTIONS
-.TP
-\fBnew\fB
-Creates a new task.
-.TP
-\fBshow <tid>\fB
-Display task with <tid> task ID.
-.TP
-\fBedit <tid>\fB
-Opens the GUI editor for the task with <tid> task ID.
-.TP
-\fBdelete <tid>\fB
-Removes task with <tid> task ID.
-.TP
-\fBlist [all|today|<filter>|<tag>]\fB
-List tasks corresponding to the given attributes.
-.TP
-\fBsearch <expression>\fB
-Search tasks corresponding to <expression>. Read the documentation from GTG's
-help to know more about the search query syntax.
-.TP
-\fBcount [all|today|<filter>|<tag>]\fB
-Outputs the task count for all the task corresponding to the given attributes.
-.TP
-\fBsummary [all|today|<filter>|<tag]\fB
-Report how many tasks starting/due each day.
-.TP
-\fBpostpone <tid> <date>\fB
-Updates the start date of the task with <tid> task id to <date>.
-.TP
-\fBclose <tid>\fB
-Sets state of task identified by <tid> to done.
-.TP
-\fBbrowser [hide|show]\fB
-Hides or shows the task browser window.
-.SH "SEE ALSO"
-gtg (1)
-.SH BUGS
-Please report any bug you may experience to the \fBGTG\fP Developers, that can
-be reached at \fRhttps://edge.launchpad.net/gtg\fP
-.SH COPYRIGHT
-This manual page is Copyright 2012 Bertrand Rousseau
-<bertrand.rousseau@xxxxxxxxx>. Permission is granted to copy, distribute
-and/or modify this document under the terms of the GNU General Public License,
-Version 3 or any later version published by the Free Software Foundation.

=== removed file 'doc/gtg.1'
--- doc/gtg.1	2013-12-12 12:43:00 +0000
+++ doc/gtg.1	1970-01-01 00:00:00 +0000
@@ -1,52 +0,0 @@
-.TH gtg 1 2012-08-16 "gtg"
-.SH NAME
-gtg \-  Getting Things GNOME!, a personal tasks and TODO-list items organizer
-for the GNOME desktop environment
-.SH SYNOPSIS
-.B gtg [options]
-.SH DESCRIPTION
-Getting Things GNOME! is a personal tasks and TODO-list items organizer for the
-GNOME desktop environment inspired by the Getting Things Done (GTD)
-methodology. GTG is designed with flexibility, adaptability, and ease of use
-in mind so it can be used as more than just GTD software.
-.PP
-GTG is intended to help you track everything you need to do and need to know,
-from small tasks to large projects.
-.PP
-GTG uses a very handy system for creating and editing tasks. The task editor
-can automatically recognize metadata such as tags and subtasks through the use
-of a very simple syntax.
-.SH OPTIONS
-.TP
-\fB\-b, \-\-boot\-test\fB
-Boot-up only.  Causes gtg to exit immediately after completing the first
-iteration of the main loop.  Useful for boot performance testing work.
-.TP
-\fB\-c, \-\-no-crash\-handler\fB
-Disable crash handler.  Causes the Apport automatic crash reporting
-utility to not be invoked when gtg crashes; instead it will print out a
-normal python backtrace.  This can be useful for debugging crash bugs,
-or if the crash handler is misbehaving.
-.TP
-\fB\-d, \-\-debug\fB
-Debug mode.  Prints extra information to the console which may be useful
-for understanding and reporting bugs.
-.TP
-\fB\-h, \-\-help\fB
-Prints some information about gtg's usage and options.
-.TP
-\fB\-l, \-\-local-liblarch\fB
-Use local liblarch. Look for the liblarch python library in ../liblarch.
-This is mainly useful for testing purpose.
-.TP
-\fB\-t TITLE, \-\-title=TITLE\fB
-Set the window's title to TITLE.
-.TP
-\fB\-v, \-\-version\fB
-Prints version and exits.
-.SH COPYRIGHT
-This manual page is Copyright 2009, 2012 Luca Falavigna <dktrkranz@xxxxxxxxxx>
-and Bertrand Rousseau <bertrand.rousseau@xxxxxxxxx>. Permission is granted
-to copy, distribute and/or modify this document under the terms of the GNU
-General Public License, Version 3 or any later version published by the Free
-Software Foundation.

=== removed file 'doc/gtg_new_task.1'
--- doc/gtg_new_task.1	2012-05-23 08:55:31 +0000
+++ doc/gtg_new_task.1	1970-01-01 00:00:00 +0000
@@ -1,29 +0,0 @@
-.TH GTG_NEW_TASK 1 "4 December 2009"
-.SH NAME
-gtg_new_task \- Adds a task to the \fBGetting Things GNOME!\fP organizer
-.SH SYNOPSIS
-\fBgtg_new_task\fP [\-h | \-\-help] [\-i | \-\-interactive]
-.SH DESCRIPTION
-\fBgtg_new_task\fP creates a new task in the Getting Things GNOME! organizer
-for the GNOME desktop via the DBUS message bus.  Getting Things GNOME! must be
-running for the command to work.
-.SS Options
-.TP
-\fB[\-h | \-\-help]\fP
-Shows a brief usage help.
-.TP
-\fB[\-i | \-\-interactive]\fP
-Accepts a task description via stdin.
-.SH FILES
-None
-.SH "SEE ALSO"
-gtg (1)
-.SH BUGS
-Please report any bug you may experience to the \fBGTG\fP Developers, that can
-be reached at \fRhttps://edge.launchpad.net/gtg\fP
-.SH COPYRIGHT
-This manual page is Copyright2009 Luca Invernizzi <invernizzi.l@xxxxxxxxx>.
-Permission is granted to copy, distribute and/or modify this document under the
-terms of the GNU General Public License, Version 3 or any later version
-published by the Free Software Foundation.
-

=== renamed directory 'doc' => 'docs'
=== added file 'docs/Makefile'
--- docs/Makefile	1970-01-01 00:00:00 +0000
+++ docs/Makefile	2014-03-10 01:04:59 +0000
@@ -0,0 +1,74 @@
+# Makefile for Sphinx documentation
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         = a4
+BUILDDIR      = build
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Install Sphinx.)
+endif
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+
+.PHONY: help clean userhtml html singlehtml latex latexpdf man linkcheck
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  userhtml   to make HTML files from user level documentation"
+	@echo "  html       to make standalone HTML files"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  man        to make manual pages"
+	@echo "  linkcheck  to check all external links for integrity"
+
+clean:
+	rm -rf $(BUILDDIR)/*
+	rm -f *.1
+
+userhtml:
+	mkdir -p $(BUILDDIR)/userhtml
+	yelp-build html -o $(BUILDDIR)/userhtml userdoc/C/*.page
+	@echo
+	@echo "Build finished. User HTML pages are in $(BUILDDIR)/userhtml."
+
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) .
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."

=== added directory 'docs/source'
=== added directory 'docs/source/_static'
=== added directory 'docs/source/_templates'
=== added file 'docs/source/conf.py'
--- docs/source/conf.py	1970-01-01 00:00:00 +0000
+++ docs/source/conf.py	2014-03-10 01:04:59 +0000
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+
+# Use local version of GTG
+sys.path.insert(0, '../..')
+
+from GTG import info
+
+extensions = [
+    'sphinx.ext.autodoc',
+    'sphinx.ext.graphviz',
+    'sphinx.ext.viewcode',
+]
+
+project = 'Getting Things GNOME!'
+copyright = 'The GTG Team'
+
+short_version = '.'.join(info.VERSION.split('.')[:2])
+version = short_version
+release = info.VERSION
+
+master_doc = 'index'
+source_suffix = '.rst'
+
+exclude_patterns = []
+pygments_style = 'sphinx'
+
+# -- Options for HTML output ----------------------------------------------
+on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
+if on_rtd:
+    html_theme = 'default'
+else:
+    html_theme = 'nature'
+
+html_show_sphinx = False
+
+# -- Options for LaTeX output ---------------------------------------------
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+#  author, documentclass [howto, manual, or own class]).
+latex_documents = [(
+    'index',
+    'gtg.tex',
+    'Getting Things GNOME! Documentation',
+    'The GTG Team',
+    'manual',
+)]
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    (
+        'man/gtcli', 'gtcli',
+        'Command-line interface for Getting Things GNOME!',
+        [], 1,
+    ),
+    (
+        'man/gtg', 'gtg',
+        'Getting Things GNOME!, a personal tasks and TODO-list items '
+        'organizer for the GNOME desktop environment',
+        [], 1,
+    ),
+    (
+        'man/gtg_new_task', 'gtg_new_task',
+        'Adds a task to the Getting Things GNOME! organizer',
+        [], 1,
+    ),
+]

=== added file 'docs/source/contributing.rst'
--- docs/source/contributing.rst	1970-01-01 00:00:00 +0000
+++ docs/source/contributing.rst	2014-03-10 01:04:59 +0000
@@ -0,0 +1,140 @@
+===================
+Contributing to GTG
+===================
+
+GTG uses Bazaar_ for versioning. It might be useful to read `Bazaar's tutorial`_ first.
+
+.. _Bazaar: http://bazaar.canonical.com/
+.. _`Bazaar's tutorial`: http://doc.bazaar.canonical.com/latest/en/mini-tutorial/
+
+Dependencies
+============
+
+You need to have python-configobj installed
+
+Getting the code
+================
+
+Get the latest version of the code on Launchpad_::
+
+    $ bzr branch lp:gtg trunk
+
+Although if you're thinking of contributing more than one patch, you might want to do::
+
+    $ bzr init-repo gtg
+    $ cd gtg
+    $ bzr branch lp:gtg trunk
+
+This will share revision data between branches, reducing storage costs & network time.
+
+
+Launch gtg with debugging data (so it doesn't mess with your data)::
+
+    $ cd trunk
+    $ ./scripts/debug.sh
+
+.. _Launchpad: https://launchpad.net
+
+Choosing a feature to work on
+=============================
+
+If you are a happy user of GTG and nothing bothers you but you would like to contribute you can:
+
+* choose a `LOVE bug`_ which are easier to solve
+* ask people on IRC channel #gtg on irc://irc.gimp.org/#gtg
+* ask on our `mailing list`_
+
+.. _`LOVE bug`: https://bugs.launchpad.net/gtg/+bugs?field.status%3Alist=NEW&field.status%3Alist=CONFIRMED&field.status%3Alist=TRIAGED&field.status%3Alist=INPROGRESS&assignee_option=none&field.tag=love
+.. _`mailing list`: https://launchpad.net/~gtg-user
+
+
+Working on the feature in a branch
+==================================
+
+You have your local copy of the code (see "Getting the code"). Now, create a
+local branch of your local branch (yes, it is)::
+
+    $ cd ..
+    $ bzr branch trunk cool-new-feature
+
+(your *trunk* folder is branched in a new *cool-new-feature* folder)
+
+When working with Bazaar, it's a good idea to keep your local *trunk* branch as
+a pristine copy of trunk on Launchpad.
+
+Hack and commit your changes::
+
+    bzr commit -m "description of my change"
+
+Repeat as much as you want. Don't hesitate to abuse the local commits. Think of
+*commit* like *quick save* in a video game :)
+
+Run the units tests to see if all is fine::
+
+    $ make check=python3
+    ./run-tests
+    ...........
+    ----------------------------------------------------------------------
+    Ran 11 tests in 0.063s
+
+    OK
+
+Modify CHANGELOG to reflect your changes. If it's your first contribution, add
+yourself in the AUTHORS file with your email address.
+
+If the trunk has been updated while you were hacking, you should update your
+local trunk branch, and merge modification in **your** branch::
+
+    $ cd ../trunk
+    $ bzr pull trunk
+    $ cd ../cool-new-feature
+    $ bzr merge ../trunk
+
+If you have conflicts, you must solve them. Refer to `conflicts guide`_.
+
+.. _`conflicts guide`: http://doc.bazaar.canonical.com/bzr.0.92/en/user-guide/conflicts.html
+
+Once you don't have any conflict anymore, you must commit the changes related
+to the merge. Use a clear commit message, like::
+
+    Updating branch by merging the last trunk version.
+
+Pushing your work to your own branch on Launchpad (where *ploum* is your
+Launchpad username)::
+
+    $ bzr push lp:~ploum/gtg/cool-new-feature
+
+Alternatively, if you want other gtg users to be able to write to your branch,
+push it in the gtg-user group (you have to be part of it)::
+
+    $ bzr push lp:~gtg-user/gtg/ploum_branch
+
+Ask for a merge request and comment on the corresponding bug. (Open one if
+there is none). Add the tag *toreview* to the bug in Launchpad. This is very
+important and ensures we are not letting a patch rotting.
+
+You can file a bug at https://bugs.launchpad.net/gtg/+filebug.
+
+To ask for a merge request, run::
+
+$ cd cool-new-feature
+$ bzr lp-open
+
+This will open the branch's web page on Launchpad. From there, click *Propose for merging*.
+
+If your branch is solving specific reported bugs, please also register your
+branch to these bugs (there is an link for that in each bug report page). It
+allows to link together all related resources (which in turn is useful to dig
+out precious information from all the discussions that happened around those
+bugs).
+
+For more detailed information, see the `HACKING`_ guide included in the GTG code.
+
+.. _`HACKING`: http://bazaar.launchpad.net/~gtg/gtg/trunk/annotate/head%3A/HACKING
+
+Troubleshooting
+===============
+
+If you have a problem with SSH keys while uploading to Launchpad, look at this `SuperUser question`_.
+
+.. _`SuperUser question`: http://superuser.com/questions/161337/big-ssh-problem

=== added file 'docs/source/index.rst'
--- docs/source/index.rst	1970-01-01 00:00:00 +0000
+++ docs/source/index.rst	2014-03-10 01:04:59 +0000
@@ -0,0 +1,53 @@
+=====================
+Getting Things GNOME!
+=====================
+
+Getting Things GNOME! (GTG) is a personal tasks and TODO-list items organizer
+for the GNOME desktop environment inspired by the Getting Things Done (GTD)
+methodology. GTG is designed with flexibility, adaptability, and ease of use in
+mind so it can be used as more than just GTD software.
+
+GTG is intended to help you track everything you need to do and need to know,
+from small tasks to large projects.
+
+Contents
+========
+
+* `User level documentation <http://www.gtgnome.net/html/userdoc/index.html>`_
+
+
+Documentation for developers:
+
+.. toctree::
+   :maxdepth: 1
+
+   contributing
+
+Man pages
+=========
+
+.. toctree::
+   :maxdepth: 1
+
+   man/gtg
+   man/gtg_new_task
+   man/gtcli
+
+Release notes
+=============
+
+.. toctree::
+   :maxdepth: 1
+
+   releases/v0.3.1
+   releases/v0.2.2
+   releases/v0.1.9
+   releases/v0.1
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`

=== added directory 'docs/source/man'
=== added file 'docs/source/man/gtcli.rst'
--- docs/source/man/gtcli.rst	1970-01-01 00:00:00 +0000
+++ docs/source/man/gtcli.rst	2014-03-10 01:04:59 +0000
@@ -0,0 +1,78 @@
+gtcli(1)
+========
+
+SYNOPSIS
+--------
+
+**gtgcli [options] COMMAND [command options]**
+
+DESCRIPTION
+-----------
+gtgcli provides a handy command-line interface to GTG. It allows one to list
+and modify your task directly from the command line. It also allows one to
+interact with GTG using shell scripts.
+
+OPTIONS
+-------
+
+**-h, --help**
+    Prints some information about gtg's usage and options.
+
+COMMAND OPTIONS
+---------------
+
+**new**
+    Creates a new task.
+
+
+**show <tid>**
+    Display task with <tid> task ID.
+
+
+**edit <tid>**
+    Opens the GUI editor for the task with <tid> task ID.
+    
+
+**delete <tid>**
+    Removes task with <tid> task ID.
+
+**list [all|today|<filter>|<tag>]**
+    List tasks corresponding to the given attributes.
+
+**search <expression>**
+    Search tasks corresponding to <expression>. Read the documentation from GTG's
+    help to know more about the search query syntax.
+
+**count [all|today|<filter>|<tag>]**
+    Outputs the task count for all the task corresponding to the given attributes.
+
+**summary [all|today|<filter>|<tag]**
+    Report how many tasks starting/due each day.
+
+**postpone <tid> <date>**
+    Updates the start date of the task with <tid> task id to <date>.
+
+**close <tid>**
+    Sets state of task identified by <tid> to done.
+
+**browser [hide|show]**
+    Hides or shows the task browser window.
+
+SEE ALSO
+--------
+
+gtg (1)
+
+BUGS
+----
+
+Please report any bug you may experience to the **GTG** Developers, that can
+be reached at https://launchpad.net/gtg
+
+COPYRIGHT
+---------
+
+This manual page is Copyright 2012 Bertrand Rousseau
+<bertrand.rousseau@xxxxxxxxx>. Permission is granted to copy, distribute
+and/or modify this document under the terms of the GNU General Public License,
+Version 3 or any later version published by the Free Software Foundation.

=== added file 'docs/source/man/gtg.rst'
--- docs/source/man/gtg.rst	1970-01-01 00:00:00 +0000
+++ docs/source/man/gtg.rst	2014-03-10 01:04:59 +0000
@@ -0,0 +1,62 @@
+gtg(1)
+======
+
+SYNOPSIS
+--------
+
+**gtg [options]**
+
+DESCRIPTION
+-----------
+
+Getting Things GNOME! is a personal tasks and TODO-list items organizer for the
+GNOME desktop environment inspired by the Getting Things Done (GTD)
+methodology. GTG is designed with flexibility, adaptability, and ease of use in
+mind so it can be used as more than just GTD software.
+
+
+GTG is intended to help you track everything you need to do and need to know,
+from small tasks to large projects.
+
+GTG uses a very handy system for creating and editing tasks. The task editor
+can automatically recognize metadata such as tags and subtasks through the use
+of a very simple syntax.
+
+OPTIONS
+-------
+
+**-b, --boot-test**
+    Boot-up only. Causes gtg to exit immediately after completing the first
+    iteration of the main loop. Useful for boot performance testing work.
+
+**-c, --no-crash-handler**
+    Disable crash handler. Causes the Apport automatic crash reporting utility
+    to not be invoked when gtg crashes; instead it will print out a normal
+    python backtrace. This can be useful for debugging crash bugs, or if the
+    crash handler is misbehaving.
+
+**-d, --debug**
+    Debug mode. Prints extra information to the console which may be useful for
+    understanding and reporting bugs.
+
+**-h, --help**
+    Prints some information about gtg's usage and options.
+
+**-l, --local-liblarch**
+    Use local liblarch. Look for the liblarch python library in ../liblarch.
+    This is mainly useful for testing purpose.
+
+**-t TITLE, --title=TITLE**
+    Set the window's title to TITLE.
+
+**-v, --version**
+    Prints version and exits.
+
+COPYRIGHT
+---------
+
+This manual page is Copyright 2009, 2012 Luca Falavigna <dktrkranz@xxxxxxxxxx>
+and Bertrand Rousseau <bertrand.rousseau@xxxxxxxxx>. Permission is granted
+to copy, distribute and/or modify this document under the terms of the GNU
+General Public License, Version 3 or any later version published by the Free
+Software Foundation.

=== added file 'docs/source/man/gtg_new_task.rst'
--- docs/source/man/gtg_new_task.rst	1970-01-01 00:00:00 +0000
+++ docs/source/man/gtg_new_task.rst	2014-03-10 01:04:59 +0000
@@ -0,0 +1,42 @@
+gtg_new_task(1)
+===============
+
+SYNOPSIS
+--------
+
+**gtg_new_task** [-h | --help] [-i | --interactive]
+
+DESCRIPTION
+-----------
+
+**gtg_new_task** creates a new task in the Getting Things GNOME! organizer for
+the GNOME desktop via the DBUS message bus. Getting Things GNOME! must be
+running for the command to work.
+
+Options
+~~~~~~~
+
+**[-h | --help]**
+    Shows a brief usage help.
+
+**[-i | --interactive]**
+    Accepts a task description via stdin.
+
+SEE ALSO
+--------
+
+gtg (1)
+
+BUGS
+----
+
+Please report any bug you may experience to the **GTG** Developers, that can be
+reached at https://launchpad.net/gtg
+
+COPYRIGHT
+---------
+
+This manual page is Copyright2009 Luca Invernizzi <invernizzi.l@xxxxxxxxx>.
+Permission is granted to copy, distribute and/or modify this document under the
+terms of the GNU General Public License, Version 3 or any later version
+published by the Free Software Foundation.

=== renamed directory 'doc/release_notes' => 'docs/source/releases'
=== added file 'docs/source/releases/v0.1.9.rst'
--- docs/source/releases/v0.1.9.rst	1970-01-01 00:00:00 +0000
+++ docs/source/releases/v0.1.9.rst	2014-03-10 01:04:59 +0000
@@ -0,0 +1,49 @@
+v0.1.9: "Five curtain calls"
+============================
+
+The whole GTG development team is pleased to announce that after months of hard
+work, we've just released a new version of Getting Things GNOME!, codenamed
+"Five curtain calls". Don't even hesitate, rush on our Launchpad project page
+and download the archive or, if you prefer, download the packaged version for
+Debian and Ubuntu from our PPA!
+
+The GTG 0.1.9 release is a beta release for the upcoming 0.2, which is due very
+soon. It packs a *huge amount* of new features, among them:
+
+* support for plugins, thanks to Paulo Cabido's Google Summer of Code
+* already 6 plugins available:
+
+  - Remember the milk synchronization
+  - hamster integration
+  - bugzilla
+  - geolocation
+  - tomboy
+  - notification area
+
+* improved performances
+* support for tag groups
+* improved editor UI
+* fuzzy due dates
+
+The 0.2 release will also fixes not less than 99 bugs! Thanks to our new GTG
+developers (Paulo Cabido, Luca Invernizzi and Kevin Mehall), a terrific work
+has been achieved for 0.1.9!
+
+As this is a beta release, we kindly ask every adventurous testers to help us
+make the soon-to-come 0.2 a great release by reporting any unnoticed bug to our
+project page.
+
+Unfortunately, this release also comes with 2 unresolved bugs that worth mentioning:
+
+* A bug with intel GPU drivers in Ubuntu 9.10 distributions may prevent tag
+  color squares from appearing. It seems that upgrading to a newest version of
+  those drivers fixes that. More info on the bug page.
+* A GTK bug causes some errors to show on standard output if you run GTG from a
+  terminal, this bug has been reported upstream.
+
+Those 2 bugs are caused by upstream bugs and will only be corrected once the
+related apps will be fixed, which should happen anytime.
+
+Anyway, we'll soon come back with a new, shiny, a full-featured 0.2 release
+that will sure rock. Until then, we hope you'll like GTG 0.1.9 as much as we
+do!

=== added file 'docs/source/releases/v0.1.rst'
--- docs/source/releases/v0.1.rst	1970-01-01 00:00:00 +0000
+++ docs/source/releases/v0.1.rst	2014-03-10 01:04:59 +0000
@@ -0,0 +1,37 @@
+v0.1: "Just 5 minutes more"
+===========================
+
+Hi, everyone!
+
+The first official release of Getting Things Gnome!, GTG "Just 5
+minutes more" 0.1, is out!
+
+GTG is a personal organizer for the GNOME desktop environment, it
+focuses on ease of use and flexibility, while keeping things simple.
+
+This release is our first. So far GTG supports:
+
+ - task edition using a text editor, almost no form fields!
+ - fast consecutive tasks creation
+ - tags to sort tasks
+ - color for tags, to easily differenciate tagged tasks
+
+There are very few differences with the release candidate we published
+last week.
+
+The software is still a bit in a proof-of-concept state, since in the
+future we want to test a lot of different functionalities. We'd really
+like to know what you like and dislike in GTG, so we can organize
+future work and do what's the most important: provide a program that
+people love to use because it makes their life better ;-)
+
+Tarballs with the source code are available on our launchpad project
+page: https://launchpad.net/gtg/+download
+Packages for Ubuntu are also available on our PPA:
+https://launchpad.net/~gtg/+archive/ppa
+
+Now, grab it and tell us what you think! Don't hesitate to discuss
+with us on our mailing-lists, on IRC (#gtg on GimpNET), or to post
+bugs on launchpad!
+
+Enjoy!

=== added file 'docs/source/releases/v0.2.2.rst'
--- docs/source/releases/v0.2.2.rst	1970-01-01 00:00:00 +0000
+++ docs/source/releases/v0.2.2.rst	2014-03-10 01:04:59 +0000
@@ -0,0 +1,16 @@
+v0.2.2: Protector
+=================
+
+After just one month since the last release, the GTG development team is
+pleased to announce the release of Getting things GNOME! 0.2.2, codename
+"Protector". This one is the last of the 0.2.X serie: we have already started
+breaking everything to make GTG faster and better.
+
+Aside from the tons of bug fixes that ship with every release, this one
+features a brand new preference dialog and integration with Docky.
+
+On the new plug-ins side, you'll get one to send tasks via email, another that
+can import tasks from JSON, and the last one that can delete old closed tasks
+automatically (which makes GTG faster if you have a huge amount of tasks).  The
+RememberTheMilk plugin is now stable and features tags synchronization.
+Finally, we support the libindicator library, making GTG "Ubuntu Lucid" ready.

=== renamed file 'doc/release_notes/0.3.1.txt' => 'docs/source/releases/v0.3.1.rst'
--- doc/release_notes/0.3.1.txt	2013-11-24 14:48:09 +0000
+++ docs/source/releases/v0.3.1.rst	2014-03-10 01:04:59 +0000
@@ -1,72 +1,108 @@
-Getting Things GNOME! 0.3.1 - Release Note - 24/11/2013
-=====================================================
+v0.3.1
+======
+
+Released: 24/11/2013
 
 After about 12 months of very active development, the GTG team is proud to
 announce the release of Getting Things GNOME! 0.3.1!
 
 This version is packed with an amazing amount of bug fixes and many novelties!
-The goal with 0.3.1 was to refactor several of it’s existing functionalities and improve the codebase. A lot of time was spent on improving the plugins. In the process we have fixed lots of bugs and have made a few feature enhancements!
+The goal with 0.3.1 was to refactor several of it’s existing functionalities
+and improve the codebase. A lot of time was spent on improving the plugins. In
+the process we have fixed lots of bugs and have made a few feature
+enhancements!
 
 What's new in 0.3.1?
-------------------
-
-Here's an (incomplete) list of changes:
-
-  - Fixed Hamster Plugin: The Hamster plugin which was used to start a GTG Task as a Hamster activity was not working for some time. It has been fixed. Now tasks can be started in Hamster and they can also be stopped from GTG itself. When a task is closed or deleted, it’s corresponding hamster activity will be stopped.
-  - GTGOnline!: Parin Porecha started developing a web application for GTG (currently it is named GTGOnline!) as a part of his Google Summer of Code 2013 project [2]. It is up and running [3]. It not only supports the current features of GTG, but also Task Sharing! You can now add users to groups and share your tasks with them ! He has also written a synchronization backend to sync tasks and tags with GTGOnline!
-  - Port to python3 and gtk3: Xuan Hu ported GTG to python3 and gtk3 as a part of his Google Summer of Code 2013 project [4]. His branch [5] is almost stable and is currently being tested
-  - PEP8ification of the codebase: There were more than 10,000 PEP8 errors in the codebase which have now been removed.
-  - New task keyboard shortcut: Working on another application and need to create a task ?
-Now it is possible via a keyboard shortcut. Configure it in the settings, and on pressing the shortcut, new task editor will open up !
-  - Translations: all translations were updated
-  - Stability: we've fixed many bugs causing crashes during this release.
-  - Various other fixes:
-      - Updated and improved plugins: notification area icon, urgency color,
-        export, ...
-      - 7 new feature enhancements
-
-You can see the complete list of changes in our CHANGELOG:
-https://bazaar.launchpad.net/~gtg/gtg/trunk/view/1332/CHANGELOG
+--------------------
+
+Here's an (incomplete) list of changes.
+You can see the complete list of changes in our CHANGELOG_.
+
+.. _CHANGELOG: https://bazaar.launchpad.net/~gtg/gtg/trunk/view/1332/CHANGELOG
+
+Fixed Hamster Plugin
+~~~~~~~~~~~~~~~~~~~~
+
+The Hamster plugin which was used to start a GTG Task as a Hamster activity was
+not working for some time. It has been fixed. Now tasks can be started in
+Hamster and they can also be stopped from GTG itself. When a task is closed or
+deleted, it’s corresponding hamster activity will be stopped.
+
+GTGOnline!
+~~~~~~~~~~
+
+Parin Porecha started developing a web application for GTG (currently it is
+named GTGOnline!) as a part of `his Google Summer of Code 2013 project`_. It
+is `up and running`_. It not only supports the current features of GTG, but
+also Task Sharing! You can now add users to groups and share your tasks with
+them! He has also written a synchronization backend to sync tasks and tags
+with GTGOnline!
+
+.. _`his Google Summer of Code 2013 project`: https://wiki.gnome.org/Outreach/SummerOfCode/2013/Projects/ParinPorecha_GTGOnline
+.. _`up and running`: http://gtgonline-parinporecha.rhcloud.com/
+
+Port to python3 and gtk3
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Xuan Hu `ported GTG to python3 and gtk3`_ as a part of his Google Summer of
+Code 2013 project. `Xuan's branch`_ is almost stable and is currently being tested
+
+.. _`ported GTG to python3 and gtk3`: https://wiki.gnome.org/Outreach/SummerOfCode/2013/Projects/XuanHu_PortingGTG
+.. _`Xuan's branch`: https://code.launchpad.net/~huxuan/gtg/port-to-gtk3-py3/
+
+PEP8ification of the codebase
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There were more than 10,000 PEP8 errors in the codebase which have now been removed.
+
+New task keyboard shortcut
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Working on another application and need to create a task?
+
+Now it is possible via a keyboard shortcut. Configure it in the settings, and
+on pressing the shortcut, new task editor will open up!
+
+Translations
+~~~~~~~~~~~~
+
+All translations were updated.
+
+Stability
+~~~~~~~~~
+
+We've fixed many bugs causing crashes during this release.
+
+Various other fixes
+~~~~~~~~~~~~~~~~~~~
+
+* Updated and improved plugins: notification area icon, urgency color, export, ...
+* 7 new feature enhancements
+
+
+Thank You
+---------
 
 The GTG developer team would like to thank and congratulate all the great people
 which contributed to this version. You did an amazing work! Thanks also to our
 many new contributors which joined us during this development cycle!
 
+
 What's next?
 ------------
 
-Xuan Hu ported GTG to python3 and GTK3 as a part of his GSoC project. He has done a great job, and his branch is almost stable.
-During the next months, we plan to test his branch and fix the remaining bugs so that it can be merged for release 0.3.2
-Porting to GTK3 has also paved the way for upcoming redesign of GTG [6]!
+Xuan Hu ported GTG to python3 and GTK3 as a part of his GSoC project. He has
+done a great job, and his branch is almost stable.  During the next months, we
+plan to test his branch and fix the remaining bugs so that it can be merged for
+release 0.3.2 Porting to GTK3 has also paved the way for upcoming
+`redesign of GTG`_!
 
 We also have many other projects in the pipeline, most notably:
 
- - collaborative task management, a project which has been started by
-   Izidor Matusov during Google Summer of Code 2012 [10].
- - Testing and merging GTGOnline! synchronization backend [3] with the trunk.
-
-
-About Getting Things GNOME!
----------------------------
-
-GTG is a personal task organizer for the GNOME desktop inspired by the Getting
-Things Done (GTD) methodology. GTG is designed with flexibility, adaptability,
-and ease of use in mind so it can be used as more than just GTD software.
-
-GTG is intended to help you track everything you need to do and need to know,
-from small tasks to large projects.
-
-Links:
-------
-
- [1] Screenshots: http://gtgnome.net/screenshots
- [2] Parin Porecha’s GSoC (web application for Getting Things Gnome!): https://wiki.gnome.org/SummerOfCode2013/Projects/ParinPorecha_GTGOnline
- [3] Parin Porecha’s GTGOnline! sync backend branch: https://code.launchpad.net/~parinporecha/gtg/backend_gtgonline
- [3] Link to GTG’s web application (GTGOnline!): http://gtgonline-parinporecha.rhcloud.com/
- [4] Xuan Hu’s GSoC (Porting GTG and Liblarch to Gtk3 and Python3): https://wiki.gnome.org/SummerOfCode2013/Projects/XuanHu_PortingGTG
- [5] Xuan Hu’s python3 and gtk3 port branch: https://code.launchpad.net/~huxuan/gtg/port-to-gtk3-py3/
- [6] GTG design page: https://live.gnome.org/gtg/Design
- [7] The project's blog: http://gtgnome.net/
- [8] The project's page on launchpad: https://launchpad.net/gtg
- [9] The project's page on the GNOME wiki: https://live.gnome.org/gtg/
- [10] Izidor Matusov's GSoC (collaborative GTG): https://live.gnome.org/SummerOfCode2012/Projects/IzidorMatusov_CollaborativeGTG
+* `collaborative task management`_, a project which has been started by
+  Izidor Matusov during Google Summer of Code 2012.
+* Testing and merging GTGOnline! `synchronization backend`_ with the trunk.
+
+.. _`redesign of GTG`: https://wiki.gnome.org/Apps/gtg/Design/
+.. _`collaborative task management`: https://wiki.gnome.org/Outreach/SummerOfCode/2012/Projects/IzidorMatusov_CollaborativeGTG
+.. _`synchronization backend`: https://code.launchpad.net/~parinporecha/gtg/backend_gtgonline

=== removed file 'gtg'
--- gtg	2013-11-25 02:37:46 +0000
+++ gtg	1970-01-01 00:00:00 +0000
@@ -1,92 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding:utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things GNOME! - A personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 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/>.
-# -----------------------------------------------------------------------------
-
-""" Main script which parse arguments and launch GTG """
-
-import sys
-
-from optparse import OptionParser
-from gi.repository.Gdk import Screen
-
-from GTG import info
-from GTG.tools.import_liblarch import import_liblarch
-
-
-def x_is_running():
-    """ Return True if GTG could be displayed on the current XServer """
-    try:
-        if Screen().get_default().get_display():
-            return True
-    except RuntimeError as exc:
-        print(exc)
-    return False
-
-
-def main():
-    """ Parse arguments and run GTG
-
-    Importing GTG.gtg must be done after importing and setting up paths
-    for Liblarch """
-
-    parser = OptionParser()
-    parser.add_option('-b', '--boot-test', action='store_true',
-                      dest='boot_test', help="Exit after completing \
-                                                    boot-up actions",
-                      default=False)
-    parser.add_option('-c', '--no-crash-handler', action='store_true',
-                      dest='no_crash_handler', help="Disable the automatic \
-                                                             crash handler",
-                      default=False)
-    parser.add_option('-d', '--debug', action='store_true', dest='debug',
-                      help="Enable debug output", default=False)
-    parser.add_option('-l', '--local-liblarch', action='store_true',
-                      dest='local_liblarch', default=False,
-                      help="Use local liblarch located in ../liblarch if it is\
-                                                                      posible")
-    parser.add_option('-t', '--title', action='store',
-                      help="Use special title for windows' title")
-    parser.add_option('-v', '--version', action='store_true',
-                      dest='print_version', help="Print GTG's version number",
-                      default=False)
-    (options, args) = parser.parse_args()
-
-    if options.title is not None:
-        info.NAME = options.title
-
-    if options.print_version:
-        print("GTG (Getting Things GNOME!)", info.VERSION)
-        print()
-        print("For more information:", info.URL)
-        sys.exit(0)
-
-    elif not x_is_running():
-        print("Could not open X display")
-        sys.exit(1)
-
-    elif import_liblarch(options.local_liblarch):
-        from GTG import gtg
-        sys.exit(gtg.main(options, args))
-
-
-if __name__ == "__main__":
-    try:
-        main()
-    except KeyboardInterrupt:
-        sys.exit(1)

=== removed file 'profile.py'
--- profile.py	2013-11-25 02:37:46 +0000
+++ profile.py	1970-01-01 00:00:00 +0000
@@ -1,36 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 - 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/>.
-# -----------------------------------------------------------------------------
-
-import GTG.gtg
-import cProfile
-import pstats
-from optparse import OptionParser
-
-parser = OptionParser()
-parser.add_option("-d", "--debug",
-               action="store_true", dest="debug", help="enable debug output")
-(options, args) = parser.parse_args()
-
-cProfile.run("GTG.gtg.main(options, args)", filename="gtg.profile")
-
-p = pstats.Stats('gtg.profile')
-p.sort_stats('cumulative').print_stats(15)
-p.sort_stats('time').print_stats(15)
-p.sort_stats('calls').print_stats(15)

=== added file 'requirements.txt'
--- requirements.txt	1970-01-01 00:00:00 +0000
+++ requirements.txt	2014-03-10 01:04:59 +0000
@@ -0,0 +1,1 @@
+nose

=== modified file 'run-tests'
--- run-tests	2013-11-25 02:37:46 +0000
+++ run-tests	2014-03-10 01:04:59 +0000
@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 # -----------------------------------------------------------------------------
 # Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 - Lionel Dricot & Bertrand Rousseau
+# Copyright (c) 2008-2014 - 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
@@ -18,81 +18,13 @@
 # this program.  If not, see <http://www.gnu.org/licenses/>.
 # -----------------------------------------------------------------------------
 
-"""Runs the GTG unit tests."""
-
 import sys
-import unittest
-
-from GTG.tests import test_suite
-from GTG.tools.testingmode import TestingMode
-from GTG.tools.import_liblarch import import_liblarch
-
-TEST_MODULE_PREFIX = "GTG.tests."
-
-def main(args):
-    runner = unittest.TextTestRunner(
-                    stream = sys.stdout,
-                    descriptions = True,
-                    verbosity = 1)
-    if "-h" in args:
-        print("USAGE:")
-        print("./run_tests  to run all tests")
-        print("./run_tests  [module_name ..] to run tests from the listed " + \
-              "modules")
-        print("./run_tests  [function_path ..] to run the selected " + \
-              "functions as tests")
-        return
-    if "-l" in args:
-        args.remove("-l")
-    # fake modules that mimic the behaviour of external libraries ...
-    TestingMode().set_testing_mode(True)
-    if args:
-        #if we have specified the name of the test in the command line
-        suites = []
-        for arg in args:
-            #each arg can be a module name (as test_liblarch), or a module name
-            # "dot" a function name (as test_liblarch.test_something)
-            arg_content = arg.split(".")
-            module_name = arg_content[0]
-            #load the module
-            module_path = TEST_MODULE_PREFIX + module_name
-            module = __import__(module_path)
-            sys.modules[module_path] = module
-            globals()[module_path] = module
-            tests = getattr(module, "tests")
-            a_test = getattr(tests, module_name)
-            if len(arg_content) > 1:
-                path = TEST_MODULE_PREFIX + arg
-                suites.append(unittest.TestLoader().loadTestsFromName(path))
-                #NOTE: this can be done easily with pattern matching with 
-                #      unittest.TestLoader().discover(..), but that's only from 
-                #      python 2.7 on. 
-            else:
-                suites.append(getattr(a_test, "test_suite")())
-    else:
-        #otherwise, run all tests
-        suites = [test_suite()]
-
-    #run the tests
-    tests_are_successful = True
-    for suite in suites:
-        result = runner.run(suite)
-        tests_are_successful &= result.wasSuccessful()
-
-    #show the aggregated result of all the tests
-    if tests_are_successful:
-        print("ALL TESTS ARE SUCCESSFUL")
-        return 0
-    else:
-        print("""
-        ****************************************
-        SOME TESTS *FAILED*!
-        ****************************************
-        """)
-        return 1
-
-if __name__ == '__main__':
-    use_local = "-l" in sys.argv[1:] or "--local-liblarch" in sys.argv[1:]
-
-    if import_liblarch(use_local):
-        sys.exit(main(sys.argv[1:]))
+import nose
+
+if __name__ == "__main__":
+    # By default, use spec plugin on tests folder
+    if len(sys.argv) == 1:
+        sys.argv.append('tests')
+        sys.argv.append('--with-specplugin')
+
+    nose.main()

=== removed file 'scripts/build_integrity.py'
--- scripts/build_integrity.py	2013-08-28 20:13:59 +0000
+++ scripts/build_integrity.py	1970-01-01 00:00:00 +0000
@@ -1,69 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: UTF-8 -*-
-# Copyright © 2012 Izidor Matušov <izidor.matusov@xxxxxxxxx
-#
-# 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/>.
-
-import os
-import sys
-import re
-
-exclude_list = ['data/.*', 'po/.*', 'doc/.*', 'AUTHORS', 'CHANGELOG',
-                'LICENSE', 'README', 'gtcli_bash_completion', 'gtg.desktop',
-                'org.gnome.GTG.service', 'setup.py',
-                ]
-
-# Build MANIFEST and also run build action
-if os.system("python setup.py sdist > /dev/null") != 0:
-    print("sdist operation failed")
-    sys.exit(1)
-
-if os.system("python setup.py build > /dev/null") != 0:
-    print("build operation failed")
-    sys.exit(1)
-
-manifest_files = []
-
-for f in open('MANIFEST', 'r'):
-    f = f.strip()
-    if f == "" or f.startswith('#'):
-        continue
-    f = os.path.normpath(f)
-
-    exclude = False
-    for ex in exclude_list:
-        if re.match(ex, f):
-            exclude = True
-            break
-    if exclude:
-        continue
-
-    manifest_files.append(f)
-
-build_files = []
-for root, dirs, files in os.walk('build/'):
-    for f in files:
-        filename = os.path.join(root, f)
-        filename = filename.split('/', 1)[1]
-        if filename.startswith('lib.') or filename.startswith('scripts-'):
-            filename = filename.split('/', 1)[1]
-
-        build_files.append(filename)
-
-missing_files = list(set(manifest_files) - set(build_files))
-if len(missing_files) > 0:
-    missing_files.sort()
-    print("Missing build files:")
-    print("\n".join("\t%s" % f for f in missing_files))
-    sys.exit(1)

=== modified file 'scripts/debug.sh'
--- scripts/debug.sh	2012-06-07 14:00:34 +0000
+++ scripts/debug.sh	2014-03-10 01:04:59 +0000
@@ -9,26 +9,20 @@
 args="--no-crash-handler"
 dataset="default"
 norun=0
-profile=0
 title=""
 
 # Create execution-time data directory if needed
 mkdir -p tmp
 
 # Interpret arguments
-while getopts bdlnps: o
+while getopts bdns: o
 do  case "$o" in
     b)   args="$args --boot-test";;
     d)   args="$args -d";;
-    # Request usage local liblarch if it is possible
-    l)   args="$args -l"
-         liblarchArgs="$liblarchArgs -l"
-        ;;
     n)   norun=1;;
-    p)   profile=1;;
     s)   dataset="$OPTARG";;
     t)   title="$OPTARG";;
-    [?]) echo >&2 "Usage: $0 [-s dataset] [-t title] [-b] [-d] [-l] [-n] [-p]"
+    [?]) echo >&2 "Usage: $0 [-s dataset] [-t title] [-b] [-d] [-l] [-n]"
          exit 1;;
     esac
 done
@@ -37,7 +31,7 @@
 if [  $dataset != "default" -a ! -d "./tmp/$dataset" ]
 then
     echo "Copying $dataset dataset to ./tmp/"
-    cp -r test/data/$dataset tmp/
+    cp -r data/test-data/$dataset tmp/
 fi
 
 echo "Setting XDG vars to use $dataset dataset."
@@ -57,23 +51,5 @@
 fi
 
 if [ $norun -eq 0 ]; then
-    # Check for liblarch
-    if ! ./GTG/tools/import_liblarch.py $liblarchArgs; then
-        echo
-        echo -n "Download latest liblarch? [y/N] "
-        read answer
-        if [ "$answer" = "y" -o "$answer" = "Y" -o "$answer" = "yes" ]; then
-            git clone https://github.com/liblarch/liblarch ../liblarch
-        else
-            exit 1
-        fi
-    fi
-
-    if [ $profile -eq 1 ]; then
-        python -m cProfile -o gtg.prof ./gtg $args -t "$title"
-        python ./scripts/profile_interpret.sh
-    else
-	./gtg $args -t "$title"
-    fi
+    ./GTG/gtg $args -t "$title"
 fi
-

=== modified file 'scripts/generate_mofile.sh'
--- scripts/generate_mofile.sh	2011-11-28 21:43:47 +0000
+++ scripts/generate_mofile.sh	2014-03-10 01:04:59 +0000
@@ -3,6 +3,6 @@
 LANGUAGES=$(ls po/*.po | sed 's/po\/\(.*\).po/\1/')
 
 for i in $LANGUAGES; do
-	mkdir po/$i/LC_MESSAGES/ --parents
-	msgfmt po/$i.po --output-file=po/$i/LC_MESSAGES/gtg.mo
+    mkdir po/$i/LC_MESSAGES/ --parents
+    msgfmt po/$i.po --output-file=po/$i/LC_MESSAGES/gtg.mo
 done

=== removed file 'scripts/man_page_viewer'
--- scripts/man_page_viewer	2010-01-05 09:34:49 +0000
+++ scripts/man_page_viewer	1970-01-01 00:00:00 +0000
@@ -1,12 +0,0 @@
-#!/bin/bash
-
-if [ ! $# -eq 1 ]; then 
-    echo "usage: $0 <man_file_path>"
-    exit 1
-fi
-if [ ! -x "`which groff`" ]; then
-    echo "please install groff"
-    exit 2
-fi
-
-groff -Tascii -man $1 | more

=== removed file 'scripts/profile_interpret.sh'
--- scripts/profile_interpret.sh	2013-08-28 20:13:59 +0000
+++ scripts/profile_interpret.sh	1970-01-01 00:00:00 +0000
@@ -1,4 +0,0 @@
-#!/usr/bin/env python3
-import pstats
-p = pstats.Stats('gtg.prof')
-p.strip_dirs().sort_stats("cumulative").print_stats(20)

=== modified file 'setup.py'
--- setup.py	2014-02-01 07:53:21 +0000
+++ setup.py	2014-03-10 01:04:59 +0000
@@ -18,222 +18,174 @@
 # this program.  If not, see <http://www.gnu.org/licenses/>.
 # -----------------------------------------------------------------------------
 
-from distutils.core     import setup
-from distutils.command.install_data import install_data
+from distutils.core import setup
+from glob import glob
 from subprocess import call
-
-import glob
 import os
+import sys
 
 from GTG import info
 
-### CONSTANTS ################################################################
-
-HELP_DIR = "share/help"
-GLOBAL_ICON_DIR = "share/icons"
-
-### TOOLS ####################################################################
-
-def create_icon_list():
-    fileList = []
-    rootdir = "data/icons"
-    for root, subFolders, files in os.walk(rootdir):
-        dirList = []
-        for file in files:
-            if file.endswith(".png") or file.endswith(".svg"):
-                dirList.append(os.path.join(root, file))
-        if len(dirList)!=0:
-            newroot = root.replace(rootdir + "/", "")
-            fileList.append((os.path.join(GLOBAL_ICON_DIR, newroot), dirList))
-    return fileList
-
-
-def create_userdoc_list():
-    fileList = []
-    rootdir = "doc/userdoc"
-    for root, subFolders, files in os.walk(rootdir):
-        dirList = []
-        for file in files:
-            dirList.append(os.path.join(root, file))
-        if len(dirList)!=0:
-            comps = root.split(os.sep)
-            prefix = os.path.join(comps[0], comps[1], comps[2])+os.sep
-            if root != prefix[:-1]:
-                newroot = root.replace(prefix, "")
+
+def find_packages():
+    """ Generate list of all packages """
+    packages = []
+    for package, __, files in os.walk('GTG'):
+        # Package has to have init file
+        if '__init__.py' not in files:
+            continue
+        # Convert filepath to package name
+        package = package.replace(os.path.sep, '.')
+        packages.append(package)
+    return packages
+
+
+def find_package_data():
+    """ Generate list of data files within a package """
+    packages = {
+        package.replace('.', os.path.sep) for package in find_packages()}
+    package_data = {}
+
+    for folder, __, files in os.walk('GTG'):
+        # Find package
+        closest_package = folder
+        while closest_package and closest_package not in packages:
+            # Try one level up
+            closest_package = os.path.dirname(closest_package)
+
+        if not closest_package:
+            continue
+
+        allowed_extensions = [
+            '', '.gtg-plugin', '.png', '.svg', '.ui', '.html', '.tex', '.txt']
+        is_this_package = folder == closest_package
+        if not is_this_package:
+            allowed_extensions.append('.py')
+
+        for filename in files:
+            ext = os.path.splitext(filename)[-1]
+            if ext not in allowed_extensions:
+                continue
+
+            # Find path relative to package
+            filename = os.path.join(folder, filename)
+            assert filename.startswith(closest_package)
+            filename = filename[len(closest_package + os.path.sep):]
+
+            # Assign data file to package name
+            package_name = closest_package.replace(os.path.sep, '.')
+            if package_name in package_data:
+                package_data[package_name].append(filename)
             else:
-                newroot = ""
-            newroot = os.path.join(HELP_DIR, comps[2], "gtg", newroot)
-            fileList.append((newroot, dirList))
-    fileList.append('doc/release_notes/0.3.1.txt')
-    return fileList
-
-
-def create_data_files():
+                package_data[package_name] = [filename]
+
+    return package_data
+
+
+def compile_mo_files():
+    """ Compile all .po files into .mo files """
+    mo_files = []
+    mo_dir = os.path.join('build', 'po')
+    for po_file in glob('po/*.po'):
+        lang = os.path.splitext(os.path.basename(po_file))[0]
+        mo_file = os.path.join(mo_dir, lang, 'gtg.mo')
+        target_dir = os.path.dirname(mo_file)
+        if not os.path.isdir(target_dir):
+            os.makedirs(target_dir)
+
+        try:
+            return_code = call(['msgfmt', '-o', mo_file, po_file])
+        except OSError:
+            sys.stderr.write(
+                'Translation not available, please install gettext\n')
+            break
+
+        if return_code:
+            raise Warning('Error when building locales')
+            continue
+
+        install_folder = os.path.join('share', 'locale', lang, 'LC_MESSAGES')
+        mo_files.append((install_folder, [mo_file]))
+
+    return mo_files
+
+
+def find_icons(src_folder, dest_folder, allowed_extensions):
+    """ Find all icons in the folder """
+    data_list = []
+
+    for folder, __, files in os.walk(src_folder):
+        assert folder.startswith(src_folder)
+        install_folder = dest_folder + folder[len(src_folder):]
+        file_list = []
+        for filename in files:
+            ext = os.path.splitext(filename)[-1]
+            if ext in allowed_extensions:
+                filename = os.path.join(folder, filename)
+                file_list.append(filename)
+
+        if file_list:
+            data_list.append((install_folder, file_list))
+
+    return data_list
+
+
+def find_user_help():
+    """ Find all files for user help """
+    help_files = []
+
+    for folder, __, files in os.walk('docs/userdoc'):
+        folders = folder.split(os.path.sep)[2:]
+        if not folders:
+            continue
+        folders.insert(1, 'gtg')
+        install_folder = os.path.join('share', 'help', *folders)
+
+        help_files.append((
+            install_folder,
+            [os.path.join(folder, filename) for filename in files],
+        ))
+
+    return help_files
+
+
+def find_data_files():
+    """ Generate list of data files for installing in share folder """
     data_files = []
-    # icons
-    icons = create_icon_list()
-    data_files.extend(icons)
-    # gtg .desktop icon
-    data_files.append(('share/icons/hicolor/16x16/apps',
-                       ['data/icons/hicolor/16x16/apps/gtg.png']))
-    data_files.append(('share/icons/hicolor/22x22/apps',
-                       ['data/icons/hicolor/22x22/apps/gtg.png']))
-    data_files.append(('share/icons/hicolor/24x24/apps',
-                       ['data/icons/hicolor/24x24/apps/gtg.png']))
-    data_files.append(('share/icons/hicolor/32x32/apps',
-                       ['data/icons/hicolor/32x32/apps/gtg.png']))
-    data_files.append(('share/icons/hicolor/scalable/apps',
-                       ['data/icons/hicolor/scalable/apps/gtg.svg']))
-    # documentation
-    helpfiles = create_userdoc_list()
-    data_files.extend(helpfiles)
-    # misc
-    data_files.append(('share/applications', ['gtg.desktop']))
-    data_files.append(('share/dbus-1/services', ['org.gnome.GTG.service']))
-    data_files.append(('share/man/man1',
-                       ['doc/gtg.1', 'doc/gtcli.1', 'doc/gtg_new_task.1']))
-
-    # bash completion
-    data_files.append(('share/gtg/', ['gtcli_bash_completion']))
-
-    # appdata file
-    data_files.append(('share/appdata/', ['gtg.appdata.xml']))
+
+    # .mo files
+    data_files.extend(compile_mo_files())
+
+    # Icons
+    data_files.extend(
+        find_icons('data/icons', 'share/icons', ['.png', '.svg']))
+
+    # User docs
+    data_files.extend(find_user_help())
+
+    # Generate man files and include them
+    os.system('sphinx-build -b man docs/source build/docs')
+    data_files.append(('share/man/man1', glob('build/docs/*.1')))
+
+    # Misc files
+    data_files.extend([
+        ('share/applications', ['data/gtg.desktop']),
+        ('share/appdata/', ['data/gtg.appdata.xml']),
+        ('share/dbus-1/services', ['data/org.gnome.GTG.service']),
+        ('share/gtg/', ['data/gtcli_bash_completion']),
+    ])
+
     return data_files
 
 
-#### TRANSLATIONS(from pyroom setup.py) ######################################
-
-PO_DIR = 'po'
-MO_DIR = os.path.join('build', 'po')
-
-for po in glob.glob(os.path.join(PO_DIR, '*.po')):
-    lang = os.path.basename(po[:-3])
-    mo = os.path.join(MO_DIR, lang, 'gtg.mo')
-    target_dir = os.path.dirname(mo)
-    if not os.path.isdir(target_dir):
-        os.makedirs(target_dir)
-    try:
-        return_code = call(['msgfmt', '-o', mo, po])
-    except OSError:
-        print('Translation not available, please install gettext')
-        break
-    if return_code:
-        raise Warning('Error when building locales')
-
-
-class InstallData(install_data):
-
-    def run(self):
-        self.data_files.extend(self.find_mo_files())
-        install_data.run(self)
-
-    def find_mo_files(self):
-        data_files = []
-        for mo in glob.glob(os.path.join(MO_DIR, '*', 'gtg.mo')):
-            lang = os.path.basename(os.path.dirname(mo))
-            dest = os.path.join('share', 'locale', lang, 'LC_MESSAGES')
-            data_files.append((dest, [mo]))
-        return data_files
-
-### SETUP SCRIPT ##############################################################
-
-author = 'The GTG Team'
-
 setup(
-  name = 'gtg',
-  version = info.VERSION,
-  url = info.URL,
-  author = author,
-  author_email = info.EMAIL,
-  description = info.SHORT_DESCRIPTION,
-  packages = [
-    'GTG',
-    'GTG.backends',
-    'GTG.backends.rtm',
-    'GTG.core',
-    'GTG.core.plugins',
-    'GTG.gtk',
-    'GTG.gtk.editor',
-    'GTG.gtk.browser',
-    'GTG.gtk.backends_dialog',
-    'GTG.gtk.backends_dialog.parameters_ui',
-    'GTG.tools',
-    'GTG.plugins',
-    'GTG.plugins.bugzilla',
-    'GTG.plugins.export',
-    'GTG.plugins.geolocalized_tasks',
-    'GTG.plugins.hamster',
-    'GTG.plugins.notification_area',
-    'GTG.plugins.task_reaper',
-    'GTG.plugins.send_email',
-    'GTG.plugins.tomboy',
-    'GTG.plugins.urgency_color',
-    'GTG.plugins.untouched_tasks',
-    'GTG.plugins.not_today',
-    ],
-  package_data = {
-    'GTG.core.plugins': ['pluginmanager.ui'],
-    'GTG.gtk': [
-        'preferences.ui',
-        'plugins.ui',
-        'deletion.ui',
-        'backends_dialog.ui',
-        ],
-    'GTG.gtk.browser': ['taskbrowser.ui', 'modifytags_dialog.ui'],
-    'GTG.gtk.editor': ['taskeditor.ui'],
-    'GTG.plugins': [
-        'bugzilla.gtg-plugin',
-        'export.gtg-plugin',
-        'geolocalized-tasks.gtg-plugin',
-        'hamster.gtg-plugin',
-        'notification-area.gtg-plugin',
-        'task-reaper.gtg-plugin',
-        'send-email.gtg-plugin',
-        'tomboy.gtg-plugin',
-        'urgency-color.gtg-plugin',
-        'not-today.gtg-plugin',
-        'untouched-tasks.gtg-plugin',
-        ],
-    'GTG.plugins.export': ['export.ui',
-                          './export_templates/description_pocketmod.py',
-                          './export_templates/description_sexy.py',
-                          './export_templates/description_simple.py',
-                          './export_templates/description_statusrpt.py',
-                          './export_templates/description_textual.py',
-                          './export_templates/graphics_pocketmod.svg',
-                          './export_templates/script_pocketmod',
-                          './export_templates/template_pocketmod.tex',
-                          './export_templates/template_sexy.html',
-                          './export_templates/template_simple.html',
-                          './export_templates/template_statusrpt.txt',
-                          './export_templates/template_textual.txt',
-                          './export_templates/thumbnail_pocketmod.png',
-                          './export_templates/thumbnail_sexy.png',
-                          './export_templates/thumbnail_simple.png',
-                          './export_templates/thumbnail_statusrpt.png',
-                          './export_templates/thumbnail_textual.png',
-                          ],
-    'GTG.plugins.geolocalized_tasks': ['geolocalized.ui',
-                          'icons/hicolor/24x24/geolocalization.png',
-                          'icons/hicolor/16x16/assign-location.png',
-                          'icons/hicolor/svg/assign-location.svg',
-                          'icons/hicolor/svg/geolocalization.svg'],
-    'GTG.plugins.tomboy': ['tomboy.ui'],
-    'GTG.plugins.hamster': ['prefs.ui',
-                            'icons/hicolor/32x32/hamster-activity-start.png',
-                            'icons/hicolor/32x32/hamster-activity-stop.png',
-                            'icons/hicolor/svg/hamster-activity-start.svg',
-                            'icons/hicolor/svg/hamster-activity-stop.svg'],
-    'GTG.plugins.task_reaper': ['reaper.ui'],
-    'GTG.plugins.notification_area': ['notification_area.ui',
-                     './data/icons/hicolor/22x22/apps/gtg_need_attention.png',
-            './data/icons/ubuntu-mono-dark/22x22/apps/gtg_need_attention.svg',
-           './data/icons/ubuntu-mono-light/22x22/apps/gtg_need_attention.svg',
-                            ],
-    'GTG.plugins.urgency_color': ['preferences.ui'],
-    'GTG.plugins.untouched_tasks': ['untouchedTasks.ui'],
-   },
-  data_files = create_data_files(),
-  scripts=['gtg', 'gtcli', 'gtg_new_task'],
-  cmdclass={'install_data': InstallData},
+    name='gtg',
+    version=info.VERSION,
+    url=info.URL,
+    author='The GTG Team',
+    author_email=info.EMAIL,
+    description=info.SHORT_DESCRIPTION,
+    packages=find_packages(),
+    scripts=['GTG/gtg', 'GTG/gtcli', 'GTG/gtg_new_task'],
+    data_files=find_data_files(),
 )

=== renamed directory 'test' => 'tests'
=== renamed file 'GTG/tests/__init__.py' => 'tests/__init__.py'
--- GTG/tests/__init__.py	2014-03-09 12:59:59 +0000
+++ tests/__init__.py	2014-03-10 01:04:59 +0000
@@ -1,60 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2013 - 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/>.
-# -----------------------------------------------------------------------------
-
-""" Unit tests for GTG. """
-
-import unittest
-import os
-import sys
-
-TEST_MODULE_PREFIX = "GTG.tests."
-
-
-def test_suite():
-    '''
-    Automatically loads all the tests in the GTG/tests directory and returns a
-    unittest.TestSuite filled with them
-    '''
-    # find all the test files
-    test_dir = os.path.dirname(__file__)
-    test_files = [
-        f for f in os.listdir(test_dir)
-        if f.endswith(".py") and f.startswith("test_")]
-
-    # Loading of the test files and adding to the TestSuite
-    test_suite = unittest.TestSuite()
-    for module_name in [f[:-3] for f in test_files]:
-            # module loading
-            module_path = TEST_MODULE_PREFIX + module_name
-            module = __import__(module_path)
-            sys.modules[module_path] = module
-            globals()[module_path] = module
-            # fetching the testsuite
-
-            # Crude hack to satisfy both GIT repository and GTG trunk
-            if TEST_MODULE_PREFIX == "GTG.tests.":
-                tests = getattr(module, "tests")
-            else:
-                tests = module
-
-            a_test = getattr(tests, module_name)
-            # adding it to the unittest.TestSuite
-            test_suite.addTest(getattr(a_test, "test_suite")())
-
-    return test_suite

=== added directory 'tests/core'
=== added file 'tests/core/__init__.py'
=== renamed file 'GTG/tests/test_search_filter.py' => 'tests/core/test_search_filter.py'
--- GTG/tests/test_search_filter.py	2013-11-23 14:40:23 +0000
+++ tests/core/test_search_filter.py	2014-03-10 01:04:59 +0000
@@ -17,16 +17,15 @@
 # this program.  If not, see <http://www.gnu.org/licenses/>.
 # -----------------------------------------------------------------------------
 
-""" Tests for search filter """
+from unittest import TestCase
 
-import unittest
 from GTG.core.search import search_filter
 from GTG.tools.dates import Date
 
 d = Date.parse
 
 
-class FakeTask:
+class FakeTask(object):
 
     def __init__(self, title="", body="", tags=[], due_date=""):
         self.title = title
@@ -47,7 +46,7 @@
         return self.due_date
 
 
-class TestSearchFilter(unittest.TestCase):
+class TestSearchFilter(TestCase):
 
     def test_empty(self):
         self.assertFalse(search_filter(FakeTask()))
@@ -207,7 +206,3 @@
                                       {'q': [("soon", True)]}))
         self.assertTrue(search_filter(FakeTask(due_date="someday"),
                                       {'q': [("someday", True)]}))
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromName(__name__)

=== renamed file 'GTG/tests/test_search_query.py' => 'tests/core/test_search_query.py'
--- GTG/tests/test_search_query.py	2014-03-09 12:59:59 +0000
+++ tests/core/test_search_query.py	2014-03-10 01:04:59 +0000
@@ -17,9 +17,7 @@
 # this program.  If not, see <http://www.gnu.org/licenses/>.
 # -----------------------------------------------------------------------------
 
-""" Tests for parsing searching query """
-
-import unittest
+from unittest import TestCase
 from GTG.core.search import parse_search_query, InvalidQuery
 from GTG.tools.dates import Date
 
@@ -27,7 +25,7 @@
 d = Date.parse
 
 
-class TestSearchQuery(unittest.TestCase):
+class TestSearchQuery(TestCase):
 
     def test_word_query(self):
         self.assertEqual(parse("query"),
@@ -222,7 +220,3 @@
                          {'q': [('today', False)]})
         self.assertEqual(parse('word !today'),
                          {'q': [('word', True, 'word'), ('today', True)]})
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromName(__name__)

=== renamed file 'GTG/tests/test_tag.py' => 'tests/core/test_tag.py'
--- GTG/tests/test_tag.py	2013-11-23 14:40:23 +0000
+++ tests/core/test_tag.py	2014-03-10 01:04:59 +0000
@@ -17,134 +17,53 @@
 # this program.  If not, see <http://www.gnu.org/licenses/>.
 # -----------------------------------------------------------------------------
 
-"""Tests for the tags"""
-
-import unittest
-
-from GTG.core.tag import Tag, Set_Name_Attribute_Error
-from GTG.core.datastore import DataStore
-
-from GTG.tests.signals_testing import GobjectSignalsManager
-
-
-class TestTag(unittest.TestCase):
-    """Tests for `Tag`."""
-
+from unittest import TestCase
+
+from GTG.core.tag import Tag
+
+
+class TestTag(TestCase):
     def setUp(self):
-        ds = DataStore()
-        self.req = ds.get_requester()
-        # initalize gobject signaling system
-        self.gobject_signal_manager = GobjectSignalsManager()
-        self.gobject_signal_manager.init_signals()
-        # refresh the viewtree for tasks
-        tt = self.req.get_tasks_tree()
-        tt.reset_filters()
-
-    def tearDown(self):
-#        finally:
-        # stopping gobject main loop
-        self.gobject_signal_manager.terminate_signals()
-
-    def test_name(self):
-        # The first argument to the Tag constructor is the name, which you can
-        # get with get_name().
-        tag = Tag('foo', self.req)
-        self.assertEqual('foo', tag.get_name())
+        self.tag = Tag('foo', None)
+        # Do not allow notifying related tasks
+        self.tag.notify_related_tasks = lambda: None
+
+    def test_has_name(self):
+        self.assertEqual('foo', self.tag.get_name())
 
     def test_name_is_attribute(self):
-        # The name of the tag is also stored as an attribute.
-        tag = Tag('foo', self.req)
-        self.assertEqual('foo', tag.get_attribute('name'))
+        self.assertEqual('foo', self.tag.get_attribute('name'))
 
     def test_missing_attribute_returns_none(self):
-        # If get_attribute is called for an attribute that doesn't exist, it
-        # returns None.
-        tag = Tag('whatever', self.req)
-        result = tag.get_attribute('no-such-attribute')
-        self.assertEqual(None, result)
+        self.assertEqual(None, self.tag.get_attribute('no-such-attribute'))
 
     def test_set_then_get_attribute(self):
-        # Attributes set with set_attribute can be retrieved with
-        # get_attribute.
-        tag = Tag('whatever', self.req)
-        tag.set_attribute('new-attribute', 'value')
-        result = tag.get_attribute('new-attribute')
-        self.assertEqual('value', result)
+        self.tag.set_attribute('new-attribute', 'value')
+        attr = self.tag.get_attribute('new-attribute')
+        self.assertEqual('value', attr)
 
     def test_set_non_str_attribute_casts_to_string(self):
-        # If the value of the attribute passed to set_attribute is not a
-        # string, it's cast to a string.
-        tag = Tag('whatever', self.req)
-        tag.set_attribute('new-attribute', 42)
-        result = tag.get_attribute('new-attribute')
-        self.assertEqual('42', result)
-
-    def test_get_all_attributes_initial(self):
-        # Initially, a Tag has only the name attribute.
-        tag = Tag('foo', self.req)
-        self.assertEqual(['name'], tag.get_all_attributes())
-
-    def test_get_all_attributes_after_setting(self):
-        # After attributes are set, get_all_attributes includes those
-        # attributes. The order is not guaranteed.
-        tag = Tag('foo', self.req)
-        tag.set_attribute('bar', 'baz')
-        self.assertEqual(set(['name', 'bar']), set(tag.get_all_attributes()))
-
-    def test_get_all_but_name(self):
-        # If 'butname' is True, then exclude the 'name' attribute.
-        tag = Tag('foo', self.req)
-        self.assertEqual([], tag.get_all_attributes(butname=True))
-        tag.set_attribute('bar', 'baz')
-        self.assertEqual(['bar'], tag.get_all_attributes(butname=True))
-
-    def test_str(self):
-        # str(tag) is 'Tag: <name>'
-        tag = Tag('foo', self.req)
-        self.assertEqual('Tag: foo', str(tag))
-
-    def test_set_name_attribute_does_nothing(self):
-        # The 'name' attribute is set by the constructor. After it is set, it
-        # cannot be changed with further calls to set_attribute.
-        tag = Tag('old', self.req)
-        try:
-            tag.set_attribute('name', 'new')
-        except Set_Name_Attribute_Error:
-            pass
-        self.assertEqual('old', tag.get_name())
-        self.assertEqual('old', tag.get_attribute('name'))
-
-    # XXX: The following tests check the current behaviour of the Tag class,
-    # but I'm not sure if they're correct behaviour. -- jml, 2009-07-17
-    def test_save_not_called_on_construction(self):
-        # The save callback isn't called by the constructor, despite the fact
-        # that it sets the name attribute.
-        save_calls = []
-        Tag('old', self.req)
-        self.assertEqual(0, len(save_calls))
-
-    def test_set_name_doesnt_call_save(self):
-        # Setting the name attribute doesn't call save.
-        save_calls = []
-        tag = Tag('old', self.req)
-        try:
-            tag.set_attribute('name', 'new')
-        except Set_Name_Attribute_Error:
-            pass
-        self.assertEqual(0, len(save_calls))
-
-    def test_intask_counting_after_rename(self):
-        '''We test that the task counting for tags work
-        even after tag renaming (stuttering tag bug)'''
-        t = self.req.new_task(tags=['@testtag'])
-        t.modified()
-        tag = self.req.get_tag('@testtag')
-        self.assertEqual(tag.get_active_tasks_count(), 1)
-        t.rename_tag('@testtag', '@test')
-        tag2 = self.req.get_tag('@test')
-        self.assertEqual(tag2.get_active_tasks_count(), 1)
-        self.assertEqual(tag.get_active_tasks_count(), 0)
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(TestTag)
+        self.tag.set_attribute('new-attribute', 42)
+        attr = self.tag.get_attribute('new-attribute')
+        self.assertEqual('42', attr)
+
+    def test_initial_attribute_is_name_only(self):
+        self.assertEqual(['name'], self.tag.get_all_attributes())
+
+    def test_can_add_new_attributes(self):
+        self.tag.set_attribute('bar', 'baz')
+        self.assertEqual({'name', 'bar'}, set(self.tag.get_all_attributes()))
+
+    def test_get_all_attributes_but_name(self):
+        self.assertEqual([], self.tag.get_all_attributes(butname=True))
+        self.tag.set_attribute('bar', 'baz')
+        self.assertEqual(['bar'], self.tag.get_all_attributes(butname=True))
+
+    def test_name_cannot_be_changed(self):
+        self.assertEqual('foo', self.tag.get_name())
+
+        with self.assertRaises(KeyError):
+            self.tag.set_attribute('name', 'new')
+
+        self.assertEqual('foo', self.tag.get_name())
+        self.assertEqual('foo', self.tag.get_attribute('name'))

=== added directory 'tests/tools'
=== added file 'tests/tools/__init__.py'
=== renamed file 'GTG/tests/test_dates.py' => 'tests/tools/test_dates.py'
--- GTG/tests/test_dates.py	2013-11-23 14:40:23 +0000
+++ tests/tools/test_dates.py	2014-03-10 01:04:59 +0000
@@ -17,11 +17,7 @@
 # this program.  If not, see <http://www.gnu.org/licenses/>.
 # -----------------------------------------------------------------------------
 
-'''
-Tests for the various Date classes
-'''
-
-import unittest
+from unittest import TestCase
 from datetime import date, timedelta
 
 from GTG import _
@@ -42,19 +38,22 @@
         return aday.replace(day=day, month=aday.month + 1)
 
 
-class TestDates(unittest.TestCase):
-    """ Tests for the various Date classes """
+class TestDates(TestCase):
 
-    def test_parse_dates(self):
-        """ Parse common numeric date """
+    def test_parses_common_formats(self):
         self.assertEqual(str(Date.parse("1985-03-29")), "1985-03-29")
         self.assertEqual(str(Date.parse("19850329")), "1985-03-29")
         self.assertEqual(str(Date.parse("1985/03/29")), "1985-03-29")
 
+    def test_parses_todays_month_day_format(self):
         today = date.today()
         parse_string = "%02d%02d" % (today.month, today.day)
         self.assertEqual(Date.parse(parse_string), today)
 
+    def test_parses_today_as_today(self):
+        today = date.today()
+        self.assertEqual(Date(today), today)
+
     def test_parse_fuzzy_dates(self):
         """ Parse fuzzy dates like now, soon, later, someday """
         self.assertEqual(Date.parse("now"), Date.now())
@@ -129,13 +128,3 @@
                 aday = aday.replace(day=i)
 
             self.assertEqual(Date.parse(str(i)), aday)
-
-    def test_prevent_regression(self):
-        """ A day represented in GTG Date must be still the same """
-        aday = date.today()
-        self.assertEqual(Date(aday), aday)
-
-
-def test_suite():
-    """ Return unittests """
-    return unittest.TestLoader().loadTestsFromTestCase(TestDates)

=== renamed file 'GTG/tests/test_interruptible.py' => 'tests/tools/test_interruptible.py'
--- GTG/tests/test_interruptible.py	2013-11-23 14:40:23 +0000
+++ tests/tools/test_interruptible.py	2014-03-10 01:04:59 +0000
@@ -17,41 +17,39 @@
 # this program.  If not, see <http://www.gnu.org/licenses/>.
 # -----------------------------------------------------------------------------
 
-""" Tests for interrupting cooperative threads """
-
 from threading import Thread, Event
+from unittest import TestCase
 import time
-import unittest
 
 from GTG.tools.interruptible import interruptible, _cancellation_point
 
 
-class TestInterruptible(unittest.TestCase):
-    """ Tests for interrupting cooperative threads """
+class TestInterruptibleDecorator(TestCase):
+
+    def setUp(self):
+        self.quit_condition = False
+        self.thread_started = Event()
+
+    @interruptible
+    def never_ending(self, cancellation_point):
+        self.thread_started.set()
+        while True:
+            time.sleep(0.01)
+            cancellation_point()
 
     def test_interruptible_decorator(self):
         """ Tests for the @interruptible decorator. """
-        self.quit_condition = False
         cancellation_point = lambda: _cancellation_point(
             lambda: self.quit_condition)
-        self.thread_started = Event()
-
-        @interruptible
-        def never_ending(cancellation_point):
-            self.thread_started.set()
-            while True:
-                time.sleep(0.1)
-                cancellation_point()
-        thread = Thread(target=never_ending, args=(cancellation_point, ))
+        thread = Thread(target=self.never_ending, args=(cancellation_point,))
         thread.start()
+
+        # Wait until thread comes to live
         self.thread_started.wait()
+
+        # Ask to it to quit within 20ms
         self.quit_condition = True
-        countdown = 10
-        while thread.is_alive() and countdown > 0:
-            time.sleep(0.1)
-            countdown -= 1
+        time.sleep(0.02)
+
+        # Thread is finished
         self.assertFalse(thread.is_alive())
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(TestInterruptible)

=== renamed file 'GTG/tests/test_networkmanager.py' => 'tests/tools/test_networkmanager.py'
--- GTG/tests/test_networkmanager.py	2013-11-23 14:40:23 +0000
+++ tests/tools/test_networkmanager.py	2014-03-10 01:04:59 +0000
@@ -17,21 +17,12 @@
 # this program.  If not, see <http://www.gnu.org/licenses/>.
 # -----------------------------------------------------------------------------
 
-""" Tests for Network Manager """
-
-import unittest
+from unittest import TestCase
 
 from GTG.tools.networkmanager import is_connection_up
 
 
-class TestNetworkManager(unittest.TestCase):
-    """ Test network manager tool code """
+class TestNetworkManager(TestCase):
 
-    def test_is_connection_up_dont_throw_exception(self):
-        """ is_connection_up() returns a boolean value and
-        don't throw any exception """
+    def test_is_connection_up_and_doesnt_throw_exception(self):
         self.assertIn(is_connection_up(), [True, False])
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromName(__name__)

=== added file 'tests/tools/test_tags.py'
--- tests/tools/test_tags.py	1970-01-01 00:00:00 +0000
+++ tests/tools/test_tags.py	2014-03-10 01:04:59 +0000
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things GNOME! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2013 - 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/>.
+# -----------------------------------------------------------------------------
+
+from unittest import TestCase
+
+from GTG.tools.tags import extract_tags_from_text, parse_tag_list
+
+
+class TestExtractTags(TestCase):
+    """ extract_tags_from_text """
+
+    def assertTags(self, text, expected_tags):
+        tag_list = extract_tags_from_text(text)
+        self.assertEqual(expected_tags, tag_list)
+
+    def test_doesnt_find_empty_tag(self):
+        self.assertTags("", [])
+
+    def test_finds_tag_at_beginning(self):
+        self.assertTags("@tag some other text", ["@tag"])
+
+    def test_finds_tag_at_end(self):
+        self.assertTags("some text ended with @endtag", ["@endtag"])
+
+    def test_ignores_emails(self):
+        self.assertTags(
+            "no @emails allowed: invernizzi.l@xxxxxxxxx", ["@emails"])
+
+    def test_ignores_diffs(self):
+        self.assertTags("no @@diff stuff", [])
+
+    def test_accepts_hypen_in_tag(self):
+        self.assertTags("@do-this-today", ["@do-this-today"])
+        self.assertTags("@con--tinuous---hypen-s", ["@con--tinuous---hypen-s"])
+
+    def test_ignores_hypen_at_end_of_tag(self):
+        self.assertTags("@hypen-at-end- some other text", ["@hypen-at-end"])
+        self.assertTags("@hypen-at-end-, with comma", ["@hypen-at-end"])
+
+    def test_accepts_dot_in_tag(self):
+        self.assertTags("text @gtg-0.3", ["@gtg-0.3"])
+
+    def test_ignores_dot_at_end_of_tag(self):
+        self.assertTags("@tag.", ["@tag"])
+
+    def test_accepts_slash_in_tag(self):
+        self.assertTags("@do/this/today", ["@do/this/today"])
+
+    def test_ignores_slash_at_end_of_tag(self):
+        self.assertTags("@slash/es/", ["@slash/es"])
+
+    def test_accepts_colon_in_tag(self):
+        self.assertTags("@my:tag", ["@my:tag"])
+
+    def ignore_colon_at_end(self):
+        self.assertTags("@:a:b:c:", ["@:a:b:c"])
+
+
+class TestParseTagList(TestCase):
+    """ parse_tag_list """
+
+    def test_parses_positive_single_tag(self):
+        self.assertEqual(parse_tag_list("tag"), [("@tag", True)])
+        self.assertEqual(parse_tag_list("@tag"), [("@tag", True)])
+
+    def test_parses_postivie_tag_list(self):
+        self.assertEqual(
+            parse_tag_list("a b c"),
+            [("@a", True), ("@b", True), ("@c", True)],
+        )
+        self.assertEqual(
+            parse_tag_list("@a @b @c"),
+            [("@a", True), ("@b", True), ("@c", True)],
+        )
+
+    def test_parses_negative_single_tag(self):
+        self.assertEqual(parse_tag_list("!tag"), [("@tag", False)])
+        self.assertEqual(parse_tag_list("!@tag"), [("@tag", False)])
+
+    def test_parses_negative_tag_list(self):
+        self.assertEqual(
+            parse_tag_list("!a !b !c"),
+            [("@a", False), ("@b", False), ("@c", False)],
+        )
+        self.assertEqual(
+            parse_tag_list("!@a !@b !@c"),
+            [("@a", False), ("@b", False), ("@c", False)],
+        )
+
+    def test_parses_mixed_tags(self):
+        self.assertEqual(
+            parse_tag_list("add !remove"),
+            [("@add", True), ("@remove", False)],
+        )
+        self.assertEqual(
+            parse_tag_list("!@remove @add"),
+            [("@remove", False), ("@add", True)],
+        )

=== renamed file 'GTG/tests/test_urlregex.py' => 'tests/tools/test_urlregex.py'
--- GTG/tests/test_urlregex.py	2013-11-23 14:40:23 +0000
+++ tests/tools/test_urlregex.py	2014-03-10 01:04:59 +0000
@@ -17,20 +17,16 @@
 # this program.  If not, see <http://www.gnu.org/licenses/>.
 # -----------------------------------------------------------------------------
 
-""" Tests for URL regex """
+from unittest import TestCase
 
-import unittest
 from GTG.tools.urlregex import match
 
 
-class TestURLRegex(unittest.TestCase):
-    """ Test extractor of URL from text """
+class TestURLRegex(TestCase):
+    """ URL Regex """
 
-    def test_anchor_amperstand(self):
-        """ Reproducer for bug #1023555 """
+    def test_allows_ampersand_in_anchor(self):
+        # Reproducer for https://bugs.launchpad.net/gtg/+bug/1023555
         url = "http://test.com/#hi&there";
-        self.assertEqual(match(url).group(0), url)
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromName(__name__)
+        matched_url = match(url).group(0)
+        self.assertEqual(url, matched_url)


Follow ups