diff --git a/scripts/parceloob b/scripts/parceloob
new file mode 100755
index 0000000000000000000000000000000000000000..7393fd826b200485d1de233236056903ef5c77ee
--- /dev/null
+++ b/scripts/parceloob
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim: ft=python et softtabstop=4 cinoptions=4 shiftwidth=4 ts=4 ai
+
+# Copyright(C) 2013 Romain Bignon
+#
+# This file is part of weboob.
+#
+# weboob is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# weboob 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with weboob. If not, see .
+
+
+from weboob.applications.parceloob import Parceloob
+
+
+if __name__ == '__main__':
+ Parceloob.run()
diff --git a/weboob/applications/parceloob/__init__.py b/weboob/applications/parceloob/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..bb9ef6cadeb730e1fa8fea315d4ec5864f13a321
--- /dev/null
+++ b/weboob/applications/parceloob/__init__.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2013 Romain Bignon
+#
+# This file is part of weboob.
+#
+# weboob is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# weboob 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with weboob. If not, see .
+
+
+from .parceloob import Parceloob
+
+__all__ = ['Parceloob']
diff --git a/weboob/applications/parceloob/parceloob.py b/weboob/applications/parceloob/parceloob.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2679473470d2817c16a1b44881a534e74fffbe8
--- /dev/null
+++ b/weboob/applications/parceloob/parceloob.py
@@ -0,0 +1,159 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2013 Romain Bignon
+#
+# This file is part of weboob.
+#
+# weboob is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# weboob 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with weboob. If not, see .
+
+
+import sys
+
+from weboob.capabilities.base import empty
+from weboob.capabilities.parcel import ICapParcel, Parcel
+from weboob.tools.application.repl import ReplApplication
+from weboob.tools.application.formatters.iformatter import IFormatter
+
+
+__all__ = ['Parceloob']
+
+
+STATUS = {Parcel.STATUS_PLANNED: ('PLANNED', 'red'),
+ Parcel.STATUS_IN_TRANSIT: ('IN TRANSIT', 'yellow'),
+ Parcel.STATUS_ARRIVED: ('ARRIVED', 'green'),
+ Parcel.STATUS_UNKNOWN: ('', 'white'),
+ }
+
+class HistoryFormatter(IFormatter):
+ MANDATORY_FIELDS = ()
+
+ def format_obj(self, obj, alias):
+ if isinstance(obj, Parcel):
+ result = u'Parcel %s (%s)\n' % (self.colored(obj.id, 'red', 'bold'),
+ self.colored(obj.backend, 'blue', 'bold'))
+ result += u'%sArrival:%s %s\n' % (self.BOLD, self.NC, obj.arrival)
+ status, status_color = STATUS[obj.status]
+ result += u'%sStatus:%s %s\n' % (self.BOLD, self.NC, self.colored(status, status_color))
+ result += u'%sInfo:%s %s\n\n' % (self.BOLD, self.NC, obj.info)
+ result += u' Date Location Activity \n'
+ result += u'---------------------+-----------------+---------------------------------------------------\n'
+ return result
+
+ return ' %s %s %s' % (self.colored('%-19s' % obj.date, 'blue'),
+ self.colored('%-17s' % obj.location, 'magenta'),
+ self.colored(obj.activity, 'yellow'))
+
+class StatusFormatter(IFormatter):
+ MANDATORY_FIELDS = ('id',)
+
+ def format_obj(self, obj, alias):
+ if alias is not None:
+ id = '%s (%s)' % (self.colored('%3s' % ('#' + alias), 'red', 'bold'),
+ self.colored(obj.backend, 'blue', 'bold'))
+ clean = '#%s (%s)' % (alias, obj.backend)
+ if len(clean) < 15:
+ id += (' ' * (15 - len(clean)))
+ else:
+ id = self.colored('%30s' % obj.fullid, 'red', 'bold')
+
+ status, status_color = STATUS[obj.status]
+ arrival = obj.arrival.strftime('%Y-%m-%d') if not empty(obj.arrival) else ''
+ result = u'%s %s %s %s %s' % (id, self.colored(u'—', 'cyan'),
+ self.colored('%-10s' % status, status_color),
+ self.colored('%-10s' % arrival, 'blue'),
+ self.colored('%-20s' % obj.info, 'yellow'))
+
+ return result
+
+
+class Parceloob(ReplApplication):
+ APPNAME = 'parceloob'
+ VERSION = '0.g'
+ COPYRIGHT = 'Copyright(C) 2013 Romain Bignon'
+ CAPS = ICapParcel
+ DESCRIPTION = "Console application to track your parcels."
+ SHORT_DESCRIPTION = "manage your parcels"
+ EXTRA_FORMATTERS = {'status': StatusFormatter,
+ 'history': HistoryFormatter,
+ }
+ DEFAULT_FORMATTER = 'table'
+ COMMANDS_FORMATTERS = {'status': 'status',
+ 'info': 'history',
+ }
+ STORAGE = {'tracking': []}
+
+ def do_track(self, line):
+ """
+ track ID
+
+ Track a parcel.
+ """
+ parcel = self.get_object(line, 'get_parcel_tracking')
+ if not parcel:
+ print >>sys.stderr, 'Error: the parcel "%s" is not found' % line
+ return 2
+
+ parcels = set(self.storage.get('tracking', default=[]))
+ parcels.add(parcel.fullid)
+ self.storage.set('tracking', list(parcels))
+ self.storage.save()
+
+ print 'Parcel "%s" has been tracked.' % parcel.fullid
+
+ def do_untrack(self, line):
+ """
+ untrack ID
+
+ Stop tracking a parcel.
+ """
+ parcel = self.get_object(line, 'get_parcel_tracking')
+ if not parcel:
+ print >>sys.stderr, 'Error: the parcel "%s" is not found' % line
+ return 2
+
+ parcels = set(self.storage.get('tracking', default=[]))
+ try:
+ parcels.remove(parcel.fullid)
+ except KeyError:
+ print >>sys.stderr, "Error: parcel \"%s\" wasn't tracked" % parcel.fullid
+ return 2
+
+ self.storage.set('tracking', list(parcels))
+ self.storage.save()
+
+ print "Parcel \"%s\" isn't tracked anymore." % parcel.fullid
+
+ def do_status(self, line):
+ """
+ status
+
+ Display status for all of the tracked parcels.
+ """
+ self.start_format()
+ for id in self.storage.get('tracking', default=[]):
+ p = self.get_object(id, 'get_parcel_tracking')
+ if p is None:
+ continue
+ self.cached_format(p)
+
+ def do_info(self, id):
+ parcel = self.get_object(id, 'get_parcel_tracking', [])
+ if not parcel:
+ print >>sys.stderr, 'Error: parcel not found'
+ return 2
+
+ self.start_format()
+ self.format(parcel)
+ for event in parcel.history:
+ self.format(event)