changeset 2562:26edcf3a30eb

core, setup: huge cleaning: - moved directories from src and frontends/src to sat and sat_frontends, which is the recommanded naming convention - move twisted directory to root - removed all hacks from setup.py, and added missing dependencies, it is now clean - use https URL for website in setup.py - removed "Environment :: X11 Applications :: GTK", as wix is deprecated and removed - renamed sat.sh to sat and fixed its installation - added python_requires to specify Python version needed - replaced glib2reactor which use deprecated code by gtk3reactor sat can now be installed directly from virtualenv without using --system-site-packages anymore \o/
author Goffi <goffi@goffi.org>
date Mon, 02 Apr 2018 19:44:50 +0200
parents bd30dc3ffe5a
children 9af003a8cb99
files MANIFEST.in bin/sat frontends/src/__init__.py frontends/src/bridge/__init__.py frontends/src/bridge/bridge_frontend.py frontends/src/bridge/dbus_bridge.py frontends/src/jp/__init__.py frontends/src/jp/arg_tools.py frontends/src/jp/base.py frontends/src/jp/cmd_account.py frontends/src/jp/cmd_adhoc.py frontends/src/jp/cmd_avatar.py frontends/src/jp/cmd_blog.py frontends/src/jp/cmd_bookmarks.py frontends/src/jp/cmd_debug.py frontends/src/jp/cmd_event.py frontends/src/jp/cmd_file.py frontends/src/jp/cmd_forums.py frontends/src/jp/cmd_identity.py frontends/src/jp/cmd_info.py frontends/src/jp/cmd_input.py frontends/src/jp/cmd_invitation.py frontends/src/jp/cmd_merge_request.py frontends/src/jp/cmd_message.py frontends/src/jp/cmd_param.py frontends/src/jp/cmd_pipe.py frontends/src/jp/cmd_profile.py frontends/src/jp/cmd_pubsub.py frontends/src/jp/cmd_roster.py frontends/src/jp/cmd_shell.py frontends/src/jp/cmd_ticket.py frontends/src/jp/cmd_uri.py frontends/src/jp/common.py frontends/src/jp/constants.py frontends/src/jp/jp frontends/src/jp/output_std.py frontends/src/jp/output_template.py frontends/src/jp/output_xml.py frontends/src/jp/xmlui_manager.py frontends/src/primitivus/__init__.py frontends/src/primitivus/chat.py frontends/src/primitivus/config.py frontends/src/primitivus/constants.py frontends/src/primitivus/contact_list.py frontends/src/primitivus/game_tarot.py frontends/src/primitivus/keys.py frontends/src/primitivus/notify.py frontends/src/primitivus/primitivus frontends/src/primitivus/profile_manager.py frontends/src/primitivus/progress.py frontends/src/primitivus/status.py frontends/src/primitivus/widget.py frontends/src/primitivus/xmlui.py frontends/src/quick_frontend/__init__.py frontends/src/quick_frontend/constants.py frontends/src/quick_frontend/quick_app.py frontends/src/quick_frontend/quick_blog.py frontends/src/quick_frontend/quick_chat.py frontends/src/quick_frontend/quick_contact_list.py frontends/src/quick_frontend/quick_contact_management.py frontends/src/quick_frontend/quick_game_tarot.py frontends/src/quick_frontend/quick_games.py frontends/src/quick_frontend/quick_list_manager.py frontends/src/quick_frontend/quick_menus.py frontends/src/quick_frontend/quick_profile_manager.py frontends/src/quick_frontend/quick_utils.py frontends/src/quick_frontend/quick_widgets.py frontends/src/tools/__init__.py frontends/src/tools/composition.py frontends/src/tools/css_color.py frontends/src/tools/games.py frontends/src/tools/host_listener.py frontends/src/tools/jid.py frontends/src/tools/misc.py frontends/src/tools/strings.py frontends/src/tools/xmltools.py frontends/src/tools/xmlui.py sat/__init__.py sat/bridge/__init__.py sat/bridge/bridge_constructor/__init__.py sat/bridge/bridge_constructor/base_constructor.py sat/bridge/bridge_constructor/bridge_constructor.py sat/bridge/bridge_constructor/bridge_template.ini sat/bridge/bridge_constructor/constants.py sat/bridge/bridge_constructor/constructors/__init__.py sat/bridge/bridge_constructor/constructors/dbus-xml/__init__.py sat/bridge/bridge_constructor/constructors/dbus-xml/constructor.py sat/bridge/bridge_constructor/constructors/dbus-xml/dbus_xml_template.xml sat/bridge/bridge_constructor/constructors/dbus/__init__.py sat/bridge/bridge_constructor/constructors/dbus/constructor.py sat/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py sat/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py sat/bridge/bridge_constructor/constructors/embedded/__init__.py sat/bridge/bridge_constructor/constructors/embedded/constructor.py sat/bridge/bridge_constructor/constructors/embedded/embedded_frontend_template.py sat/bridge/bridge_constructor/constructors/embedded/embedded_template.py sat/bridge/bridge_constructor/constructors/mediawiki/__init__.py sat/bridge/bridge_constructor/constructors/mediawiki/constructor.py sat/bridge/bridge_constructor/constructors/mediawiki/mediawiki_template.tpl sat/bridge/bridge_constructor/constructors/pb/__init__.py sat/bridge/bridge_constructor/constructors/pb/constructor.py sat/bridge/bridge_constructor/constructors/pb/pb_core_template.py sat/bridge/bridge_constructor/constructors/pb/pb_frontend_template.py sat/bridge/dbus_bridge.py sat/core/__init__.py sat/core/constants.py sat/core/exceptions.py sat/core/i18n.py sat/core/log.py sat/core/log_config.py sat/core/sat_main.py sat/core/xmpp.py sat/memory/__init__.py sat/memory/cache.py sat/memory/crypto.py sat/memory/disco.py sat/memory/memory.py sat/memory/params.py sat/memory/persistent.py sat/memory/sqlite.py sat/plugins/__init__.py sat/plugins/plugin_adhoc_dbus.py sat/plugins/plugin_blog_import.py sat/plugins/plugin_blog_import_dokuwiki.py sat/plugins/plugin_blog_import_dotclear.py sat/plugins/plugin_comp_file_sharing.py sat/plugins/plugin_exp_command_export.py sat/plugins/plugin_exp_events.py sat/plugins/plugin_exp_jingle_stream.py sat/plugins/plugin_exp_lang_detect.py sat/plugins/plugin_exp_parrot.py sat/plugins/plugin_exp_pubsub_hook.py sat/plugins/plugin_exp_pubsub_schema.py sat/plugins/plugin_import.py sat/plugins/plugin_merge_req_mercurial.py sat/plugins/plugin_misc_account.py sat/plugins/plugin_misc_android.py sat/plugins/plugin_misc_debug.py sat/plugins/plugin_misc_extra_pep.py sat/plugins/plugin_misc_file.py sat/plugins/plugin_misc_forums.py sat/plugins/plugin_misc_groupblog.py sat/plugins/plugin_misc_identity.py sat/plugins/plugin_misc_imap.py sat/plugins/plugin_misc_invitations.py sat/plugins/plugin_misc_ip.py sat/plugins/plugin_misc_maildir.py sat/plugins/plugin_misc_merge_requests.py sat/plugins/plugin_misc_nat-port.py sat/plugins/plugin_misc_quiz.py sat/plugins/plugin_misc_radiocol.py sat/plugins/plugin_misc_register_account.py sat/plugins/plugin_misc_room_game.py sat/plugins/plugin_misc_smtp.py sat/plugins/plugin_misc_static_blog.py sat/plugins/plugin_misc_tarot.py sat/plugins/plugin_misc_text_commands.py sat/plugins/plugin_misc_text_syntaxes.py sat/plugins/plugin_misc_tickets.py sat/plugins/plugin_misc_upload.py sat/plugins/plugin_misc_uri_finder.py sat/plugins/plugin_misc_watched.py sat/plugins/plugin_misc_welcome.py sat/plugins/plugin_misc_xmllog.py sat/plugins/plugin_sec_otr.py sat/plugins/plugin_syntax_wiki_dotclear.py sat/plugins/plugin_tickets_import.py sat/plugins/plugin_tickets_import_bugzilla.py sat/plugins/plugin_tmp_directory_subscription.py sat/plugins/plugin_xep_0020.py sat/plugins/plugin_xep_0033.py sat/plugins/plugin_xep_0045.py sat/plugins/plugin_xep_0047.py sat/plugins/plugin_xep_0048.py sat/plugins/plugin_xep_0049.py sat/plugins/plugin_xep_0050.py sat/plugins/plugin_xep_0054.py sat/plugins/plugin_xep_0055.py sat/plugins/plugin_xep_0059.py sat/plugins/plugin_xep_0060.py sat/plugins/plugin_xep_0065.py sat/plugins/plugin_xep_0070.py sat/plugins/plugin_xep_0071.py sat/plugins/plugin_xep_0077.py sat/plugins/plugin_xep_0085.py sat/plugins/plugin_xep_0092.py sat/plugins/plugin_xep_0095.py sat/plugins/plugin_xep_0096.py sat/plugins/plugin_xep_0100.py sat/plugins/plugin_xep_0115.py sat/plugins/plugin_xep_0163.py sat/plugins/plugin_xep_0166.py sat/plugins/plugin_xep_0184.py sat/plugins/plugin_xep_0203.py sat/plugins/plugin_xep_0231.py sat/plugins/plugin_xep_0234.py sat/plugins/plugin_xep_0249.py sat/plugins/plugin_xep_0260.py sat/plugins/plugin_xep_0261.py sat/plugins/plugin_xep_0264.py sat/plugins/plugin_xep_0277.py sat/plugins/plugin_xep_0280.py sat/plugins/plugin_xep_0297.py sat/plugins/plugin_xep_0300.py sat/plugins/plugin_xep_0313.py sat/plugins/plugin_xep_0329.py sat/plugins/plugin_xep_0334.py sat/plugins/plugin_xep_0363.py sat/stdui/__init__.py sat/stdui/ui_contact_list.py sat/stdui/ui_profile_manager.py sat/test/__init__.py sat/test/constants.py sat/test/helpers.py sat/test/helpers_plugins.py sat/test/test_core_xmpp.py sat/test/test_helpers_plugins.py sat/test/test_memory.py sat/test/test_memory_crypto.py sat/test/test_plugin_misc_groupblog.py sat/test/test_plugin_misc_radiocol.py sat/test/test_plugin_misc_room_game.py sat/test/test_plugin_misc_text_syntaxes.py sat/test/test_plugin_xep_0033.py sat/test/test_plugin_xep_0085.py sat/test/test_plugin_xep_0203.py sat/test/test_plugin_xep_0277.py sat/test/test_plugin_xep_0297.py sat/test/test_plugin_xep_0313.py sat/test/test_plugin_xep_0334.py sat/tools/__init__.py sat/tools/common/__init__.py sat/tools/common/ansi.py sat/tools/common/data_format.py sat/tools/common/data_objects.py sat/tools/common/dynamic_import.py sat/tools/common/regex.py sat/tools/common/template.py sat/tools/common/template_xmlui.py sat/tools/common/uri.py sat/tools/config.py sat/tools/email.py sat/tools/sat_defer.py sat/tools/stream.py sat/tools/trigger.py sat/tools/utils.py sat/tools/xml_tools.py sat_frontends/__init__.py sat_frontends/bridge/__init__.py sat_frontends/bridge/bridge_frontend.py sat_frontends/bridge/dbus_bridge.py sat_frontends/jp/__init__.py sat_frontends/jp/arg_tools.py sat_frontends/jp/base.py sat_frontends/jp/cmd_account.py sat_frontends/jp/cmd_adhoc.py sat_frontends/jp/cmd_avatar.py sat_frontends/jp/cmd_blog.py sat_frontends/jp/cmd_bookmarks.py sat_frontends/jp/cmd_debug.py sat_frontends/jp/cmd_event.py sat_frontends/jp/cmd_file.py sat_frontends/jp/cmd_forums.py sat_frontends/jp/cmd_identity.py sat_frontends/jp/cmd_info.py sat_frontends/jp/cmd_input.py sat_frontends/jp/cmd_invitation.py sat_frontends/jp/cmd_merge_request.py sat_frontends/jp/cmd_message.py sat_frontends/jp/cmd_param.py sat_frontends/jp/cmd_pipe.py sat_frontends/jp/cmd_profile.py sat_frontends/jp/cmd_pubsub.py sat_frontends/jp/cmd_roster.py sat_frontends/jp/cmd_shell.py sat_frontends/jp/cmd_ticket.py sat_frontends/jp/cmd_uri.py sat_frontends/jp/common.py sat_frontends/jp/constants.py sat_frontends/jp/jp sat_frontends/jp/output_std.py sat_frontends/jp/output_template.py sat_frontends/jp/output_xml.py sat_frontends/jp/xmlui_manager.py sat_frontends/primitivus/__init__.py sat_frontends/primitivus/chat.py sat_frontends/primitivus/config.py sat_frontends/primitivus/constants.py sat_frontends/primitivus/contact_list.py sat_frontends/primitivus/game_tarot.py sat_frontends/primitivus/keys.py sat_frontends/primitivus/notify.py sat_frontends/primitivus/primitivus sat_frontends/primitivus/profile_manager.py sat_frontends/primitivus/progress.py sat_frontends/primitivus/status.py sat_frontends/primitivus/widget.py sat_frontends/primitivus/xmlui.py sat_frontends/quick_frontend/__init__.py sat_frontends/quick_frontend/constants.py sat_frontends/quick_frontend/quick_app.py sat_frontends/quick_frontend/quick_blog.py sat_frontends/quick_frontend/quick_chat.py sat_frontends/quick_frontend/quick_contact_list.py sat_frontends/quick_frontend/quick_contact_management.py sat_frontends/quick_frontend/quick_game_tarot.py sat_frontends/quick_frontend/quick_games.py sat_frontends/quick_frontend/quick_list_manager.py sat_frontends/quick_frontend/quick_menus.py sat_frontends/quick_frontend/quick_profile_manager.py sat_frontends/quick_frontend/quick_utils.py sat_frontends/quick_frontend/quick_widgets.py sat_frontends/tools/__init__.py sat_frontends/tools/composition.py sat_frontends/tools/css_color.py sat_frontends/tools/games.py sat_frontends/tools/host_listener.py sat_frontends/tools/jid.py sat_frontends/tools/misc.py sat_frontends/tools/strings.py sat_frontends/tools/xmltools.py sat_frontends/tools/xmlui.py setup.py src/__init__.py src/bridge/__init__.py src/bridge/bridge_constructor/__init__.py src/bridge/bridge_constructor/base_constructor.py src/bridge/bridge_constructor/bridge_constructor.py src/bridge/bridge_constructor/bridge_template.ini src/bridge/bridge_constructor/constants.py src/bridge/bridge_constructor/constructors/__init__.py src/bridge/bridge_constructor/constructors/dbus-xml/__init__.py src/bridge/bridge_constructor/constructors/dbus-xml/constructor.py src/bridge/bridge_constructor/constructors/dbus-xml/dbus_xml_template.xml src/bridge/bridge_constructor/constructors/dbus/__init__.py src/bridge/bridge_constructor/constructors/dbus/constructor.py src/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py src/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py src/bridge/bridge_constructor/constructors/embedded/__init__.py src/bridge/bridge_constructor/constructors/embedded/constructor.py src/bridge/bridge_constructor/constructors/embedded/embedded_frontend_template.py src/bridge/bridge_constructor/constructors/embedded/embedded_template.py src/bridge/bridge_constructor/constructors/mediawiki/__init__.py src/bridge/bridge_constructor/constructors/mediawiki/constructor.py src/bridge/bridge_constructor/constructors/mediawiki/mediawiki_template.tpl src/bridge/bridge_constructor/constructors/pb/__init__.py src/bridge/bridge_constructor/constructors/pb/constructor.py src/bridge/bridge_constructor/constructors/pb/pb_core_template.py src/bridge/bridge_constructor/constructors/pb/pb_frontend_template.py src/bridge/dbus_bridge.py src/core/__init__.py src/core/constants.py src/core/exceptions.py src/core/i18n.py src/core/log.py src/core/log_config.py src/core/sat_main.py src/core/xmpp.py src/memory/__init__.py src/memory/cache.py src/memory/crypto.py src/memory/disco.py src/memory/memory.py src/memory/params.py src/memory/persistent.py src/memory/sqlite.py src/plugins/__init__.py src/plugins/plugin_adhoc_dbus.py src/plugins/plugin_blog_import.py src/plugins/plugin_blog_import_dokuwiki.py src/plugins/plugin_blog_import_dotclear.py src/plugins/plugin_comp_file_sharing.py src/plugins/plugin_exp_command_export.py src/plugins/plugin_exp_events.py src/plugins/plugin_exp_jingle_stream.py src/plugins/plugin_exp_lang_detect.py src/plugins/plugin_exp_parrot.py src/plugins/plugin_exp_pubsub_hook.py src/plugins/plugin_exp_pubsub_schema.py src/plugins/plugin_import.py src/plugins/plugin_merge_req_mercurial.py src/plugins/plugin_misc_account.py src/plugins/plugin_misc_android.py src/plugins/plugin_misc_debug.py src/plugins/plugin_misc_extra_pep.py src/plugins/plugin_misc_file.py src/plugins/plugin_misc_forums.py src/plugins/plugin_misc_groupblog.py src/plugins/plugin_misc_identity.py src/plugins/plugin_misc_imap.py src/plugins/plugin_misc_invitations.py src/plugins/plugin_misc_ip.py src/plugins/plugin_misc_maildir.py src/plugins/plugin_misc_merge_requests.py src/plugins/plugin_misc_nat-port.py src/plugins/plugin_misc_quiz.py src/plugins/plugin_misc_radiocol.py src/plugins/plugin_misc_register_account.py src/plugins/plugin_misc_room_game.py src/plugins/plugin_misc_smtp.py src/plugins/plugin_misc_static_blog.py src/plugins/plugin_misc_tarot.py src/plugins/plugin_misc_text_commands.py src/plugins/plugin_misc_text_syntaxes.py src/plugins/plugin_misc_tickets.py src/plugins/plugin_misc_upload.py src/plugins/plugin_misc_uri_finder.py src/plugins/plugin_misc_watched.py src/plugins/plugin_misc_welcome.py src/plugins/plugin_misc_xmllog.py src/plugins/plugin_sec_otr.py src/plugins/plugin_syntax_wiki_dotclear.py src/plugins/plugin_tickets_import.py src/plugins/plugin_tickets_import_bugzilla.py src/plugins/plugin_tmp_directory_subscription.py src/plugins/plugin_xep_0020.py src/plugins/plugin_xep_0033.py src/plugins/plugin_xep_0045.py src/plugins/plugin_xep_0047.py src/plugins/plugin_xep_0048.py src/plugins/plugin_xep_0049.py src/plugins/plugin_xep_0050.py src/plugins/plugin_xep_0054.py src/plugins/plugin_xep_0055.py src/plugins/plugin_xep_0059.py src/plugins/plugin_xep_0060.py src/plugins/plugin_xep_0065.py src/plugins/plugin_xep_0070.py src/plugins/plugin_xep_0071.py src/plugins/plugin_xep_0077.py src/plugins/plugin_xep_0085.py src/plugins/plugin_xep_0092.py src/plugins/plugin_xep_0095.py src/plugins/plugin_xep_0096.py src/plugins/plugin_xep_0100.py src/plugins/plugin_xep_0115.py src/plugins/plugin_xep_0163.py src/plugins/plugin_xep_0166.py src/plugins/plugin_xep_0184.py src/plugins/plugin_xep_0203.py src/plugins/plugin_xep_0231.py src/plugins/plugin_xep_0234.py src/plugins/plugin_xep_0249.py src/plugins/plugin_xep_0260.py src/plugins/plugin_xep_0261.py src/plugins/plugin_xep_0264.py src/plugins/plugin_xep_0277.py src/plugins/plugin_xep_0280.py src/plugins/plugin_xep_0297.py src/plugins/plugin_xep_0300.py src/plugins/plugin_xep_0313.py src/plugins/plugin_xep_0329.py src/plugins/plugin_xep_0334.py src/plugins/plugin_xep_0363.py src/sat.sh src/stdui/__init__.py src/stdui/ui_contact_list.py src/stdui/ui_profile_manager.py src/test/__init__.py src/test/constants.py src/test/helpers.py src/test/helpers_plugins.py src/test/test_core_xmpp.py src/test/test_helpers_plugins.py src/test/test_memory.py src/test/test_memory_crypto.py src/test/test_plugin_misc_groupblog.py src/test/test_plugin_misc_radiocol.py src/test/test_plugin_misc_room_game.py src/test/test_plugin_misc_text_syntaxes.py src/test/test_plugin_xep_0033.py src/test/test_plugin_xep_0085.py src/test/test_plugin_xep_0203.py src/test/test_plugin_xep_0277.py src/test/test_plugin_xep_0297.py src/test/test_plugin_xep_0313.py src/test/test_plugin_xep_0334.py src/tools/__init__.py src/tools/common/__init__.py src/tools/common/ansi.py src/tools/common/data_format.py src/tools/common/data_objects.py src/tools/common/dynamic_import.py src/tools/common/regex.py src/tools/common/template.py src/tools/common/template_xmlui.py src/tools/common/uri.py src/tools/config.py src/tools/email.py src/tools/sat_defer.py src/tools/stream.py src/tools/trigger.py src/tools/utils.py src/tools/xml_tools.py src/twisted/plugins/sat_plugin.py twisted/plugins/sat_plugin.py
diffstat 456 files changed, 65711 insertions(+), 65938 deletions(-) [+]
line wrap: on
line diff
--- a/MANIFEST.in	Mon Apr 02 08:56:24 2018 +0200
+++ b/MANIFEST.in	Mon Apr 02 19:44:50 2018 +0200
@@ -7,6 +7,6 @@
 include src/sat.*
 include misc
 include frontends/src/jp/jp frontends/src/primitivus/primitivus
-include src/bridge/bridge_constructor/mediawiki_template.tpl
+include src/bridge/bridge_constructor
 global-exclude *.un~
 prune src/bridge/bridge_constructor/generated
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/sat	Mon Apr 02 19:44:50 2018 +0200
@@ -0,0 +1,107 @@
+#!/bin/sh
+
+DEBUG=""
+DAEMON=""
+PYTHON="python2"
+TWISTD="$(which twistd)"
+
+kill_process() {
+    # $1 is the file containing the PID to kill, $2 is the process name
+    if [ -f $1 ]; then
+        PID=`cat $1`
+        if ps -p $PID > /dev/null; then
+            printf  "Terminating $2... "
+            kill $PID
+            while ps -p $PID > /dev/null; do
+                sleep 0.2
+            done
+            printf "OK\n"
+        else
+            echo "No running process of ID $PID... removing PID file"
+            rm -f $1
+        fi
+    else
+        echo "$2 is probably not running (PID file doesn't exist)"
+    fi
+}
+
+#We use python to parse config files
+eval `"$PYTHON" << PYTHONEND
+from sat.core.constants import Const as C
+from sat.memory.memory import fixLocalDir
+from ConfigParser import SafeConfigParser
+from os.path import expanduser, join
+import sys
+import codecs
+import locale
+
+sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout)
+
+fixLocalDir()  # XXX: tmp update code, will be removed in the future
+
+config = SafeConfigParser(defaults=C.DEFAULT_CONFIG)
+try:
+    config.read(C.CONFIG_FILES)
+except:
+    print ("echo \"/!\\ Can't read main config ! Please check the syntax\";")
+    print ("exit 1")
+    sys.exit()
+
+env=[]
+env.append("PID_DIR='%s'" % join(expanduser(config.get('DEFAULT', 'pid_dir')),''))
+env.append("LOG_DIR='%s'" % join(expanduser(config.get('DEFAULT', 'log_dir')),''))
+env.append("APP_NAME='%s'" % C.APP_NAME)
+env.append("APP_NAME_FILE='%s'" % C.APP_NAME_FILE)
+print ";".join(env)
+PYTHONEND
+`
+APP_NAME="$APP_NAME"
+PID_FILE="$PID_DIR$APP_NAME_FILE.pid"
+LOG_FILE="$LOG_DIR$APP_NAME_FILE.log"
+RUNNING_MSG="$APP_NAME is running"
+NOT_RUNNING_MSG="$APP_NAME is *NOT* running"
+
+# if there is one argument which is "stop", then we kill SaT
+if [ $# -eq 1 ];then
+    if [ $1 = "stop" ];then
+        kill_process $PID_FILE "$APP_NAME"
+        exit 0
+    elif [ $1 = "debug" ];then
+        echo "Launching $APP_NAME in debug mode"
+        DEBUG="--debug"
+    elif [ $1 = "fg" ];then
+        echo "Launching $APP_NAME in foreground mode"
+        DAEMON="n"
+    elif [ $1 = "status" ];then
+		if [ -f $PID_FILE ]; then
+			PID=`cat $PID_FILE`
+			ps -p$PID 2>&1 > /dev/null
+			if [ $? = 0  ];then
+				echo "$RUNNING_MSG (pid: $PID)"
+				exit 0
+			else
+				echo "$NOT_RUNNING_MSG, but a pid file is present (bad exit ?): $PID_FILE"
+				exit 2
+			fi
+		else
+			echo "$NOT_RUNNING_MSG"
+			exit 1
+		fi
+	else
+		echo "bad argument, please use one of (stop, debug, fg, status) or no argument"
+		exit 1
+    fi
+fi
+
+MAIN_OPTIONS="-${DAEMON}o"
+
+#Don't change the next lines
+AUTO_OPTIONS=""
+ADDITIONAL_OPTIONS="--pidfile $PID_FILE --logfile $LOG_FILE $AUTO_OPTIONS $DEBUG"
+
+log_dir=`dirname "$LOG_FILE"`
+if [ ! -d $log_dir ] ; then
+    mkdir $log_dir
+fi
+
+exec $PYTHON $TWISTD $MAIN_OPTIONS $ADDITIONAL_OPTIONS $APP_NAME_FILE
--- a/frontends/src/bridge/bridge_frontend.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-#!/usr/bin/env python2
-#-*- coding: utf-8 -*-
-
-# SAT communication bridge
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-
-class BridgeException(Exception):
-    """An exception which has been raised from the backend and arrived to the frontend."""
-
-    def __init__(self, name, message='', condition=''):
-        """
-
-        @param name (str): full exception class name (with module)
-        @param message (str): error message
-        @param condition (str) : error condition
-        """
-        Exception.__init__(self)
-        self.fullname = unicode(name)
-        self.message = unicode(message)
-        self.condition = unicode(condition) if condition else ''
-        self.module, dummy, self.classname = unicode(self.fullname).rpartition('.')
-
-    def __str__(self):
-        message = (': %s' % self.message) if self.message else ''
-        return self.classname + message
-
-    def __eq__(self, other):
-        return self.classname == other
--- a/frontends/src/bridge/dbus_bridge.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,732 +0,0 @@
-#!/usr/bin/env python2
-#-*- coding: utf-8 -*-
-
-# SAT communication bridge
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-from sat.core.i18n import _
-from bridge_frontend import BridgeException
-import dbus
-from sat.core.log import getLogger
-log = getLogger(__name__)
-from sat.core.exceptions import BridgeExceptionNoService, BridgeInitError
-
-from dbus.mainloop.glib import DBusGMainLoop
-DBusGMainLoop(set_as_default=True)
-
-import ast
-
-const_INT_PREFIX = "org.goffi.SAT"  # Interface prefix
-const_ERROR_PREFIX = const_INT_PREFIX + ".error"
-const_OBJ_PATH = '/org/goffi/SAT/bridge'
-const_CORE_SUFFIX = ".core"
-const_PLUGIN_SUFFIX = ".plugin"
-const_TIMEOUT = 120
-
-
-def dbus_to_bridge_exception(dbus_e):
-    """Convert a DBusException to a BridgeException.
-
-    @param dbus_e (DBusException)
-    @return: BridgeException
-    """
-    full_name = dbus_e.get_dbus_name()
-    if full_name.startswith(const_ERROR_PREFIX):
-        name = dbus_e.get_dbus_name()[len(const_ERROR_PREFIX) + 1:]
-    else:
-        name = full_name
-    # XXX: dbus_e.args doesn't contain the original DBusException args, but we
-    # receive its serialized form in dbus_e.args[0]. From that we can rebuild
-    # the original arguments list thanks to ast.literal_eval (secure eval).
-    message = dbus_e.get_dbus_message()  # similar to dbus_e.args[0]
-    try:
-        message, condition = ast.literal_eval(message)
-    except (SyntaxError, ValueError, TypeError):
-        condition = ''
-    return BridgeException(name, message, condition)
-
-
-class Bridge(object):
-
-    def bridgeConnect(self, callback, errback):
-        try:
-            self.sessions_bus = dbus.SessionBus()
-            self.db_object = self.sessions_bus.get_object(const_INT_PREFIX,
-                                                          const_OBJ_PATH)
-            self.db_core_iface = dbus.Interface(self.db_object,
-                                                dbus_interface=const_INT_PREFIX + const_CORE_SUFFIX)
-            self.db_plugin_iface = dbus.Interface(self.db_object,
-                                                  dbus_interface=const_INT_PREFIX + const_PLUGIN_SUFFIX)
-        except dbus.exceptions.DBusException, e:
-            if e._dbus_error_name in ('org.freedesktop.DBus.Error.ServiceUnknown',
-                                      'org.freedesktop.DBus.Error.Spawn.ExecFailed'):
-                errback(BridgeExceptionNoService())
-            elif e._dbus_error_name == 'org.freedesktop.DBus.Error.NotSupported':
-                log.error(_(u"D-Bus is not launched, please see README to see instructions on how to launch it"))
-                errback(BridgeInitError)
-            else:
-                errback(e)
-        callback()
-        #props = self.db_core_iface.getProperties()
-
-    def register_signal(self, functionName, handler, iface="core"):
-        if iface == "core":
-            self.db_core_iface.connect_to_signal(functionName, handler)
-        elif iface == "plugin":
-            self.db_plugin_iface.connect_to_signal(functionName, handler)
-        else:
-            log.error(_('Unknown interface'))
-
-    def __getattribute__(self, name):
-        """ usual __getattribute__ if the method exists, else try to find a plugin method """
-        try:
-            return object.__getattribute__(self, name)
-        except AttributeError:
-            # The attribute is not found, we try the plugin proxy to find the requested method
-
-            def getPluginMethod(*args, **kwargs):
-                # We first check if we have an async call. We detect this in two ways:
-                #   - if we have the 'callback' and 'errback' keyword arguments
-                #   - or if the last two arguments are callable
-
-                async = False
-                args = list(args)
-
-                if kwargs:
-                    if 'callback' in kwargs:
-                        async = True
-                        _callback = kwargs.pop('callback')
-                        _errback = kwargs.pop('errback', lambda failure: log.error(unicode(failure)))
-                    try:
-                        args.append(kwargs.pop('profile'))
-                    except KeyError:
-                        try:
-                            args.append(kwargs.pop('profile_key'))
-                        except KeyError:
-                            pass
-                    # at this point, kwargs should be empty
-                    if kwargs:
-                        log.warnings(u"unexpected keyword arguments, they will be ignored: {}".format(kwargs))
-                elif len(args) >= 2 and callable(args[-1]) and callable(args[-2]):
-                    async = True
-                    _errback = args.pop()
-                    _callback = args.pop()
-
-                method = getattr(self.db_plugin_iface, name)
-
-                if async:
-                    kwargs['timeout'] = const_TIMEOUT
-                    kwargs['reply_handler'] = _callback
-                    kwargs['error_handler'] = lambda err: _errback(dbus_to_bridge_exception(err))
-
-                return method(*args, **kwargs)
-
-            return getPluginMethod
-
-    def actionsGet(self, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.actionsGet(profile_key, **kwargs)
-
-    def addContact(self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.addContact(entity_jid, profile_key, **kwargs)
-
-    def asyncDeleteProfile(self, profile, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.asyncDeleteProfile(profile, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
-
-    def asyncGetParamA(self, name, category, attribute="value", security_limit=-1, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return unicode(self.db_core_iface.asyncGetParamA(name, category, attribute, security_limit, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler))
-
-    def asyncGetParamsValuesFromCategory(self, category, security_limit=-1, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.asyncGetParamsValuesFromCategory(category, security_limit, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
-
-    def connect(self, profile_key="@DEFAULT@", password='', options={}, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.connect(profile_key, password, options, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
-
-    def delContact(self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.delContact(entity_jid, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
-
-    def discoFindByFeatures(self, namespaces, identities, bare_jid=False, service=True, roster=True, own_jid=True, profile_key=u"@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.discoFindByFeatures(namespaces, identities, bare_jid, service, roster, own_jid, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
-
-    def discoInfos(self, entity_jid, node=u'', use_cache=True, profile_key=u"@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.discoInfos(entity_jid, node, use_cache, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
-
-    def discoItems(self, entity_jid, node=u'', use_cache=True, profile_key=u"@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.discoItems(entity_jid, node, use_cache, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
-
-    def disconnect(self, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.disconnect(profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
-
-    def getConfig(self, section, name, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return unicode(self.db_core_iface.getConfig(section, name, **kwargs))
-
-    def getContacts(self, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.getContacts(profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
-
-    def getContactsFromGroup(self, group, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.getContactsFromGroup(group, profile_key, **kwargs)
-
-    def getEntitiesData(self, jids, keys, profile, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.getEntitiesData(jids, keys, profile, **kwargs)
-
-    def getEntityData(self, jid, keys, profile, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.getEntityData(jid, keys, profile, **kwargs)
-
-    def getFeatures(self, profile_key, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.getFeatures(profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
-
-    def getMainResource(self, contact_jid, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return unicode(self.db_core_iface.getMainResource(contact_jid, profile_key, **kwargs))
-
-    def getParamA(self, name, category, attribute="value", profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return unicode(self.db_core_iface.getParamA(name, category, attribute, profile_key, **kwargs))
-
-    def getParamsCategories(self, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.getParamsCategories(**kwargs)
-
-    def getParamsUI(self, security_limit=-1, app='', profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return unicode(self.db_core_iface.getParamsUI(security_limit, app, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler))
-
-    def getPresenceStatuses(self, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.getPresenceStatuses(profile_key, **kwargs)
-
-    def getReady(self, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.getReady(timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
-
-    def getVersion(self, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return unicode(self.db_core_iface.getVersion(**kwargs))
-
-    def getWaitingSub(self, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.getWaitingSub(profile_key, **kwargs)
-
-    def historyGet(self, from_jid, to_jid, limit, between=True, filters='', profile="@NONE@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.historyGet(from_jid, to_jid, limit, between, filters, profile, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
-
-    def isConnected(self, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.isConnected(profile_key, **kwargs)
-
-    def launchAction(self, callback_id, data, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.launchAction(callback_id, data, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
-
-    def loadParamsTemplate(self, filename, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.loadParamsTemplate(filename, **kwargs)
-
-    def menuHelpGet(self, menu_id, language, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return unicode(self.db_core_iface.menuHelpGet(menu_id, language, **kwargs))
-
-    def menuLaunch(self, menu_type, path, data, security_limit, profile_key, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.menuLaunch(menu_type, path, data, security_limit, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
-
-    def menusGet(self, language, security_limit, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.menusGet(language, security_limit, **kwargs)
-
-    def messageSend(self, to_jid, message, subject={}, mess_type="auto", extra={}, profile_key="@NONE@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.messageSend(to_jid, message, subject, mess_type, extra, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
-
-    def namespacesGet(self, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.namespacesGet(**kwargs)
-
-    def paramsRegisterApp(self, xml, security_limit=-1, app='', callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.paramsRegisterApp(xml, security_limit, app, **kwargs)
-
-    def profileCreate(self, profile, password='', component='', callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.profileCreate(profile, password, component, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
-
-    def profileIsSessionStarted(self, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.profileIsSessionStarted(profile_key, **kwargs)
-
-    def profileNameGet(self, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return unicode(self.db_core_iface.profileNameGet(profile_key, **kwargs))
-
-    def profileSetDefault(self, profile, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.profileSetDefault(profile, **kwargs)
-
-    def profileStartSession(self, password='', profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.profileStartSession(password, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
-
-    def profilesListGet(self, clients=True, components=False, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.profilesListGet(clients, components, **kwargs)
-
-    def progressGet(self, id, profile, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.progressGet(id, profile, **kwargs)
-
-    def progressGetAll(self, profile, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.progressGetAll(profile, **kwargs)
-
-    def progressGetAllMetadata(self, profile, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.progressGetAllMetadata(profile, **kwargs)
-
-    def saveParamsTemplate(self, filename, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.saveParamsTemplate(filename, **kwargs)
-
-    def sessionInfosGet(self, profile_key, callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.sessionInfosGet(profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
-
-    def setParam(self, name, value, category, security_limit=-1, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.setParam(name, value, category, security_limit, profile_key, **kwargs)
-
-    def setPresence(self, to_jid='', show='', statuses={}, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.setPresence(to_jid, show, statuses, profile_key, **kwargs)
-
-    def subscription(self, sub_type, entity, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.subscription(sub_type, entity, profile_key, **kwargs)
-
-    def updateContact(self, entity_jid, name, groups, profile_key="@DEFAULT@", callback=None, errback=None):
-        if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        return self.db_core_iface.updateContact(entity_jid, name, groups, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
--- a/frontends/src/jp/arg_tools.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SàT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-from sat.core.i18n import _
-from sat.core import exceptions
-
-
-def escape(arg, smart=True):
-    """format arg with quotes
-
-    @param smart(bool): if True, only escape if needed
-    """
-    if smart and not ' ' in arg and not '"' in arg:
-        return arg
-    return u'"' + arg.replace(u'"',u'\\"') + u'"'
-
-
-def get_cmd_choices(cmd=None, parser=None):
-    try:
-        choices = parser._subparsers._group_actions[0].choices
-        return choices[cmd] if cmd is not None else choices
-    except (KeyError, AttributeError):
-        raise exceptions.NotFound
-
-
-def get_use_args(host, args, use, verbose=False, parser=None):
-    """format args for argparse parser with values prefilled
-
-    @param host(JP): jp instance
-    @param args(list(str)): arguments to use
-    @param use(dict[str, str]): arguments to fill if found in parser
-    @param verbose(bool): if True a message will be displayed when argument is used or not
-    @param parser(argparse.ArgumentParser): parser to use
-    @return (tuple[list[str],list[str]]): 2 args lists:
-        - parser args, i.e. given args corresponding to parsers
-        - use args, i.e. generated args from use
-    """
-    # FIXME: positional args are not handled correclty
-    #        if there is more that one, the position is not corrected
-    if parser is None:
-        parser = host.parser
-
-    # we check not optional args to see if there
-    # is a corresonding parser
-    # else USE args would not work correctly (only for current parser)
-    parser_args = []
-    for arg in args:
-        if arg.startswith('-'):
-            break
-        try:
-            parser = get_cmd_choices(arg, parser)
-        except exceptions.NotFound:
-            break
-        parser_args.append(arg)
-
-    # post_args are remaning given args,
-    # without the ones corresponding to parsers
-    post_args = args[len(parser_args):]
-
-    opt_args = []
-    pos_args = []
-    actions = {a.dest: a for a in parser._actions}
-    for arg, value in use.iteritems():
-        try:
-            if arg == u'item' and not u'item' in actions:
-                # small hack when --item is appended to a --items list
-                arg = u'items'
-            action = actions[arg]
-        except KeyError:
-            if verbose:
-                host.disp(_(u'ignoring {name}={value}, not corresponding to any argument (in USE)').format(
-                    name=arg,
-                    value=escape(value)))
-        else:
-            if verbose:
-                host.disp(_(u'arg {name}={value} (in USE)').format(name=arg, value=escape(value)))
-            if not action.option_strings:
-                pos_args.append(value)
-            else:
-                opt_args.append(action.option_strings[0])
-                opt_args.append(value)
-    return parser_args, opt_args + pos_args + post_args
--- a/frontends/src/jp/base.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1084 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SAT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-from sat.core.i18n import _
-
-### logging ###
-import logging as log
-log.basicConfig(level=log.DEBUG,
-                format='%(message)s')
-###
-
-import sys
-import locale
-import os.path
-import argparse
-from glob import iglob
-from importlib import import_module
-from sat_frontends.tools.jid import JID
-from sat.tools import config
-from sat.tools.common import dynamic_import
-from sat.tools.common import uri
-from sat.core import exceptions
-import sat_frontends.jp
-from sat_frontends.jp.constants import Const as C
-from sat_frontends.tools import misc
-import xml.etree.ElementTree as ET  # FIXME: used temporarily to manage XMLUI
-import shlex
-from collections import OrderedDict
-
-## bridge handling
-# we get bridge name from conf and initialise the right class accordingly
-main_config = config.parseMainConf()
-bridge_name = config.getConfig(main_config, '', 'bridge', 'dbus')
-
-
-# TODO: move loops handling in a separated module
-if 'dbus' in bridge_name:
-    from gi.repository import GLib
-
-
-    class JPLoop(object):
-
-        def __init__(self):
-            self.loop = GLib.MainLoop()
-
-        def run(self):
-            self.loop.run()
-
-        def quit(self):
-            self.loop.quit()
-
-        def call_later(self, delay, callback, *args):
-            """call a callback repeatedly
-
-            @param delay(int): delay between calls in ms
-            @param callback(callable): method to call
-                if the callback return True, the call will continue
-                else the calls will stop
-            @param *args: args of the callbac
-            """
-            GLib.timeout_add(delay, callback, *args)
-
-else:
-    print u"can't start jp: only D-Bus bridge is currently handled"
-    sys.exit(C.EXIT_ERROR)
-    # FIXME: twisted loop can be used when jp can handle fully async bridges
-    # from twisted.internet import reactor
-
-    # class JPLoop(object):
-
-    #     def run(self):
-    #         reactor.run()
-
-    #     def quit(self):
-    #         reactor.stop()
-
-    #     def _timeout_cb(self, args, callback, delay):
-    #         ret = callback(*args)
-    #         if ret:
-    #             reactor.callLater(delay, self._timeout_cb, args, callback, delay)
-
-    #     def call_later(self, delay, callback, *args):
-    #         delay = float(delay) / 1000
-    #         reactor.callLater(delay, self._timeout_cb, args, callback, delay)
-
-if bridge_name == "embedded":
-    from sat.core import sat_main
-    sat = sat_main.SAT()
-
-if sys.version_info < (2, 7, 3):
-    # XXX: shlex.split only handle unicode since python 2.7.3
-    # this is a workaround for older versions
-    old_split = shlex.split
-    new_split = (lambda s, *a, **kw: [t.decode('utf-8') for t in old_split(s.encode('utf-8'), *a, **kw)]
-        if isinstance(s, unicode) else old_split(s, *a, **kw))
-    shlex.split = new_split
-
-try:
-    import progressbar
-except ImportError:
-    msg = (_(u'ProgressBar not available, please download it at http://pypi.python.org/pypi/progressbar\n') +
-           _(u'Progress bar deactivated\n--\n'))
-    print >>sys.stderr,msg.encode('utf-8')
-    progressbar=None
-
-#consts
-PROG_NAME = u"jp"
-DESCRIPTION = """This software is a command line tool for XMPP.
-Get the latest version at """ + C.APP_URL
-
-COPYLEFT = u"""Copyright (C) 2009-2018 Jérôme Poisson, Adrien Cossa
-This program comes with ABSOLUTELY NO WARRANTY;
-This is free software, and you are welcome to redistribute it under certain conditions.
-"""
-
-PROGRESS_DELAY = 10 # the progression will be checked every PROGRESS_DELAY ms
-
-
-def unicode_decoder(arg):
-    # Needed to have unicode strings from arguments
-    return arg.decode(locale.getpreferredencoding())
-
-
-class Jp(object):
-    """
-    This class can be use to establish a connection with the
-    bridge. Moreover, it should manage a main loop.
-
-    To use it, you mainly have to redefine the method run to perform
-    specify what kind of operation you want to perform.
-
-    """
-    def __init__(self):
-        """
-
-        @attribute quit_on_progress_end (bool): set to False if you manage yourself exiting,
-            or if you want the user to stop by himself
-        @attribute progress_success(callable): method to call when progress just started
-            by default display a message
-        @attribute progress_success(callable): method to call when progress is successfully finished
-            by default display a message
-        @attribute progress_failure(callable): method to call when progress failed
-            by default display a message
-        """
-        # FIXME: need_loop should be removed, everything must be async in bridge so
-        #        loop will always be needed
-        bridge_module = dynamic_import.bridge(bridge_name, 'sat_frontends.bridge')
-        if bridge_module is None:
-            log.error(u"Can't import {} bridge".format(bridge_name))
-            sys.exit(1)
-
-        self.bridge = bridge_module.Bridge()
-        self.bridge.bridgeConnect(callback=self._bridgeCb, errback=self._bridgeEb)
-
-    def _bridgeCb(self):
-        self.parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
-                                              description=DESCRIPTION)
-        self._make_parents()
-        self.add_parser_options()
-        self.subparsers = self.parser.add_subparsers(title=_(u'Available commands'), dest='subparser_name')
-        self._auto_loop = False # when loop is used for internal reasons
-        self._need_loop = False
-
-        # progress attributes
-        self._progress_id = None # TODO: manage several progress ids
-        self.quit_on_progress_end = True
-
-        # outputs
-        self._outputs = {}
-        for type_ in C.OUTPUT_TYPES:
-            self._outputs[type_] = OrderedDict()
-        self.default_output = {}
-
-    def _bridgeEb(self, failure):
-        if isinstance(failure, exceptions.BridgeExceptionNoService):
-            print(_(u"Can't connect to SàT backend, are you sure it's launched ?"))
-        elif isinstance(failure, exceptions.BridgeInitError):
-            print(_(u"Can't init bridge"))
-        else:
-            print(_(u"Error while initialising bridge: {}".format(failure)))
-        sys.exit(C.EXIT_BRIDGE_ERROR)
-
-    @property
-    def version(self):
-        return self.bridge.getVersion()
-
-    @property
-    def progress_id(self):
-        return self._progress_id
-
-    @progress_id.setter
-    def progress_id(self, value):
-        self._progress_id = value
-        self.replayCache('progress_ids_cache')
-
-    @property
-    def watch_progress(self):
-        try:
-            self.pbar
-        except AttributeError:
-            return False
-        else:
-            return True
-
-    @watch_progress.setter
-    def watch_progress(self, watch_progress):
-        if watch_progress:
-            self.pbar = None
-
-    @property
-    def verbosity(self):
-        try:
-            return self.args.verbose
-        except AttributeError:
-            return 0
-
-    def replayCache(self, cache_attribute):
-        """Replay cached signals
-
-        @param cache_attribute(str): name of the attribute containing the cache
-            if the attribute doesn't exist, there is no cache and the call is ignored
-            else the cache must be a list of tuples containing the replay callback as first item,
-            then the arguments to use
-        """
-        try:
-            cache = getattr(self, cache_attribute)
-        except AttributeError:
-            pass
-        else:
-            for cache_data in cache:
-                cache_data[0](*cache_data[1:])
-
-    def disp(self, msg, verbosity=0, error=False, no_lf=False):
-        """Print a message to user
-
-        @param msg(unicode): message to print
-        @param verbosity(int): minimal verbosity to display the message
-        @param error(bool): if True, print to stderr instead of stdout
-        @param no_lf(bool): if True, do not emit line feed at the end of line
-        """
-        if self.verbosity >= verbosity:
-            if error:
-                if no_lf:
-                    print >>sys.stderr,msg.encode('utf-8'),
-                else:
-                    print >>sys.stderr,msg.encode('utf-8')
-            else:
-                if no_lf:
-                    print msg.encode('utf-8'),
-                else:
-                    print msg.encode('utf-8')
-
-    def output(self, type_, name, extra_outputs, data):
-        if name in extra_outputs:
-            extra_outputs[name](data)
-        else:
-            self._outputs[type_][name]['callback'](data)
-
-    def addOnQuitCallback(self, callback, *args, **kwargs):
-        """Add a callback which will be called on quit command
-
-        @param callback(callback): method to call
-        """
-        try:
-            callbacks_list = self._onQuitCallbacks
-        except AttributeError:
-            callbacks_list = self._onQuitCallbacks = []
-        finally:
-            callbacks_list.append((callback, args, kwargs))
-
-    def getOutputChoices(self, output_type):
-        """Return valid output filters for output_type
-
-        @param output_type: True for default,
-            else can be any registered type
-        """
-        return self._outputs[output_type].keys()
-
-    def _make_parents(self):
-        self.parents = {}
-
-        # we have a special case here as the start-session option is present only if connection is not needed,
-        # so we create two similar parents, one with the option, the other one without it
-        for parent_name in ('profile', 'profile_session'):
-            parent = self.parents[parent_name] = argparse.ArgumentParser(add_help=False)
-            parent.add_argument("-p", "--profile", action="store", type=str, default='@DEFAULT@', help=_("Use PROFILE profile key (default: %(default)s)"))
-            parent.add_argument("--pwd", action="store", type=unicode_decoder, default='', metavar='PASSWORD', help=_("Password used to connect profile, if necessary"))
-
-        profile_parent, profile_session_parent = self.parents['profile'], self.parents['profile_session']
-
-        connect_short, connect_long, connect_action, connect_help = "-c", "--connect", "store_true", _(u"Connect the profile before doing anything else")
-        profile_parent.add_argument(connect_short, connect_long, action=connect_action, help=connect_help)
-
-        profile_session_connect_group = profile_session_parent.add_mutually_exclusive_group()
-        profile_session_connect_group.add_argument(connect_short, connect_long, action=connect_action, help=connect_help)
-        profile_session_connect_group.add_argument("--start-session", action="store_true", help=_("Start a profile session without connecting"))
-
-        progress_parent = self.parents['progress'] = argparse.ArgumentParser(add_help=False)
-        if progressbar:
-            progress_parent.add_argument("-P", "--progress", action="store_true", help=_("Show progress bar"))
-
-        verbose_parent = self.parents['verbose'] = argparse.ArgumentParser(add_help=False)
-        verbose_parent.add_argument('--verbose', '-v', action='count', default=0, help=_(u"Add a verbosity level (can be used multiple times)"))
-
-        draft_parent = self.parents['draft'] = argparse.ArgumentParser(add_help=False)
-        draft_group = draft_parent.add_argument_group(_('draft handling'))
-        draft_group.add_argument("-D", "--current", action="store_true", help=_(u"load current draft"))
-        draft_group.add_argument("-F", "--draft-path", type=unicode_decoder, help=_(u"path to a draft file to retrieve"))
-
-
-    def make_pubsub_group(self, flags, defaults):
-        """generate pubsub options according to flags
-
-        @param flags(iterable[unicode]): see [CommandBase.__init__]
-        @param defaults(dict[unicode, unicode]): help text for default value
-            key can be "service" or "node"
-            value will be set in " (DEFAULT: {value})", or can be None to remove DEFAULT
-        @return (ArgumentParser): parser to add
-        """
-        flags = misc.FlagsHandler(flags)
-        parent = argparse.ArgumentParser(add_help=False)
-        pubsub_group = parent.add_argument_group('pubsub')
-        pubsub_group.add_argument("-u", "--pubsub-url", type=unicode_decoder,
-                                  help=_(u"Pubsub URL (xmpp or http)"))
-
-        service_help = _(u"JID of the PubSub service")
-        if not flags.service:
-            default = defaults.pop(u'service', _(u'PEP service'))
-            if default is not None:
-                service_help += _(u" (DEFAULT: {default})".format(default=default))
-        pubsub_group.add_argument("-s", "--service", type=unicode_decoder, default=u'',
-                                  help=service_help)
-
-        node_help = _(u"node to request")
-        if not flags.node:
-            default = defaults.pop(u'node', _(u'standard node'))
-            if default is not None:
-                node_help += _(u" (DEFAULT: {default})".format(default=default))
-        pubsub_group.add_argument("-n", "--node", type=unicode_decoder, default=u'', help=node_help)
-
-        if flags.single_item:
-            item_help = (u"item to retrieve")
-            if not flags.item:
-                default = defaults.pop(u'item', _(u'last item'))
-                if default is not None:
-                    item_help += _(u" (DEFAULT: {default})".format(default=default))
-            pubsub_group.add_argument("-i", "--item", type=unicode_decoder, help=item_help)
-            pubsub_group.add_argument("-L", "--last-item", action='store_true', help=_(u'retrieve last item'))
-        elif flags.multi_items:
-            # mutiple items
-            pubsub_group.add_argument("-i", "--item", type=unicode_decoder, action='append', dest='items', default=[], help=_(u"items to retrieve (DEFAULT: all)"))
-            if not flags.no_max:
-                pubsub_group.add_argument("-m", "--max", type=int, default=10,
-                    help=_(u"maximum number of items to get ({no_limit} to get all items)".format(no_limit=C.NO_LIMIT)))
-
-        if flags:
-            raise exceptions.InternalError('unknowns flags: {flags}'.format(flags=u', '.join(flags)))
-        if defaults:
-            raise exceptions.InternalError('unused defaults: {defaults}'.format(defaults=defaults))
-
-        return parent
-
-    def add_parser_options(self):
-        self.parser.add_argument('--version', action='version', version=("%(name)s %(version)s %(copyleft)s" % {'name': PROG_NAME, 'version': self.version, 'copyleft': COPYLEFT}))
-
-    def register_output(self, type_, name, callback, description="", default=False):
-        if type_ not in C.OUTPUT_TYPES:
-            log.error(u"Invalid output type {}".format(type_))
-            return
-        self._outputs[type_][name] = {'callback': callback,
-                                      'description': description
-                                     }
-        if default:
-            if type_ in self.default_output:
-                self.disp(_(u'there is already a default output for {}, ignoring new one').format(type_))
-            else:
-                self.default_output[type_] = name
-
-
-    def parse_output_options(self):
-        options = self.command.args.output_opts
-        options_dict = {}
-        for option in options:
-            try:
-                key, value = option.split(u'=', 1)
-            except ValueError:
-                key, value = option, None
-            options_dict[key.strip()] = value.strip() if value is not None else None
-        return options_dict
-
-    def check_output_options(self, accepted_set, options):
-        if not accepted_set.issuperset(options):
-            self.disp(u"The following output options are invalid: {invalid_options}".format(
-                invalid_options = u', '.join(set(options).difference(accepted_set))),
-                error=True)
-            self.quit(C.EXIT_BAD_ARG)
-
-    def import_plugins(self):
-        """Automaticaly import commands and outputs in jp
-
-        looks from modules names cmd_*.py in jp path and import them
-        """
-        path = os.path.dirname(sat_frontends.jp.__file__)
-        # XXX: outputs must be imported before commands as they are used for arguments
-        for type_, pattern in ((C.PLUGIN_OUTPUT, 'output_*.py'), (C.PLUGIN_CMD, 'cmd_*.py')):
-            modules = (os.path.splitext(module)[0] for module in map(os.path.basename, iglob(os.path.join(path, pattern))))
-            for module_name in modules:
-                module_path = "sat_frontends.jp." + module_name
-                try:
-                    module = import_module(module_path)
-                    self.import_plugin_module(module, type_)
-                except ImportError as e:
-                    self.disp(_(u"Can't import {module_path} plugin, ignoring it: {msg}".format(
-                    module_path = module_path,
-                    msg = e)), error=True)
-                except exceptions.CancelError:
-                    continue
-                except exceptions.MissingModule as e:
-                    self.disp(_(u"Missing module for plugin {name}: {missing}".format(
-                        name = module_path,
-                        missing = e)), error=True)
-
-
-    def import_plugin_module(self, module, type_):
-        """add commands or outpus from a module to jp
-
-        @param module: module containing commands or outputs
-        @param type_(str): one of C_PLUGIN_*
-        """
-        try:
-            class_names =  getattr(module, '__{}__'.format(type_))
-        except AttributeError:
-            log.disp(_(u"Invalid plugin module [{type}] {module}").format(type=type_, module=module), error=True)
-            raise ImportError
-        else:
-            for class_name in class_names:
-                cls = getattr(module, class_name)
-                cls(self)
-
-    def get_xmpp_uri_from_http(self, http_url):
-        """parse HTML page at http(s) URL, and looks for xmpp: uri"""
-        if http_url.startswith('https'):
-            scheme = u'https'
-        elif http_url.startswith('http'):
-            scheme = u'http'
-        else:
-            raise exceptions.InternalError(u'An HTTP scheme is expected in this method')
-        self.disp(u"{scheme} URL found, trying to find associated xmpp: URI".format(scheme=scheme.upper()),1)
-        # HTTP URL, we try to find xmpp: links
-        try:
-            from lxml import etree
-        except ImportError:
-            self.disp(u"lxml module must be installed to use http(s) scheme, please install it with \"pip install lxml\"", error=True)
-            self.host.quit(1)
-        import urllib2
-        parser = etree.HTMLParser()
-        try:
-            root = etree.parse(urllib2.urlopen(http_url), parser)
-        except etree.XMLSyntaxError as e:
-            self.disp(_(u"Can't parse HTML page : {msg}").format(msg=e))
-            links = []
-        else:
-            links = root.xpath("//link[@rel='alternate' and starts-with(@href, 'xmpp:')]")
-        if not links:
-            self.disp(u'Could not find alternate "xmpp:" URI, can\'t find associated XMPP PubSub node/item', error=True)
-            self.host.quit(1)
-        xmpp_uri = links[0].get('href')
-        return xmpp_uri
-
-    def parse_pubsub_args(self):
-        if self.args.pubsub_url is not None:
-            url = self.args.pubsub_url
-
-            if url.startswith('http'):
-                # http(s) URL, we try to retrieve xmpp one from there
-                url = self.get_xmpp_uri_from_http(url)
-
-            try:
-                uri_data = uri.parseXMPPUri(url)
-            except ValueError:
-                self.parser.error(_(u'invalid XMPP URL: {url}').format(url=url))
-            else:
-                if uri_data[u'type'] == 'pubsub':
-                    # URL is alright, we only set data not already set by other options
-                    if not self.args.service:
-                        self.args.service = uri_data[u'path']
-                    if not self.args.node:
-                        self.args.node = uri_data[u'node']
-                    uri_item = uri_data.get(u'item')
-                    if uri_item:
-                        # there is an item in URI
-                        # we use it only if item is not already set
-                        # and item_last is not used either
-                        try:
-                            item = self.args.item
-                        except AttributeError:
-                            if not self.args.items:
-                                self.args.items = [uri_item]
-                        else:
-                            if not item:
-                                try:
-                                    item_last = self.args.item_last
-                                except AttributeError:
-                                    item_last = False
-                                if not item_last:
-                                    self.args.item = uri_item
-                else:
-                    self.parser.error(_(u'XMPP URL is not a pubsub one: {url}').format(url=url))
-        flags = self.args._cmd._pubsub_flags
-        # we check required arguments here instead of using add_arguments' required option
-        # because the required argument can be set in URL
-        if C.SERVICE in flags and not self.args.service:
-            self.parser.error(_(u"argument -s/--service is required"))
-        if C.NODE in flags and not self.args.node:
-            self.parser.error(_(u"argument -n/--node is required"))
-        if C.ITEM in flags and not self.args.item:
-            self.parser.error(_(u"argument -i/--item is required"))
-
-        # FIXME: mutually groups can't be nested in a group and don't support title
-        #        so we check conflict here. This may be fixed in Python 3, to be checked
-        try:
-            if self.args.item and self.args.item_last:
-                self.parser.error(_(u"--item and --item-last can't be used at the same time"))
-        except AttributeError:
-            pass
-
-    def run(self, args=None, namespace=None):
-        self.args = self.parser.parse_args(args, namespace=None)
-        if self.args._cmd._use_pubsub:
-            self.parse_pubsub_args()
-        try:
-            self.args._cmd.run()
-            if self._need_loop or self._auto_loop:
-                self._start_loop()
-        except KeyboardInterrupt:
-            log.info(_("User interruption: good bye"))
-
-    def _start_loop(self):
-        self.loop = JPLoop()
-        self.loop.run()
-
-    def stop_loop(self):
-        try:
-            self.loop.quit()
-        except AttributeError:
-            pass
-
-    def confirmOrQuit(self, message, cancel_message=_(u"action cancelled by user")):
-        """Request user to confirm action, and quit if he doesn't"""
-
-        res = raw_input("{} (y/N)? ".format(message))
-        if res not in ("y", "Y"):
-            self.disp(cancel_message)
-            self.quit(C.EXIT_USER_CANCELLED)
-
-    def quitFromSignal(self, errcode=0):
-        """Same as self.quit, but from a signal handler
-
-        /!\: return must be used after calling this method !
-        """
-        assert self._need_loop
-        # XXX: python-dbus will show a traceback if we exit in a signal handler
-        # so we use this little timeout trick to avoid it
-        self.loop.call_later(0, self.quit, errcode)
-
-    def quit(self, errcode=0):
-        # first the onQuitCallbacks
-        try:
-            callbacks_list = self._onQuitCallbacks
-        except AttributeError:
-            pass
-        else:
-            for callback, args, kwargs in callbacks_list:
-                callback(*args, **kwargs)
-
-        self.stop_loop()
-        sys.exit(errcode)
-
-    def check_jids(self, jids):
-        """Check jids validity, transform roster name to corresponding jids
-
-        @param profile: profile name
-        @param jids: list of jids
-        @return: List of jids
-
-        """
-        names2jid = {}
-        nodes2jid = {}
-
-        for contact in self.bridge.getContacts(self.profile):
-            jid_s, attr, groups = contact
-            _jid = JID(jid_s)
-            try:
-                names2jid[attr["name"].lower()] = jid_s
-            except KeyError:
-                pass
-
-            if _jid.node:
-                nodes2jid[_jid.node.lower()] = jid_s
-
-        def expand_jid(jid):
-            _jid = jid.lower()
-            if _jid in names2jid:
-                expanded = names2jid[_jid]
-            elif _jid in nodes2jid:
-                expanded = nodes2jid[_jid]
-            else:
-                expanded = jid
-            return expanded.decode('utf-8')
-
-        def check(jid):
-            if not jid.is_valid:
-                log.error (_("%s is not a valid JID !"), jid)
-                self.quit(1)
-
-        dest_jids=[]
-        try:
-            for i in range(len(jids)):
-                dest_jids.append(expand_jid(jids[i]))
-                check(dest_jids[i])
-        except AttributeError:
-            pass
-
-        return dest_jids
-
-    def connect_profile(self, callback):
-        """ Check if the profile is connected and do it if requested
-
-        @param callback: method to call when profile is connected
-        @exit: - 1 when profile is not connected and --connect is not set
-               - 1 when the profile doesn't exists
-               - 1 when there is a connection error
-        """
-        # FIXME: need better exit codes
-
-        def cant_connect(failure):
-            log.error(_(u"Can't connect profile: {reason}").format(reason=failure))
-            self.quit(1)
-
-        def cant_start_session(failure):
-            log.error(_(u"Can't start {profile}'s session: {reason}").format(profile=self.profile, reason=failure))
-            self.quit(1)
-
-        self.profile = self.bridge.profileNameGet(self.args.profile)
-
-        if not self.profile:
-            log.error(_("The profile [{profile}] doesn't exist").format(profile=self.args.profile))
-            self.quit(1)
-
-        try:
-            start_session = self.args.start_session
-        except AttributeError:
-            pass
-        else:
-            if start_session:
-                self.bridge.profileStartSession(self.args.pwd, self.profile, lambda dummy: callback(), cant_start_session)
-                self._auto_loop = True
-                return
-            elif not self.bridge.profileIsSessionStarted(self.profile):
-                if not self.args.connect:
-                    log.error(_(u"Session for [{profile}] is not started, please start it before using jp, or use either --start-session or --connect option").format(profile=self.profile))
-                    self.quit(1)
-            else:
-                callback()
-                return
-
-
-        if not hasattr(self.args, 'connect'):
-            # a profile can be present without connect option (e.g. on profile creation/deletion)
-            return
-        elif self.args.connect is True:  # if connection is asked, we connect the profile
-            self.bridge.connect(self.profile, self.args.pwd, {}, lambda dummy: callback(), cant_connect)
-            self._auto_loop = True
-            return
-        else:
-            if not self.bridge.isConnected(self.profile):
-                log.error(_(u"Profile [{profile}] is not connected, please connect it before using jp, or use --connect option").format(profile=self.profile))
-                self.quit(1)
-
-        callback()
-
-    def get_full_jid(self, param_jid):
-        """Return the full jid if possible (add main resource when find a bare jid)"""
-        _jid = JID(param_jid)
-        if not _jid.resource:
-            #if the resource is not given, we try to add the main resource
-            main_resource = self.bridge.getMainResource(param_jid, self.profile)
-            if main_resource:
-                return "%s/%s" % (_jid.bare, main_resource)
-        return param_jid
-
-
-class CommandBase(object):
-
-    def __init__(self, host, name, use_profile=True, use_output=False, extra_outputs=None,
-                       need_connect=None, help=None, **kwargs):
-        """Initialise CommandBase
-
-        @param host: Jp instance
-        @param name(unicode): name of the new command
-        @param use_profile(bool): if True, add profile selection/connection commands
-        @param use_output(bool, unicode): if not False, add --output option
-        @param extra_outputs(dict): list of command specific outputs:
-            key is output name ("default" to use as main output)
-            value is a callable which will format the output (data will be used as only argument)
-            if a key already exists with normal outputs, the extra one will be used
-        @param need_connect(bool, None): True if profile connection is needed
-            False else (profile session must still be started)
-            None to set auto value (i.e. True if use_profile is set)
-            Can't be set if use_profile is False
-        @param help(unicode): help message to display
-        @param **kwargs: args passed to ArgumentParser
-            use_* are handled directly, they can be:
-            - use_progress(bool): if True, add progress bar activation option
-                progress* signals will be handled
-            - use_verbose(bool): if True, add verbosity option
-            - use_pubsub(bool): if True, add pubsub options
-                mandatory arguments are controlled by pubsub_req
-            - use_draft(bool): if True, add draft handling options
-            ** other arguments **
-            - pubsub_flags(iterable[unicode]): tuple of flags to set pubsub options, can be:
-                C.SERVICE: service is required
-                C.NODE: node is required
-                C.SINGLE_ITEM: only one item is allowed
-        @attribute need_loop(bool): to set by commands when loop is needed
-        """
-        self.need_loop = False # to be set by commands when loop is needed
-        try: # If we have subcommands, host is a CommandBase and we need to use host.host
-            self.host = host.host
-        except AttributeError:
-            self.host = host
-
-        # --profile option
-        parents = kwargs.setdefault('parents', set())
-        if use_profile:
-            #self.host.parents['profile'] is an ArgumentParser with profile connection arguments
-            if need_connect is None:
-                need_connect = True
-            parents.add(self.host.parents['profile' if need_connect else 'profile_session'])
-        else:
-            assert need_connect is None
-        self.need_connect = need_connect
-        # from this point, self.need_connect is None if connection is not needed at all
-        # False if session starting is needed, and True if full connection is needed
-
-        # --output option
-        if use_output:
-            if extra_outputs is None:
-                extra_outputs = {}
-            self.extra_outputs = extra_outputs
-            if use_output == True:
-                use_output = C.OUTPUT_TEXT
-            assert use_output in C.OUTPUT_TYPES
-            self._output_type = use_output
-            output_parent = argparse.ArgumentParser(add_help=False)
-            choices = set(self.host.getOutputChoices(use_output))
-            choices.update(extra_outputs)
-            if not choices:
-                raise exceptions.InternalError("No choice found for {} output type".format(use_output))
-            try:
-                default = self.host.default_output[use_output]
-            except KeyError:
-                if u'default' in choices:
-                    default = u'default'
-                elif u'simple' in choices:
-                    default = u'simple'
-                else:
-                    default = list(choices)[0]
-            output_parent.add_argument('--output', '-O', choices=sorted(choices), default=default, help=_(u"select output format (default: {})".format(default)))
-            output_parent.add_argument('--output-option', '--oo', type=unicode_decoder, action="append", dest='output_opts', default=[], help=_(u"output specific option"))
-            parents.add(output_parent)
-        else:
-            assert extra_outputs is None
-
-        self._use_pubsub = kwargs.pop('use_pubsub', False)
-        if self._use_pubsub:
-            flags = kwargs.pop('pubsub_flags', [])
-            defaults = kwargs.pop('pubsub_defaults', {})
-            parents.add(self.host.make_pubsub_group(flags, defaults))
-            self._pubsub_flags = flags
-
-        # other common options
-        use_opts = {k:v for k,v in kwargs.iteritems() if k.startswith('use_')}
-        for param, do_use in use_opts.iteritems():
-            opt=param[4:] # if param is use_verbose, opt is verbose
-            if opt not in self.host.parents:
-                raise exceptions.InternalError(u"Unknown parent option {}".format(opt))
-            del kwargs[param]
-            if do_use:
-                parents.add(self.host.parents[opt])
-
-        self.parser = host.subparsers.add_parser(name, help=help, **kwargs)
-        if hasattr(self, "subcommands"):
-            self.subparsers = self.parser.add_subparsers()
-        else:
-            self.parser.set_defaults(_cmd=self)
-        self.add_parser_options()
-
-    @property
-    def args(self):
-        return self.host.args
-
-    @property
-    def profile(self):
-        return self.host.profile
-
-    @property
-    def verbosity(self):
-        return self.host.verbosity
-
-    @property
-    def progress_id(self):
-        return self.host.progress_id
-
-    @progress_id.setter
-    def progress_id(self, value):
-        self.host.progress_id = value
-
-    def progressStartedHandler(self, uid, metadata, profile):
-        if profile != self.profile:
-            return
-        if self.progress_id is None:
-            # the progress started message can be received before the id
-            # so we keep progressStarted signals in cache to replay they
-            # when the progress_id is received
-            cache_data = (self.progressStartedHandler, uid, metadata, profile)
-            try:
-                self.host.progress_ids_cache.append(cache_data)
-            except AttributeError:
-                self.host.progress_ids_cache = [cache_data]
-        else:
-            if self.host.watch_progress and uid == self.progress_id:
-                self.onProgressStarted(metadata)
-                self.host.loop.call_later(PROGRESS_DELAY, self.progressUpdate)
-
-    def progressFinishedHandler(self, uid, metadata, profile):
-        if profile != self.profile:
-            return
-        if uid == self.progress_id:
-            try:
-                self.host.pbar.finish()
-            except AttributeError:
-                pass
-            self.onProgressFinished(metadata)
-            if self.host.quit_on_progress_end:
-                self.host.quitFromSignal()
-
-    def progressErrorHandler(self, uid, message, profile):
-        if profile != self.profile:
-            return
-        if uid == self.progress_id:
-            if self.args.progress:
-                self.disp('') # progress is not finished, so we skip a line
-            if self.host.quit_on_progress_end:
-                self.onProgressError(message)
-                self.host.quitFromSignal(1)
-
-    def progressUpdate(self):
-        """This method is continualy called to update the progress bar"""
-        data = self.host.bridge.progressGet(self.progress_id, self.profile)
-        if data:
-            try:
-                size = data['size']
-            except KeyError:
-                self.disp(_(u"file size is not known, we can't show a progress bar"), 1, error=True)
-                return False
-            if self.host.pbar is None:
-                #first answer, we must construct the bar
-                self.host.pbar = progressbar.ProgressBar(max_value=int(size),
-                                                         widgets=[_(u"Progress: "),progressbar.Percentage(),
-                                                         " ",
-                                                         progressbar.Bar(),
-                                                         " ",
-                                                         progressbar.FileTransferSpeed(),
-                                                         " ",
-                                                         progressbar.ETA()])
-                self.host.pbar.start()
-
-            self.host.pbar.update(int(data['position']))
-
-        elif self.host.pbar is not None:
-            return False
-
-        self.onProgressUpdate(data)
-
-        return True
-
-    def onProgressStarted(self, metadata):
-        """Called when progress has just started
-
-        can be overidden by a command
-        @param metadata(dict): metadata as sent by bridge.progressStarted
-        """
-        self.disp(_(u"Operation started"), 2)
-
-    def onProgressUpdate(self, metadata):
-        """Method called on each progress updata
-
-        can be overidden by a command to handle progress metadata
-        @para metadata(dict): metadata as returned by bridge.progressGet
-        """
-        pass
-
-    def onProgressFinished(self, metadata):
-        """Called when progress has just finished
-
-        can be overidden by a command
-        @param metadata(dict): metadata as sent by bridge.progressFinished
-        """
-        self.disp(_(u"Operation successfully finished"), 2)
-
-    def onProgressError(self, error_msg):
-        """Called when a progress failed
-
-        @param error_msg(unicode): error message as sent by bridge.progressError
-        """
-        self.disp(_(u"Error while doing operation: {}").format(error_msg), error=True)
-
-    def disp(self, msg, verbosity=0, error=False, no_lf=False):
-        return self.host.disp(msg, verbosity, error, no_lf)
-
-    def output(self, data):
-        try:
-            output_type = self._output_type
-        except AttributeError:
-            raise exceptions.InternalError(_(u'trying to use output when use_output has not been set'))
-        return self.host.output(output_type, self.args.output, self.extra_outputs, data)
-
-    def exitCb(self, msg=None):
-        """generic callback for success
-
-        optionally print a message, and quit
-        msg(None, unicode): if not None, print this message
-        """
-        if msg is not None:
-            self.disp(msg)
-        self.host.quit(C.EXIT_OK)
-
-    def errback(self, failure_, msg=None, exit_code=C.EXIT_ERROR):
-        """generic callback for errbacks
-
-        display failure_ then quit with generic error
-        @param failure_: arguments returned by errback
-        @param msg(unicode, None): message template
-            use {} if you want to display failure message
-        @param exit_code(int): shell exit code
-        """
-        if msg is None:
-            msg = _(u"error: {}")
-        self.disp(msg.format(failure_), error=True)
-        self.host.quit(exit_code)
-
-    def add_parser_options(self):
-        try:
-            subcommands = self.subcommands
-        except AttributeError:
-            # We don't have subcommands, the class need to implements add_parser_options
-            raise NotImplementedError
-
-        # now we add subcommands to ourself
-        for cls in subcommands:
-            cls(self)
-
-    def run(self):
-        """this method is called when a command is actually run
-
-        It set stuff like progression callbacks and profile connection
-        You should not overide this method: you should call self.start instead
-        """
-        # we keep a reference to run command, it may be useful e.g. for outputs
-        self.host.command = self
-        # host._need_loop is set here from our current value and not before
-        # as the need_loop decision must be taken only by then running command
-        self.host._need_loop = self.need_loop
-
-        try:
-            show_progress = self.args.progress
-        except AttributeError:
-            # the command doesn't use progress bar
-            pass
-        else:
-            if show_progress:
-                self.host.watch_progress = True
-            # we need to register the following signal even if we don't display the progress bar
-            self.host.bridge.register_signal("progressStarted", self.progressStartedHandler)
-            self.host.bridge.register_signal("progressFinished", self.progressFinishedHandler)
-            self.host.bridge.register_signal("progressError", self.progressErrorHandler)
-
-        if self.need_connect is not None:
-             self.host.connect_profile(self.connected)
-        else:
-            self.start()
-
-    def connected(self):
-        """this method is called when profile is connected (or session is started)
-
-        this method is only called when use_profile is True
-        most of time you should override self.start instead of this method, but if loop
-        if not always needed depending on your arguments, you may override this method,
-        but don't forget to call the parent one (i.e. this one) after self.need_loop is set
-        """
-        if not self.need_loop:
-            self.host.stop_loop()
-        self.start()
-
-    def start(self):
-        """This is the starting point of the command, this method should be overriden
-
-        at this point, profile are connected if needed
-        """
-        pass
-
-
-class CommandAnswering(CommandBase):
-    """Specialised commands which answer to specific actions
-
-    to manage action_types answer,
-    """
-    action_callbacks = {} # XXX: set managed action types in a dict here:
-                          # key is the action_type, value is the callable
-                          # which will manage the answer. profile filtering is
-                          # already managed when callback is called
-
-    def __init__(self, *args, **kwargs):
-        super(CommandAnswering, self).__init__(*args, **kwargs)
-        self.need_loop = True
-
-    def onActionNew(self, action_data, action_id, security_limit, profile):
-        if profile != self.profile:
-            return
-        try:
-            action_type = action_data['meta_type']
-        except KeyError:
-            try:
-                xml_ui = action_data["xmlui"]
-            except KeyError:
-                pass
-            else:
-                self.onXMLUI(xml_ui)
-        else:
-            try:
-                callback = self.action_callbacks[action_type]
-            except KeyError:
-                pass
-            else:
-                callback(action_data, action_id, security_limit, profile)
-
-    def onXMLUI(self, xml_ui):
-        """Display a dialog received from the backend.
-
-        @param xml_ui (unicode): dialog XML representation
-        """
-        # FIXME: we temporarily use ElementTree, but a real XMLUI managing module
-        #        should be available in the future
-        # TODO: XMLUI module
-        ui = ET.fromstring(xml_ui.encode('utf-8'))
-        dialog = ui.find("dialog")
-        if dialog is not None:
-            self.disp(dialog.findtext("message"), error=dialog.get("level") == "error")
-
-    def connected(self):
-        """Auto reply to confirmations requests"""
-        self.need_loop = True
-        super(CommandAnswering, self).connected()
-        self.host.bridge.register_signal("actionNew", self.onActionNew)
-        actions = self.host.bridge.actionsGet(self.profile)
-        for action_data, action_id, security_limit in actions:
-            self.onActionNew(action_data, action_id, security_limit, self.profile)
--- a/frontends/src/jp/cmd_account.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,132 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SAT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-"""This module permits to manage XMPP accounts using in-band registration (XEP-0077)"""
-
-from sat_frontends.jp.constants import Const as C
-from sat.core.log import getLogger
-log = getLogger(__name__)
-from sat.core.i18n import _
-from sat_frontends.jp import base
-from sat_frontends.tools import jid
-
-__commands__ = ["Account"]
-
-
-class AccountCreate(base.CommandBase):
-
-    def __init__(self, host):
-        super(AccountCreate, self).__init__(host, 'create', use_profile=False, use_verbose=True, help=_(u'create a XMPP account'))
-        self.need_loop = True
-
-    def add_parser_options(self):
-        self.parser.add_argument('jid', type=base.unicode_decoder, help=_(u'jid to create'))
-        self.parser.add_argument('password', type=base.unicode_decoder, help=_(u'password of the account'))
-        self.parser.add_argument('-p', '--profile', type=base.unicode_decoder, help=_(u"create a profile to use this account (default: don't create profile)"))
-        self.parser.add_argument('-e', '--email', type=base.unicode_decoder, default="", help=_(u"email (usage depends of XMPP server)"))
-        self.parser.add_argument('-H', '--host', type=base.unicode_decoder, default="", help=_(u"server host (IP address or domain, default: use localhost)"))
-        self.parser.add_argument('-P', '--port', type=int, default=0, help=_(u"server port (IP address or domain, default: use localhost)"))
-
-    def _setParamCb(self):
-        self.host.bridge.setParam("Password", self.args.password, "Connection", profile_key=self.args.profile, callback=self.host.quit, errback=self.errback)
-
-    def _session_started(self, dummy):
-        self.host.bridge.setParam("JabberID", self.args.jid, "Connection", profile_key=self.args.profile, callback=self._setParamCb, errback=self.errback)
-
-    def _profileCreateCb(self):
-        self.disp(_(u"profile created"), 1)
-        self.host.bridge.profileStartSession(self.args.password, self.args.profile, callback=self._session_started, errback=self.errback)
-
-    def _profileCreateEb(self, failure_):
-        self.disp(_(u"Can't create profile {profile} to associate with jid {jid}: {msg}").format(
-            profile = self.args.profile,
-            jid = self.args.jid,
-            msg = failure_), error=True)
-        self.host.quit(C.EXIT_BRIDGE_ERRBACK)
-
-    def accountNewCb(self):
-        self.disp(_(u"XMPP account created"), 1)
-        if self.args.profile is not None:
-            self.disp(_(u"creating profile"), 2)
-            self.host.bridge.profileCreate(self.args.profile, self.args.password, "", callback=self._profileCreateCb, errback=self._profileCreateEb)
-        else:
-            self.host.quit()
-
-    def accountNewEb(self, failure_):
-        self.disp(_(u"Can't create new account on server {host} with jid {jid}: {msg}").format(
-            host = self.args.host or u"localhost",
-            jid = self.args.jid,
-            msg = failure_), error=True)
-        self.host.quit(C.EXIT_BRIDGE_ERRBACK)
-
-    def start(self):
-        self.host.bridge.inBandAccountNew(self.args.jid, self.args.password, self.args.email, self.args.host, self.args.port,
-            callback=self.accountNewCb, errback=self.accountNewEb)
-
-
-
-class AccountModify(base.CommandBase):
-
-    def __init__(self, host):
-        super(AccountModify, self).__init__(host, 'modify', help=_(u'change password for XMPP account'))
-        self.need_loop = True
-
-    def add_parser_options(self):
-        self.parser.add_argument('password', type=base.unicode_decoder, help=_(u'new XMPP password'))
-
-    def start(self):
-        self.host.bridge.inBandPasswordChange(self.args.password, self.args.profile,
-            callback=self.host.quit, errback=self.errback)
-
-
-class AccountDelete(base.CommandBase):
-
-    def __init__(self, host):
-        super(AccountDelete, self).__init__(host, 'delete', help=_(u'delete a XMPP account'))
-        self.need_loop = True
-
-    def add_parser_options(self):
-        self.parser.add_argument('-f', '--force', action='store_true', help=_(u'delete account without confirmation'))
-
-    def _got_jid(self, jid_str):
-        jid_ = jid.JID(jid_str)
-        if not self.args.force:
-            message = (u"You are about to delete the XMPP account with jid {jid_}\n"
-                       u"This is the XMPP account of profile \"{profile}\"\n"
-                       u"Are you sure that you want to delete this account ?".format(
-                      jid_ = jid_,
-                      profile=self.profile
-                      ))
-            res = raw_input("{} (y/N)? ".format(message))
-            if res not in ("y", "Y"):
-                self.disp(_(u"Account deletion cancelled"))
-                self.host.quit(2)
-        self.host.bridge.inBandUnregister(jid_.domain, self.args.profile,
-            callback=self.host.quit, errback=self.errback)
-
-    def start(self):
-        self.host.bridge.asyncGetParamA("JabberID", "Connection", profile_key=self.profile,
-                                        callback=self._got_jid, errback=self.errback)
-
-
-class Account(base.CommandBase):
-    subcommands = (AccountCreate, AccountModify, AccountDelete)
-
-    def __init__(self, host):
-        super(Account, self).__init__(host, 'account', use_profile=False, help=(u'XMPP account management'))
--- a/frontends/src/jp/cmd_adhoc.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,138 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SAT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-import base
-from sat.core.i18n import _
-from functools import partial
-from sat_frontends.jp.constants import Const as C
-from sat_frontends.jp import xmlui_manager
-
-__commands__ = ["AdHoc"]
-
-FLAG_LOOP = 'LOOP'
-MAGIC_BAREJID = '@PROFILE_BAREJID@'
-
-
-class Remote(base.CommandBase):
-    def __init__(self, host):
-        super(Remote, self).__init__(host, 'remote', use_verbose=True, help=_(u'remote control a software'))
-
-    def add_parser_options(self):
-        self.parser.add_argument("software", type=str, help=_(u"software name"))
-        self.parser.add_argument("-j", "--jids", type=base.unicode_decoder, nargs='*', default=[], help=_(u"jids allowed to use the command"))
-        self.parser.add_argument("-g", "--groups", type=base.unicode_decoder, nargs='*', default=[], help=_(u"groups allowed to use the command"))
-        self.parser.add_argument("--forbidden-groups", type=base.unicode_decoder, nargs='*', default=[], help=_(u"groups that are *NOT* allowed to use the command"))
-        self.parser.add_argument("--forbidden-jids", type=base.unicode_decoder, nargs='*', default=[], help=_(u"jids that are *NOT* allowed to use the command"))
-        self.parser.add_argument("-l", "--loop", action="store_true", help=_(u"loop on the commands"))
-
-    def start(self):
-        name = self.args.software.lower()
-        flags = []
-        magics = {jid for jid in self.args.jids if jid.count('@')>1}
-        magics.add(MAGIC_BAREJID)
-        jids = set(self.args.jids).difference(magics)
-        if self.args.loop:
-            flags.append(FLAG_LOOP)
-        bus_name, methods = self.host.bridge.adHocDBusAddAuto(name, jids, self.args.groups, magics,
-                                                              self.args.forbidden_jids, self.args.forbidden_groups,
-                                                              flags, self.profile)
-        if not bus_name:
-            self.disp(_("No bus name found"), 1)
-            return
-        self.disp(_("Bus name found: [%s]" % bus_name), 1)
-        for method in methods:
-            path, iface, command = method
-            self.disp(_("Command found: (path:%(path)s, iface: %(iface)s) [%(command)s]" % {'path': path,
-                                                                                         'iface': iface,
-                                                                                         'command': command
-                                                                                         }),1)
-
-
-class Run(base.CommandBase):
-    """Run an Ad-Hoc command"""
-
-    def __init__(self, host):
-        super(Run, self).__init__(host, 'run', use_verbose=True, help=_(u'run an Ad-Hoc command'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument('-j', '--jid', type=base.unicode_decoder, default=u'', help=_(u"jid of the service (default: profile's server"))
-        self.parser.add_argument("-S", "--submit", action='append_const', const=xmlui_manager.SUBMIT, dest='workflow', help=_(u"submit form/page"))
-        self.parser.add_argument("-f",
-            "--field",
-            type=base.unicode_decoder,
-            action='append',
-            nargs=2,
-            dest='workflow',
-            metavar=(u"KEY", u"VALUE"),
-            help=_(u"field value"))
-        self.parser.add_argument('node', type=base.unicode_decoder, nargs='?', default=u'', help=_(u"node of the command (default: list commands)"))
-
-    def adHocRunCb(self, xmlui_raw):
-        xmlui = xmlui_manager.create(self.host, xmlui_raw)
-        workflow = self.args.workflow
-        xmlui.show(workflow)
-        if not workflow:
-            if xmlui.type == 'form':
-                xmlui.submitForm()
-            else:
-                self.host.quit()
-
-    def start(self):
-        self.host.bridge.adHocRun(
-            self.args.jid,
-            self.args.node,
-            self.profile,
-            callback=self.adHocRunCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get ad-hoc commands list: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class List(base.CommandBase):
-    """Run an Ad-Hoc command"""
-
-    def __init__(self, host):
-        super(List, self).__init__(host, 'list', use_verbose=True, help=_(u'list Ad-Hoc commands of a service'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument('-j', '--jid', type=base.unicode_decoder, default=u'', help=_(u"jid of the service (default: profile's server"))
-
-    def adHocListCb(self, xmlui_raw):
-        xmlui = xmlui_manager.create(self.host, xmlui_raw)
-        xmlui.readonly = True
-        xmlui.show()
-        self.host.quit()
-
-    def start(self):
-        self.host.bridge.adHocList(
-            self.args.jid,
-            self.profile,
-            callback=self.adHocListCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get ad-hoc commands list: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class AdHoc(base.CommandBase):
-    subcommands = (Run, List, Remote)
-
-    def __init__(self, host):
-        super(AdHoc, self).__init__(host, 'ad-hoc', use_profile=False, help=_('Ad-hoc commands'))
--- a/frontends/src/jp/cmd_avatar.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SAT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-
-import base
-import os
-import os.path
-from sat.core.i18n import _
-from sat_frontends.jp.constants import Const as C
-from sat.tools import config
-import subprocess
-
-
-__commands__ = ["Avatar"]
-DISPLAY_CMD = ['xv', 'display', 'gwenview', 'showtell']
-
-
-class Set(base.CommandBase):
-    def __init__(self, host):
-        super(Set, self).__init__(host, 'set', use_verbose=True, help=_('set avatar of the profile'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("image_path", type=str, help=_("path to the image to upload"))
-
-    def start(self):
-        """Send files to jabber contact"""
-        path = self.args.image_path
-        if not os.path.exists(path):
-            self.disp(_(u"file [{}] doesn't exist !").format(path), error=True)
-            self.host.quit(1)
-        path = os.path.abspath(path)
-        self.host.bridge.avatarSet(path, self.profile, callback=self._avatarCb, errback=self._avatarEb)
-
-    def _avatarCb(self):
-        self.disp(_("avatar has been set"), 1)
-        self.host.quit()
-
-    def _avatarEb(self, failure_):
-        self.disp(_("error while uploading avatar: {msg}").format(msg=failure_), error=True)
-        self.host.quit(C.EXIT_ERROR)
-
-
-class Get(base.CommandBase):
-
-    def __init__(self, host):
-        super(Get, self).__init__(host, 'get', use_verbose=True, help=_('retrieve avatar of an entity'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("jid", type=base.unicode_decoder, help=_("entity"))
-        self.parser.add_argument("-s", "--show", action="store_true", help=_(u"show avatar"))
-
-    def showImage(self, path):
-        sat_conf = config.parseMainConf()
-        cmd = config.getConfig(sat_conf, 'jp', 'image_cmd')
-        cmds = [cmd] + DISPLAY_CMD if cmd else DISPLAY_CMD
-        for cmd in cmds:
-            try:
-                ret = subprocess.call([cmd] + [path])
-            except OSError:
-                pass
-            else:
-                if ret == 0:
-                    break
-        else:
-            # didn't worked with commands, we try our luck with webbrowser
-            # in some cases, webbrowser can actually open the associated display program
-            import webbrowser
-            webbrowser.open(path)
-
-    def _avatarGetCb(self, avatar_path):
-        if not avatar_path:
-            self.disp(_(u"No avatar found."), 1)
-            self.host.quit(C.EXIT_NOT_FOUND)
-
-        self.disp(avatar_path)
-        if self.args.show:
-            self.showImage(avatar_path)
-
-        self.host.quit()
-
-    def _avatarGetEb(self, failure_):
-        self.disp(_("error while getting avatar: {msg}").format(msg=failure_), error=True)
-        self.host.quit(C.EXIT_ERROR)
-
-    def start(self):
-        self.host.bridge.avatarGet(self.args.jid, False, False, self.profile, callback=self._avatarGetCb, errback=self._avatarGetEb)
-
-
-class Avatar(base.CommandBase):
-    subcommands = (Set, Get)
-
-    def __init__(self, host):
-        super(Avatar, self).__init__(host, 'avatar', use_profile=False, help=_('avatar uploading/retrieving'))
--- a/frontends/src/jp/cmd_blog.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,691 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SàT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-
-import base
-from sat.core.i18n import _
-from sat_frontends.jp.constants import Const as C
-from sat_frontends.jp import common
-from sat.tools.common.ansi import ANSI as A
-from sat.tools.common import data_objects
-from sat.tools.common import uri
-from sat.tools import config
-from ConfigParser import NoSectionError, NoOptionError
-from functools import partial
-import json
-import sys
-import os.path
-import os
-import time
-import tempfile
-import subprocess
-import codecs
-from sat.tools.common import data_format
-
-__commands__ = ["Blog"]
-
-SYNTAX_XHTML = u'xhtml'
-# extensions to use with known syntaxes
-SYNTAX_EXT = {
-    '': 'txt', # used when the syntax is not found
-    SYNTAX_XHTML: "xhtml",
-    "markdown": "md"
-    }
-
-
-CONF_SYNTAX_EXT = u'syntax_ext_dict'
-BLOG_TMP_DIR = u"blog"
-# key to remove from metadata tmp file if they exist
-KEY_TO_REMOVE_METADATA = ('id','content', 'content_xhtml', 'comments_node', 'comments_service', 'updated')
-
-URL_REDIRECT_PREFIX = 'url_redirect_'
-INOTIFY_INSTALL = '"pip install inotify"'
-MB_KEYS = (u"id",
-           u"url",
-           u"atom_id",
-           u"updated",
-           u"published",
-           u"language",
-           u"comments",  # this key is used for all comments* keys
-           u"tags",  # this key is used for all tag* keys
-           u"author",
-           u"author_jid",
-           u"author_email",
-           u"author_jid_verified",
-           u"content",
-           u"content_xhtml",
-           u"title",
-           u"title_xhtml",
-           )
-OUTPUT_OPT_NO_HEADER = u'no-header'
-
-
-def guessSyntaxFromPath(host, sat_conf, path):
-    """Return syntax guessed according to filename extension
-
-    @param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration
-    @param path(str): path to the content file
-    @return(unicode): syntax to use
-    """
-    # we first try to guess syntax with extension
-    ext = os.path.splitext(path)[1][1:] # we get extension without the '.'
-    if ext:
-        for k,v in SYNTAX_EXT.iteritems():
-            if k and ext == v:
-                return k
-
-    # if not found, we use current syntax
-    return host.bridge.getParamA("Syntax", "Composition", "value", host.profile)
-
-
-class BlogPublishCommon(object):
-    """handle common option for publising commands (Set and Edit)"""
-
-    def add_parser_options(self):
-        self.parser.add_argument("-T", '--title', type=base.unicode_decoder, help=_(u"title of the item"))
-        self.parser.add_argument("-t", '--tag', type=base.unicode_decoder, action='append', help=_(u"tag (category) of your item"))
-        self.parser.add_argument("-C", "--comments", action='store_true', help=_(u"enable comments"))
-        self.parser.add_argument("-S", '--syntax', type=base.unicode_decoder, help=_(u"syntax to use (default: get profile's default syntax)"))
-
-    def setMbDataContent(self, content, mb_data):
-        if self.args.syntax is None:
-            # default syntax has been used
-            mb_data['content_rich'] =  content
-        elif self.current_syntax == SYNTAX_XHTML:
-            mb_data['content_xhtml'] = content
-        else:
-            mb_data['content_xhtml'] = self.host.bridge.syntaxConvert(content, self.current_syntax, SYNTAX_XHTML, False, self.profile)
-
-    def setMbDataFromArgs(self, mb_data):
-        """set microblog metadata according to command line options
-
-        if metadata already exist, it will be overwritten
-        """
-        mb_data['allow_comments'] = C.boolConst(self.args.comments)
-        if self.args.tag:
-            data_format.iter2dict('tag', self.args.tag, mb_data, check_conflict=False)
-        if self.args.title is not None:
-            mb_data['title'] = self.args.title
-
-
-class Set(base.CommandBase, BlogPublishCommon):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.SINGLE_ITEM},
-                                  help=_(u'publish a new blog item or update an existing one'))
-        BlogPublishCommon.__init__(self)
-        self.need_loop=True
-
-    def add_parser_options(self):
-        BlogPublishCommon.add_parser_options(self)
-
-    def mbSendCb(self):
-        self.disp(u"Item published")
-        self.host.quit(C.EXIT_OK)
-
-    def start(self):
-        self.pubsub_item = self.args.item
-        mb_data = {}
-        self.setMbDataFromArgs(mb_data)
-        content = codecs.getreader('utf-8')(sys.stdin).read()
-        self.setMbDataContent(content, mb_data)
-
-        self.host.bridge.mbSend(
-            self.args.service,
-            self.args.node,
-            mb_data,
-            self.profile,
-            callback=self.exitCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't send item: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class Get(base.CommandBase):
-    TEMPLATE = u"blog/articles.html"
-
-    def __init__(self, host):
-        extra_outputs = {'default': self.default_output,
-                         'fancy': self.fancy_output}
-        base.CommandBase.__init__(self, host, 'get', use_verbose=True, use_pubsub=True, pubsub_flags={C.MULTI_ITEMS},
-                                  use_output=C.OUTPUT_COMPLEX, extra_outputs=extra_outputs, help=_(u'get blog item(s)'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        # TODO: a key(s) argument to select keys to display
-        self.parser.add_argument("-k", "--key", type=base.unicode_decoder, action='append', dest='keys',
-                                 help=_(u"microblog data key(s) to display (default: depend of verbosity)"))
-        # TODO: add MAM filters
-
-    def template_data_mapping(self, data):
-        return {u'items': data_objects.BlogItems(data)}
-
-    def format_comments(self, item, keys):
-        comments_data = data_format.dict2iterdict(u'comments', item, (u'node', u'service'), pop=True)
-        lines = []
-        for data in comments_data:
-            lines.append(data[u'comments'])
-            for k in (u'node', u'service'):
-                if OUTPUT_OPT_NO_HEADER in self.args.output_opts:
-                    header = u''
-                else:
-                    header = C.A_HEADER + k + u': ' + A.RESET
-                lines.append(header + data[k])
-        return u'\n'.join(lines)
-
-    def format_tags(self, item, keys):
-        tags = data_format.dict2iter('tag', item, pop=True)
-        return u', '.join(tags)
-
-    def format_updated(self, item, keys):
-        return self.format_time(item['updated'])
-
-    def format_published(self, item, keys):
-        return self.format_time(item['published'])
-
-    def format_url(self, item, keys):
-        return uri.buildXMPPUri(u'pubsub',
-                                subtype=u'microblog',
-                                path=self.metadata[u'service'],
-                                node=self.metadata[u'node'],
-                                item=item[u'id'])
-
-    def get_keys(self):
-        """return keys to display according to verbosity or explicit key request"""
-        verbosity = self.args.verbose
-        if self.args.keys:
-            if not set(MB_KEYS).issuperset(self.args.keys):
-                self.disp(u"following keys are invalid: {invalid}.\n"
-                          u"Valid keys are: {valid}.".format(
-                          invalid = u', '.join(set(self.args.keys).difference(MB_KEYS)),
-                          valid = u', '.join(sorted(MB_KEYS))),
-                          error=True)
-                self.host.quit(C.EXIT_BAD_ARG)
-            return self.args.keys
-        else:
-            if verbosity == 0:
-                return (u'title', u'content')
-            elif verbosity == 1:
-                return (u"title", u"tags", u"author", u"author_jid", u"author_email", u"author_jid_verified", u"published", u"updated", u"content")
-            else:
-                return MB_KEYS
-
-    def default_output(self, data):
-        """simple key/value output"""
-        items, self.metadata = data
-        keys = self.get_keys()
-
-        # k_cb use format_[key] methods for complex formattings
-        k_cb = {}
-        for k in keys:
-            try:
-                callback = getattr(self, "format_" + k)
-            except AttributeError:
-                pass
-            else:
-                k_cb[k] = callback
-        for idx, item in enumerate(items):
-            for k in keys:
-                if k not in item and k not in k_cb:
-                    continue
-                if OUTPUT_OPT_NO_HEADER in self.args.output_opts:
-                    header = ''
-                else:
-                    header = u"{k_fmt}{key}:{k_fmt_e} {sep}".format(
-                    k_fmt = C.A_HEADER,
-                    key = k,
-                    k_fmt_e = A.RESET,
-                    sep = u'\n' if 'content' in k else u'')
-                value = k_cb[k](item, keys) if k in k_cb else item[k]
-                self.disp(header + value)
-            # we want a separation line after each item but the last one
-            if idx < len(items)-1:
-                print(u'')
-
-    def format_time(self, timestamp):
-        """return formatted date for timestamp
-
-        @param timestamp(str,int,float): unix timestamp
-        @return (unicode): formatted date
-        """
-        fmt = u"%d/%m/%Y %H:%M:%S"
-        return time.strftime(fmt, time.localtime(float(timestamp)))
-
-    def fancy_output(self, data):
-        """display blog is a nice to read way
-
-        this output doesn't use keys filter
-        """
-        # thanks to http://stackoverflow.com/a/943921
-        rows, columns = map(int, os.popen('stty size', 'r').read().split())
-        items, metadata = data
-        verbosity = self.args.verbose
-        sep = A.color(A.FG_BLUE, columns * u'▬')
-        if items:
-            print(u'\n' + sep  + '\n')
-
-        for idx, item in enumerate(items):
-            title = item.get(u'title')
-            if verbosity > 0:
-                author = item[u'author']
-                published, updated = item[u'published'], item.get('updated')
-            else:
-                author = published = updated = None
-            if verbosity > 1:
-                tags = list(data_format.dict2iter('tag', item, pop=True))
-            else:
-                tags = None
-            content = item.get(u'content')
-
-            if title:
-                print(A.color(A.BOLD, A.FG_CYAN, item[u'title']))
-            meta = []
-            if author:
-                meta.append(A.color(A.FG_YELLOW, author))
-            if published:
-                meta.append(A.color(A.FG_YELLOW, u'on ', self.format_time(published)))
-            if updated != published:
-                 meta.append(A.color(A.FG_YELLOW, u'(updated on ', self.format_time(updated), u')'))
-            print(u' '.join(meta))
-            if tags:
-                print(A.color(A.FG_MAGENTA, u', '.join(tags)))
-            if (title or tags) and content:
-                print("")
-            if content:
-                self.disp(content)
-
-            print(u'\n' + sep  + '\n')
-
-
-    def mbGetCb(self, mb_result):
-        self.output(mb_result)
-        self.host.quit(C.EXIT_OK)
-
-    def mbGetEb(self, failure_):
-        self.disp(u"can't get blog items: {reason}".format(
-            reason=failure_), error=True)
-        self.host.quit(C.EXIT_BRIDGE_ERRBACK)
-
-    def start(self):
-        self.host.bridge.mbGet(
-            self.args.service,
-            self.args.node,
-            self.args.max,
-            self.args.items,
-            {},
-            self.profile,
-            callback=self.mbGetCb,
-            errback=self.mbGetEb)
-
-
-class Edit(base.CommandBase, BlogPublishCommon, common.BaseEdit):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'edit', use_pubsub=True, pubsub_flags={C.SINGLE_ITEM},
-                                  use_draft=True, use_verbose=True, help=_(u'edit an existing or new blog post'))
-        BlogPublishCommon.__init__(self)
-        common.BaseEdit.__init__(self, self.host, BLOG_TMP_DIR, use_metadata=True)
-
-    @property
-    def current_syntax(self):
-        if self._current_syntax is None:
-            self._current_syntax = self.host.bridge.getParamA("Syntax", "Composition", "value", self.profile)
-        return self._current_syntax
-
-    def add_parser_options(self):
-        BlogPublishCommon.add_parser_options(self)
-        self.parser.add_argument("-P", "--preview", action="store_true", help=_(u"launch a blog preview in parallel"))
-
-    def buildMetadataFile(self, content_file_path, mb_data=None):
-        """Build a metadata file using json
-
-        The file is named after content_file_path, with extension replaced by _metadata.json
-        @param content_file_path(str): path to the temporary file which will contain the body
-        @param mb_data(dict, None): microblog metadata (for existing items)
-        @return (tuple[dict, str]): merged metadata put originaly in metadata file
-            and path to temporary metadata file
-        """
-        # we first construct metadata from edited item ones and CLI argumments
-        # or re-use the existing one if it exists
-        meta_file_path = os.path.splitext(content_file_path)[0] + common.METADATA_SUFF
-        if os.path.exists(meta_file_path):
-            self.disp(u"Metadata file already exists, we re-use it")
-            try:
-                with open(meta_file_path, 'rb') as f:
-                    mb_data = json.load(f)
-            except (OSError, IOError, ValueError) as e:
-                self.disp(u"Can't read existing metadata file at {path}, aborting: {reason}".format(
-                    path=meta_file_path, reason=e), error=True)
-                self.host.quit(1)
-        else:
-            mb_data = {} if mb_data is None else mb_data.copy()
-
-        # in all cases, we want to remove unwanted keys
-        for key in KEY_TO_REMOVE_METADATA:
-            try:
-                del mb_data[key]
-            except KeyError:
-                pass
-        # and override metadata with command-line arguments
-        self.setMbDataFromArgs(mb_data)
-
-        # then we create the file and write metadata there, as JSON dict
-        # XXX: if we port jp one day on Windows, O_BINARY may need to be added here
-        with os.fdopen(os.open(meta_file_path, os.O_RDWR | os.O_CREAT | os.O_TRUNC,0o600), 'w+b') as f:
-            # we need to use an intermediate unicode buffer to write to the file unicode without escaping characters
-            unicode_dump = json.dumps(mb_data, ensure_ascii=False, indent=4, separators=(',', ': '), sort_keys=True)
-            f.write(unicode_dump.encode('utf-8'))
-
-        return mb_data, meta_file_path
-
-    def edit(self, content_file_path, content_file_obj,
-             mb_data=None):
-        """Edit the file contening the content using editor, and publish it"""
-        # we first create metadata file
-        meta_ori, meta_file_path = self.buildMetadataFile(content_file_path, mb_data)
-
-        # do we need a preview ?
-        if self.args.preview:
-            self.disp(u"Preview requested, launching it", 1)
-            # we redirect outputs to /dev/null to avoid console pollution in editor
-            # if user wants to see messages, (s)he can call "blog preview" directly
-            DEVNULL = open(os.devnull, 'wb')
-            subprocess.Popen([sys.argv[0], "blog", "preview", "--inotify", "true", "-p", self.profile, content_file_path], stdout=DEVNULL, stderr=subprocess.STDOUT)
-
-        # we launch editor
-        self.runEditor("blog_editor_args", content_file_path, content_file_obj, meta_file_path=meta_file_path, meta_ori=meta_ori)
-
-    def publish(self, content, mb_data):
-        self.setMbDataContent(content, mb_data)
-
-        if self.pubsub_item is not None:
-            mb_data['id'] = self.pubsub_item
-
-        self.host.bridge.mbSend(self.pubsub_service, self.pubsub_node, mb_data, self.profile)
-        self.disp(u"Blog item published")
-
-
-    def getTmpSuff(self):
-        # we get current syntax to determine file extension
-        return SYNTAX_EXT.get(self.current_syntax, SYNTAX_EXT[''])
-
-    def getItemData(self, service, node, item):
-        items = [item] if item is not None else []
-        mb_data =  self.host.bridge.mbGet(service, node, 1, items, {}, self.profile)[0][0]
-        try:
-            content = mb_data['content_xhtml']
-        except KeyError:
-            content = mb_data['content']
-            if content:
-                content = self.host.bridge.syntaxConvert(content, 'text', SYNTAX_XHTML, False, self.profile)
-        if content and self.current_syntax != SYNTAX_XHTML:
-            content = self.host.bridge.syntaxConvert(content, SYNTAX_XHTML, self.current_syntax, False, self.profile)
-        if content and self.current_syntax == SYNTAX_XHTML:
-            try:
-                from lxml import etree
-            except ImportError:
-                self.disp(_(u"You need lxml to edit pretty XHTML"))
-            else:
-                parser = etree.XMLParser(remove_blank_text=True)
-                root = etree.fromstring(content, parser)
-                content = etree.tostring(root, encoding=unicode, pretty_print=True)
-
-        return content, mb_data, mb_data['id']
-
-    def start(self):
-        # if there are user defined extension, we use them
-        SYNTAX_EXT.update(config.getConfig(self.sat_conf, 'jp', CONF_SYNTAX_EXT, {}))
-        self._current_syntax = self.args.syntax
-        if self._current_syntax is not None:
-            try:
-                self._current_syntax = self.args.syntax = self.host.bridge.syntaxGet(self.current_syntax)
-            except Exception as e:
-                if "NotFound" in unicode(e):  # FIXME: there is not good way to check bridge errors
-                    self.parser.error(_(u"unknown syntax requested ({syntax})").format(syntax=self.args.syntax))
-                else:
-                    raise e
-
-        self.pubsub_service, self.pubsub_node, self.pubsub_item, content_file_path, content_file_obj, mb_data = self.getItemPath()
-
-        self.edit(content_file_path, content_file_obj, mb_data=mb_data)
-
-
-class Preview(base.CommandBase):
-    # TODO: need to be rewritten with template output
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'preview', use_verbose=True, help=_(u'preview a blog content'))
-
-    def add_parser_options(self):
-        self.parser.add_argument("--inotify", type=str, choices=('auto', 'true', 'false'), default=u'auto', help=_(u"use inotify to handle preview"))
-        self.parser.add_argument("file", type=base.unicode_decoder, nargs='?', default=u'current', help=_(u"path to the content file"))
-
-    def showPreview(self):
-        # we implement showPreview here so we don't have to import webbrowser and urllib
-        # when preview is not used
-        url = 'file:{}'.format(self.urllib.quote(self.preview_file_path))
-        self.webbrowser.open_new_tab(url)
-
-    def _launchPreviewExt(self, cmd_line, opt_name):
-        url = 'file:{}'.format(self.urllib.quote(self.preview_file_path))
-        args = common.parse_args(self.host, cmd_line, url=url, preview_file=self.preview_file_path)
-        if not args:
-            self.disp(u"Couln't find command in \"{name}\", abording".format(name=opt_name), error=True)
-            self.host.quit(1)
-        subprocess.Popen(args)
-
-    def openPreviewExt(self):
-        self._launchPreviewExt(self.open_cb_cmd, "blog_preview_open_cmd")
-
-    def updatePreviewExt(self):
-        self._launchPreviewExt(self.update_cb_cmd, "blog_preview_update_cmd")
-
-    def updateContent(self):
-        with open(self.content_file_path, 'rb') as f:
-            content = f.read().decode('utf-8-sig')
-            if content and self.syntax != SYNTAX_XHTML:
-                # we use safe=True because we want to have a preview as close as possible to what the
-                # people will see
-                content = self.host.bridge.syntaxConvert(content, self.syntax, SYNTAX_XHTML, True, self.profile)
-
-        xhtml = (u'<html xmlns="http://www.w3.org/1999/xhtml">' +
-                 u'<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /></head>'+
-                 '<body>{}</body>' +
-                 u'</html>').format(content)
-
-        with open(self.preview_file_path, 'wb') as f:
-            f.write(xhtml.encode('utf-8'))
-
-    def start(self):
-        import webbrowser
-        import urllib
-        self.webbrowser, self.urllib = webbrowser, urllib
-
-        if self.args.inotify != 'false':
-            try:
-                import inotify.adapters
-                import inotify.constants
-                from inotify.calls import InotifyError
-            except ImportError:
-                if self.args.inotify == 'auto':
-                    inotify = None
-                    self.disp(u'inotify module not found, deactivating feature. You can install it with {install}'.format(install=INOTIFY_INSTALL))
-                else:
-                    self.disp(u"inotify not found, can't activate the feature! Please install it with {install}".format(install=INOTIFY_INSTALL), error=True)
-                    self.host.quit(1)
-            else:
-                # we deactivate logging in inotify, which is quite annoying
-                try:
-                    inotify.adapters._LOGGER.setLevel(40)
-                except AttributeError:
-                    self.disp(u"Logger doesn't exists, inotify may have chanded", error=True)
-        else:
-            inotify=None
-
-        sat_conf = config.parseMainConf()
-        SYNTAX_EXT.update(config.getConfig(sat_conf, 'jp', CONF_SYNTAX_EXT, {}))
-
-        try:
-            self.open_cb_cmd = config.getConfig(sat_conf, 'jp', "blog_preview_open_cmd", Exception)
-        except (NoOptionError, NoSectionError):
-            self.open_cb_cmd = None
-            open_cb = self.showPreview
-        else:
-            open_cb = self.openPreviewExt
-
-        self.update_cb_cmd = config.getConfig(sat_conf, 'jp', "blog_preview_update_cmd", self.open_cb_cmd)
-        if self.update_cb_cmd is None:
-            update_cb = self.showPreview
-        else:
-            update_cb = self.updatePreviewExt
-
-        # which file do we need to edit?
-        if self.args.file == 'current':
-            self.content_file_path = self.getCurrentFile(sat_conf, self.profile)
-        else:
-            self.content_file_path = os.path.abspath(self.args.file)
-
-        self.syntax = self.guessSyntaxFromPath(sat_conf, self.content_file_path)
-
-
-        # at this point the syntax is converted, we can display the preview
-        preview_file = tempfile.NamedTemporaryFile(suffix='.xhtml', delete=False)
-        self.preview_file_path = preview_file.name
-        preview_file.close()
-        self.updateContent()
-
-        if inotify is None:
-            # XXX: we don't delete file automatically because browser need it (and webbrowser.open can return before it is read)
-            self.disp(u'temporary file created at {}\nthis file will NOT BE DELETED AUTOMATICALLY, please delete it yourself when you have finished'.format(self.preview_file_path))
-            open_cb()
-        else:
-            open_cb()
-            i = inotify.adapters.Inotify(block_duration_s=60) # no need for 1 s duraction, inotify drive actions here
-
-            def add_watch():
-                i.add_watch(self.content_file_path, mask=inotify.constants.IN_CLOSE_WRITE |
-                                                         inotify.constants.IN_DELETE_SELF |
-                                                         inotify.constants.IN_MOVE_SELF)
-            add_watch()
-
-            try:
-                for event in i.event_gen():
-                    if event is not None:
-                        self.disp(u"Content updated", 1)
-                        if {"IN_DELETE_SELF", "IN_MOVE_SELF"}.intersection(event[1]):
-                            self.disp(u"{} event catched, changing the watch".format(", ".join(event[1])), 2)
-                            try:
-                                add_watch()
-                            except InotifyError:
-                                # if the new file is not here yet we can have an error
-                                # as a workaround, we do a little rest
-                                time.sleep(1)
-                                add_watch()
-                        self.updateContent()
-                        update_cb()
-            except InotifyError:
-                self.disp(u"Can't catch inotify events, as the file been deleted?", error=True)
-            finally:
-                os.unlink(self.preview_file_path)
-                try:
-                    i.remove_watch(self.content_file_path)
-                except InotifyError:
-                    pass
-
-
-class Import(base.CommandAnswering):
-    def __init__(self, host):
-        super(Import, self).__init__(host, 'import', use_pubsub=True, use_progress=True, help=_(u'import an external blog'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("importer", type=base.unicode_decoder, nargs='?', help=_(u"importer name, nothing to display importers list"))
-        self.parser.add_argument('--host', type=base.unicode_decoder, help=_(u"original blog host"))
-        self.parser.add_argument('--no-images-upload', action='store_true', help=_(u"do *NOT* upload images (default: do upload images)"))
-        self.parser.add_argument('--upload-ignore-host', help=_(u"do not upload images from this host (default: upload all images)"))
-        self.parser.add_argument("--ignore-tls-errors", action="store_true", help=_("ignore invalide TLS certificate for uploads"))
-        self.parser.add_argument('-o', '--option', action='append', nargs=2, default=[], metavar=(u'NAME', u'VALUE'),
-            help=_(u"importer specific options (see importer description)"))
-        self.parser.add_argument("location", type=base.unicode_decoder, nargs='?',
-            help=_(u"importer data location (see importer description), nothing to show importer description"))
-
-    def onProgressStarted(self, metadata):
-        self.disp(_(u'Blog upload started'),2)
-
-    def onProgressFinished(self, metadata):
-        self.disp(_(u'Blog uploaded successfully'),2)
-        redirections = {k[len(URL_REDIRECT_PREFIX):]:v for k,v in metadata.iteritems()
-            if k.startswith(URL_REDIRECT_PREFIX)}
-        if redirections:
-            conf = u'\n'.join([
-                u'url_redirections_profile = {}'.format(self.profile),
-                u"url_redirections_dict = {}".format(
-                # we need to add ' ' before each new line and to double each '%' for ConfigParser
-                u'\n '.join(json.dumps(redirections, indent=1, separators=(',',': ')).replace(u'%', u'%%').split(u'\n'))),
-                ])
-            self.disp(_(u'\nTo redirect old URLs to new ones, put the following lines in your sat.conf file, in [libervia] section:\n\n{conf}'.format(conf=conf)))
-
-    def onProgressError(self, error_msg):
-        self.disp(_(u'Error while uploading blog: {}').format(error_msg),error=True)
-
-    def error(self, failure):
-        self.disp(_("Error while trying to upload a blog: {reason}").format(reason=failure), error=True)
-        self.host.quit(1)
-
-    def start(self):
-        if self.args.location is None:
-            for name in ('option', 'service', 'no_images_upload'):
-                if getattr(self.args, name):
-                    self.parser.error(_(u"{name} argument can't be used without location argument").format(name=name))
-            if self.args.importer is None:
-                self.disp(u'\n'.join([u'{}: {}'.format(name, desc) for name, desc in self.host.bridge.blogImportList()]))
-            else:
-                try:
-                    short_desc, long_desc = self.host.bridge.blogImportDesc(self.args.importer)
-                except Exception as e:
-                    msg = [l for l in unicode(e).split('\n') if l][-1] # we only keep the last line
-                    self.disp(msg)
-                    self.host.quit(1)
-                else:
-                    self.disp(u"{name}: {short_desc}\n\n{long_desc}".format(name=self.args.importer, short_desc=short_desc, long_desc=long_desc))
-            self.host.quit()
-        else:
-            # we have a location, an import is requested
-            options = {key: value for key, value in self.args.option}
-            if self.args.host:
-                options['host'] = self.args.host
-            if self.args.ignore_tls_errors:
-                options['ignore_tls_errors'] = C.BOOL_TRUE
-            if self.args.no_images_upload:
-                options['upload_images'] = C.BOOL_FALSE
-                if self.args.upload_ignore_host:
-                    self.parser.error(u"upload-ignore-host option can't be used when no-images-upload is set")
-            elif self.args.upload_ignore_host:
-                options['upload_ignore_host'] = self.args.upload_ignore_host
-            def gotId(id_):
-                self.progress_id = id_
-            self.host.bridge.blogImport(self.args.importer, self.args.location, options, self.args.service, self.args.node, self.profile,
-                callback=gotId, errback=self.error)
-
-
-class Blog(base.CommandBase):
-    subcommands = (Set, Get, Edit, Preview, Import)
-
-    def __init__(self, host):
-        super(Blog, self).__init__(host, 'blog', use_profile=False, help=_('blog/microblog management'))
--- a/frontends/src/jp/cmd_bookmarks.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,114 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SAT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-import base
-from sat.core.i18n import _
-
-__commands__ = ["Bookmarks"]
-
-STORAGE_LOCATIONS = ('local', 'private', 'pubsub')
-TYPES = ('muc', 'url')
-
-class BookmarksCommon(base.CommandBase):
-    """Class used to group common options of bookmarks subcommands"""
-
-    def add_parser_options(self, location_default='all'):
-        self.parser.add_argument('-l', '--location', type=str, choices=(location_default,) + STORAGE_LOCATIONS, default=location_default, help=_("storage location (default: %(default)s)"))
-        self.parser.add_argument('-t', '--type', type=str, choices=TYPES, default=TYPES[0], help=_("bookmarks type (default: %(default)s)"))
-
-    def _errback(self, failure):
-        print (("Something went wrong: [%s]") % failure)
-        self.host.quit(1)
-
-class BookmarksList(BookmarksCommon):
-
-    def __init__(self, host):
-        super(BookmarksList, self).__init__(host, 'list', help=_('list bookmarks'))
-
-    def start(self):
-        data = self.host.bridge.bookmarksList(self.args.type, self.args.location, self.host.profile)
-        mess = []
-        for location in STORAGE_LOCATIONS:
-            if not data[location]:
-                continue
-            loc_mess = []
-            loc_mess.append(u"%s:" % location)
-            book_mess = []
-            for book_link, book_data in data[location].items():
-                name = book_data.get('name')
-                autojoin = book_data.get('autojoin', 'false') == 'true'
-                nick = book_data.get('nick')
-                book_mess.append(u"\t%s[%s%s]%s" % ((name+' ') if name else '',
-                                                 book_link,
-                                                 u' (%s)' % nick if nick else '',
-                                                 u' (*)' if autojoin else ''))
-            loc_mess.append(u'\n'.join(book_mess))
-            mess.append(u'\n'.join(loc_mess))
-
-        print u'\n\n'.join(mess)
-
-
-class BookmarksRemove(BookmarksCommon):
-
-    def __init__(self, host):
-        super(BookmarksRemove, self).__init__(host, 'remove', help=_('remove a bookmark'))
-        self.need_loop = True
-
-    def add_parser_options(self):
-        super(BookmarksRemove, self).add_parser_options()
-        self.parser.add_argument('bookmark', type=base.unicode_decoder, help=_('jid (for muc bookmark) or url of to remove'))
-
-    def start(self):
-        self.host.bridge.bookmarksRemove(self.args.type, self.args.bookmark, self.args.location, self.host.profile, callback = lambda: self.host.quit(), errback=self._errback)
-
-
-class BookmarksAdd(BookmarksCommon):
-
-    def __init__(self, host):
-        super(BookmarksAdd, self).__init__(host, 'add', help=_('add a bookmark'))
-        self.need_loop = True
-
-    def add_parser_options(self):
-        super(BookmarksAdd, self).add_parser_options(location_default='auto')
-        self.parser.add_argument('bookmark', type=base.unicode_decoder, help=_('jid (for muc bookmark) or url of to remove'))
-        self.parser.add_argument('-n', '--name', type=base.unicode_decoder, help=_("bookmark name"))
-        muc_group = self.parser.add_argument_group(_('MUC specific options'))
-        muc_group.add_argument('-N', '--nick', type=base.unicode_decoder, help=_('nickname'))
-        muc_group.add_argument('-a', '--autojoin', action='store_true', help=_('join room on profile connection'))
-
-    def start(self):
-        if self.args.type == 'url' and (self.args.autojoin or self.args.nick is not None):
-            # XXX: Argparse doesn't seem to manage this case, any better way ?
-            print _(u"You can't use --autojoin or --nick with --type url")
-            self.host.quit(1)
-        data = {}
-        if self.args.autojoin:
-            data['autojoin'] = 'true'
-        if self.args.nick is not None:
-            data['nick'] = self.args.nick
-        if self.args.name is not None:
-            data['name'] = self.args.name
-        self.host.bridge.bookmarksAdd(self.args.type, self.args.bookmark, data, self.args.location, self.host.profile, callback = lambda: self.host.quit(), errback=self._errback)
-
-
-class Bookmarks(base.CommandBase):
-    subcommands = (BookmarksList, BookmarksRemove, BookmarksAdd)
-
-    def __init__(self, host):
-        super(Bookmarks, self).__init__(host, 'bookmarks', use_profile=False, help=_('manage bookmarks'))
--- a/frontends/src/jp/cmd_debug.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,160 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SàT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-
-import base
-from sat.core.i18n import _
-from sat_frontends.jp.constants import Const as C
-from sat.tools.common.ansi import ANSI as A
-import json
-
-__commands__ = ["Debug"]
-
-
-class BridgeCommon(object):
-
-    def evalArgs(self):
-        if self.args.arg:
-            try:
-                return eval(u'[{}]'.format(u",".join(self.args.arg)))
-            except SyntaxError as e:
-                self.disp(u"Can't evaluate arguments: {mess}\n{text}\n{offset}^".format(
-                    mess=e,
-                    text=e.text.decode('utf-8'),
-                    offset=u" "*(e.offset-1)
-                    ), error=True)
-                self.host.quit(C.EXIT_BAD_ARG)
-        else:
-            return []
-
-
-class Method(base.CommandBase, BridgeCommon):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'method', help=_(u'call a bridge method'))
-        BridgeCommon.__init__(self)
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("method", type=str, help=_(u"name of the method to execute"))
-        self.parser.add_argument("arg", type=base.unicode_decoder, nargs="*", help=_(u"argument of the method"))
-
-    def method_cb(self, ret=None):
-        if ret is not None:
-            self.disp(unicode(ret))
-        self.host.quit()
-
-    def method_eb(self, failure):
-        self.disp(_(u"Error while executing {}: {}".format(self.args.method, failure)), error=True)
-        self.host.quit(C.EXIT_ERROR)
-
-    def start(self):
-        method = getattr(self.host.bridge, self.args.method)
-        args = self.evalArgs()
-        try:
-            method(*args, profile=self.profile, callback=self.method_cb, errback=self.method_eb)
-        except TypeError:
-            # maybe the method doesn't need a profile ?
-            try:
-                method(*args, callback=self.method_cb, errback=self.method_eb)
-            except TypeError:
-                self.method_eb(_(u"bad arguments"))
-
-
-class Signal(base.CommandBase, BridgeCommon):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'signal', help=_(u'send a fake signal from backend'))
-        BridgeCommon.__init__(self)
-
-    def add_parser_options(self):
-        self.parser.add_argument("signal", type=str, help=_(u"name of the signal to send"))
-        self.parser.add_argument("arg", type=base.unicode_decoder, nargs="*", help=_(u"argument of the signal"))
-
-    def start(self):
-        args = self.evalArgs()
-        json_args = json.dumps(args)
-        # XXX: we use self.args.profile and not self.profile
-        #      because we want the raw profile_key (so plugin handle C.PROF_KEY_NONE)
-        self.host.bridge.debugFakeSignal(self.args.signal, json_args, self.args.profile)
-
-
-class Bridge(base.CommandBase):
-    subcommands = (Method, Signal)
-
-    def __init__(self, host):
-        super(Bridge, self).__init__(host, 'bridge', use_profile=False, help=_('bridge s(t)imulation'))
-
-
-class Monitor(base.CommandBase):
-
-    def __init__(self, host):
-        super(Monitor, self).__init__(host,
-                                      'monitor',
-                                      use_verbose=True,
-                                      use_profile=False,
-                                      use_output=C.OUTPUT_XML,
-                                      help=_('monitor XML stream'))
-        self.need_loop = True
-
-    def add_parser_options(self):
-        self.parser.add_argument("-d", "--direction", choices=('in', 'out', 'both'), default='both', help=_(u"stream direction filter"))
-
-    def printXML(self, direction, xml_data, profile):
-        if self.args.direction == 'in' and direction != 'IN':
-            return
-        if self.args.direction == 'out' and direction != 'OUT':
-            return
-        verbosity = self.host.verbosity
-        if not xml_data.strip():
-            if verbosity <= 2:
-                return
-            whiteping = True
-        else:
-            whiteping = False
-
-        if verbosity:
-            profile_disp = u' ({})'.format(profile) if verbosity>1 else u''
-            if direction == 'IN':
-                self.disp(A.color(A.BOLD, A.FG_YELLOW, '<<<===== IN ====', A.FG_WHITE, profile_disp))
-            else:
-                self.disp(A.color(A.BOLD, A.FG_CYAN, '==== OUT ====>>>', A.FG_WHITE, profile_disp))
-        if whiteping:
-            self.disp('[WHITESPACE PING]')
-        else:
-            try:
-                self.output(xml_data)
-            except Exception:
-                # initial stream is not valid XML,
-                # in this case we print directly to data
-                # FIXME: we should test directly lxml.etree.XMLSyntaxError
-                #        but importing lxml directly here is not clean
-                #        should be wrapped in a custom Exception
-                self.disp(xml_data)
-                self.disp(u'')
-
-    def start(self):
-        self.host.bridge.register_signal('xmlLog', self.printXML, 'plugin')
-
-
-class Debug(base.CommandBase):
-    subcommands = (Bridge, Monitor)
-
-    def __init__(self, host):
-        super(Debug, self).__init__(host, 'debug', use_profile=False, help=_('debugging tools'))
--- a/frontends/src/jp/cmd_event.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,416 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SàT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-
-import base
-from sat.core.i18n import _
-from sat.tools.common.ansi import ANSI as A
-from sat_frontends.jp.constants import Const as C
-from sat_frontends.jp import common
-from functools import partial
-from dateutil import parser as du_parser
-import calendar
-import time
-
-__commands__ = ["Event"]
-
-OUTPUT_OPT_TABLE = u'table'
-
-# TODO: move date parsing to base, it may be useful for other commands
-
-
-class Get(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self,
-                                  host,
-                                  'get',
-                                  use_output=C.OUTPUT_DICT,
-                                  use_pubsub=True, pubsub_flags={C.NODE, C.SINGLE_ITEM},
-                                  use_verbose=True,
-                                  help=_(u'get event data'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        pass
-
-    def eventInviteeGetCb(self, result):
-        event_date, event_data = result
-        event_data['date'] = event_date
-        self.output(event_data)
-        self.host.quit()
-
-    def start(self):
-        self.host.bridge.eventGet(
-            self.args.service,
-            self.args.node,
-            self.args.item,
-            self.profile,
-            callback=self.eventInviteeGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get event data: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class EventBase(object):
-
-    def add_parser_options(self):
-        self.parser.add_argument("-i", "--id", type=base.unicode_decoder, default=u'', help=_(u"ID of the PubSub Item"))
-        self.parser.add_argument("-d", "--date", type=unicode, help=_(u"date of the event"))
-        self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields',
-                                 metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set"))
-
-    def parseFields(self):
-        return dict(self.args.fields) if self.args.fields else {}
-
-    def parseDate(self):
-        if self.args.date:
-            try:
-                date = int(self.args.date)
-            except ValueError:
-                try:
-                    date_time = du_parser.parse(self.args.date, dayfirst=not (u'-' in self.args.date))
-                except ValueError as e:
-                    self.parser.error(_(u"Can't parse date: {msg}").format(msg=e))
-                if date_time.tzinfo is None:
-                    date = calendar.timegm(date_time.timetuple())
-                else:
-                    date = time.mktime(date_time.timetuple())
-        else:
-            date = -1
-        return date
-
-
-class Create(EventBase, base.CommandBase):
-    def __init__(self, host):
-        super(Create, self).__init__(host, 'create', use_pubsub=True, pubsub_flags={C.NODE}, help=_('create or replace event'))
-        EventBase.__init__(self)
-        self.need_loop=True
-
-    def eventCreateCb(self, node):
-        self.disp(_(u'Event created successfuly on node {node}').format(node=node))
-        self.host.quit()
-
-    def start(self):
-        fields = self.parseFields()
-        date = self.parseDate()
-        self.host.bridge.eventCreate(
-            date,
-            fields,
-            self.args.service,
-            self.args.node,
-            self.args.id,
-            self.profile,
-            callback=self.eventCreateCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't create event: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class Modify(EventBase, base.CommandBase):
-    def __init__(self, host):
-        super(Modify, self).__init__(host, 'modify', use_pubsub=True, pubsub_flags={C.NODE}, help=_('modify an existing event'))
-        EventBase.__init__(self)
-        self.need_loop=True
-
-    def start(self):
-        fields = self.parseFields()
-        date = 0 if not self.args.date else self.parseDate()
-        self.host.bridge.eventModify(
-            self.args.service,
-            self.args.node,
-            self.args.id,
-            date,
-            fields,
-            self.profile,
-            callback=self.host.quit,
-            errback=partial(self.errback,
-                            msg=_(u"can't update event data: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class InviteeGet(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self,
-                                  host,
-                                  'get',
-                                  use_output=C.OUTPUT_DICT,
-                                  use_pubsub=True, pubsub_flags={C.NODE},
-                                  use_verbose=True,
-                                  help=_(u'get event attendance'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        pass
-
-    def eventInviteeGetCb(self, event_data):
-        self.output(event_data)
-        self.host.quit()
-
-    def start(self):
-        self.host.bridge.eventInviteeGet(
-            self.args.service,
-            self.args.node,
-            self.profile,
-            callback=self.eventInviteeGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get event data: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class InviteeSet(base.CommandBase):
-    def __init__(self, host):
-        super(InviteeSet, self).__init__(host, 'set', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, help=_('set event attendance'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields',
-                                 metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set"))
-
-    def start(self):
-        fields = dict(self.args.fields) if self.args.fields else {}
-        self.host.bridge.eventInviteeSet(
-            self.args.service,
-            self.args.node,
-            fields,
-            self.profile,
-            callback=self.host.quit,
-            errback=partial(self.errback,
-                            msg=_(u"can't set event data: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class InviteesList(base.CommandBase):
-
-    def __init__(self, host):
-        extra_outputs = {'default': self.default_output}
-        base.CommandBase.__init__(self,
-                                  host,
-                                  'list',
-                                  use_output=C.OUTPUT_DICT_DICT,
-                                  extra_outputs=extra_outputs,
-                                  use_pubsub=True, pubsub_flags={C.NODE},
-                                  use_verbose=True,
-                                  help=_(u'get event attendance'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument('-m', '--missing', action='store_true', help=_(u'show missing people (invited but no R.S.V.P. so far)'))
-        self.parser.add_argument('-R', '--no-rsvp', action='store_true', help=_(u"don't show people which gave R.S.V.P."))
-
-    def _attend_filter(self, attend, row):
-        if attend == u'yes':
-            attend_color = C.A_SUCCESS
-        elif attend == u'no':
-            attend_color = C.A_FAILURE
-        else:
-            attend_color = A.FG_WHITE
-        return A.color(attend_color, attend)
-
-    def _guests_filter(self, guests):
-        return u'(' + unicode(guests) + ')' if guests else u''
-
-    def default_output(self, event_data):
-        data = []
-        attendees_yes = 0
-        attendees_maybe = 0
-        attendees_no = 0
-        attendees_missing = 0
-        guests = 0
-        guests_maybe = 0
-        for jid_, jid_data in event_data.iteritems():
-            jid_data[u'jid'] = jid_
-            try:
-                guests_int = int(jid_data['guests'])
-            except (ValueError, KeyError):
-                pass
-            attend = jid_data.get(u'attend',u'')
-            if attend == 'yes':
-                attendees_yes += 1
-                guests += guests_int
-            elif attend == 'maybe':
-                attendees_maybe += 1
-                guests_maybe += guests_int
-            elif attend == 'no':
-                attendees_no += 1
-                jid_data[u'guests'] = ''
-            else:
-                attendees_missing += 1
-                jid_data[u'guests'] = ''
-            data.append(jid_data)
-
-        show_table = OUTPUT_OPT_TABLE in self.args.output_opts
-
-        table = common.Table.fromDict(self.host,
-            data,
-            (u'nick',) + ((u'jid',) if self.host.verbosity else ()) +  (u'attend', 'guests'),
-            headers=None,
-            filters = { u'nick': A.color(C.A_HEADER, u'{}' if show_table else u'{} '),
-                        u'jid': u'{}' if show_table else u'{} ',
-                        u'attend': self._attend_filter,
-                        u'guests': u'{}' if show_table else self._guests_filter,
-                      },
-            defaults = { u'nick': u'',
-                         u'attend': u'',
-                         u'guests': 1
-                       }
-            )
-        if show_table:
-            table.display()
-        else:
-            table.display_blank(show_header=False, col_sep=u'')
-
-        if not self.args.no_rsvp:
-            self.disp(u'')
-            self.disp(A.color(
-                C.A_SUBHEADER,
-                _(u'Attendees: '),
-                A.RESET,
-                unicode(len(data)),
-                _(u' ('),
-                C.A_SUCCESS,
-                _(u'yes: '),
-                unicode(attendees_yes),
-                A.FG_WHITE,
-                _(u', maybe: '),
-                unicode(attendees_maybe),
-                u', ',
-                C.A_FAILURE,
-                _(u'no: '),
-                unicode(attendees_no),
-                A.RESET,
-                u')'
-                ))
-            self.disp(A.color(C.A_SUBHEADER, _(u'confirmed guests: '), A.RESET, unicode(guests)))
-            self.disp(A.color(C.A_SUBHEADER, _(u'unconfirmed guests: '), A.RESET, unicode(guests_maybe)))
-            self.disp(A.color(C.A_SUBHEADER, _(u'total: '), A.RESET, unicode(guests+guests_maybe)))
-        if attendees_missing:
-            self.disp('')
-            self.disp(A.color(C.A_SUBHEADER, _(u'missing people (no reply): '), A.RESET, unicode(attendees_missing)))
-
-    def eventInviteesListCb(self, event_data, prefilled_data):
-        """fill nicknames and keep only requested people
-
-        @param event_data(dict): R.S.V.P. answers
-        @param prefilled_data(dict): prefilled data with all people
-            only filled if --missing is used
-        """
-        if self.args.no_rsvp:
-            for jid_ in event_data:
-                # if there is a jid in event_data
-                # it must be there in prefilled_data too
-                # so no need to check for KeyError
-                del prefilled_data[jid_]
-        else:
-            # we replace empty dicts for existing people with R.S.V.P. data
-            prefilled_data.update(event_data)
-
-        # we get nicknames for everybody, make it easier for organisers
-        for jid_, data in prefilled_data.iteritems():
-            id_data = self.host.bridge.identityGet(jid_, self.profile)
-            data[u'nick'] = id_data.get(u'nick', u'')
-
-        self.output(prefilled_data)
-        self.host.quit()
-
-    def getList(self, prefilled_data={}):
-        self.host.bridge.eventInviteesList(
-            self.args.service,
-            self.args.node,
-            self.profile,
-            callback=partial(self.eventInviteesListCb,
-                             prefilled_data=prefilled_data),
-            errback=partial(self.errback,
-                            msg=_(u"can't get event data: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-    def psNodeAffiliationsGetCb(self, affiliations):
-        # we fill all affiliations with empty data
-        # answered one will be filled in eventInviteesListCb
-        # we only consider people with "publisher" affiliation as invited, creators are not, and members can just observe
-        prefilled = {jid_: {} for jid_, affiliation in affiliations.iteritems() if affiliation in (u'publisher',)}
-        self.getList(prefilled)
-
-    def start(self):
-        if self.args.no_rsvp and not self.args.missing:
-            self.parser.error(_(u"you need to use --missing if you use --no-rsvp"))
-        if self.args.missing:
-            self.host.bridge.psNodeAffiliationsGet(
-                self.args.service,
-                self.args.node,
-                self.profile,
-                callback=self.psNodeAffiliationsGetCb,
-                errback=partial(self.errback,
-                                msg=_(u"can't get event data: {}"),
-                                exit_code=C.EXIT_BRIDGE_ERRBACK))
-        else:
-            self.getList()
-
-
-class InviteeInvite(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'invite', use_pubsub=True, pubsub_flags={C.NODE, C.SINGLE_ITEM}, help=_(u'invite someone to the event through email'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("-e", "--email", action="append", type=base.unicode_decoder, default=[], help='email(s) to send the invitation to')
-        self.parser.add_argument("-N", "--name", type=base.unicode_decoder, default='', help='name of the invitee')
-        self.parser.add_argument("-H", "--host-name", type=base.unicode_decoder, default='', help='name of the host')
-        self.parser.add_argument("-l", "--lang", type=base.unicode_decoder, default='', help='main language spoken by the invitee')
-        self.parser.add_argument("-U", "--url-template", type=base.unicode_decoder, default='', help='template to construct the URL')
-        self.parser.add_argument("-S", "--subject", type=base.unicode_decoder, default='', help='subject of the invitation email (default: generic subject)')
-        self.parser.add_argument("-b", "--body", type=base.unicode_decoder, default='', help='body of the invitation email (default: generic body)')
-
-    def start(self):
-        email = self.args.email[0] if self.args.email else None
-        emails_extra = self.args.email[1:]
-
-        self.host.bridge.eventInvite(
-            self.args.service,
-            self.args.node,
-            self.args.item,
-            email,
-            emails_extra,
-            self.args.name,
-            self.args.host_name,
-            self.args.lang,
-            self.args.url_template,
-            self.args.subject,
-            self.args.body,
-            self.args.profile,
-            callback=self.host.quit,
-            errback=partial(self.errback,
-                            msg=_(u"can't create invitation: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class Invitee(base.CommandBase):
-    subcommands = (InviteeGet, InviteeSet, InviteesList, InviteeInvite)
-
-    def __init__(self, host):
-        super(Invitee, self).__init__(host, 'invitee', use_profile=False, help=_(u'manage invities'))
-
-
-class Event(base.CommandBase):
-    subcommands = (Get, Create, Modify, Invitee)
-
-    def __init__(self, host):
-        super(Event, self).__init__(host, 'event', use_profile=False, help=_('event management'))
--- a/frontends/src/jp/cmd_file.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,503 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SAT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-
-import base
-import sys
-import os
-import os.path
-import tarfile
-from sat.core.i18n import _
-from sat_frontends.jp.constants import Const as C
-from sat_frontends.jp import common
-from sat_frontends.tools import jid
-from sat.tools.common.ansi import ANSI as A
-import tempfile
-import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI
-from functools import partial
-import json
-
-__commands__ = ["File"]
-
-
-class Send(base.CommandBase):
-    def __init__(self, host):
-        super(Send, self).__init__(host, 'send', use_progress=True, use_verbose=True, help=_('send a file to a contact'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("files", type=str, nargs='+', metavar='file', help=_(u"a list of file"))
-        self.parser.add_argument("jid", type=base.unicode_decoder, help=_(u"the destination jid"))
-        self.parser.add_argument("-b", "--bz2", action="store_true", help=_(u"make a bzip2 tarball"))
-        self.parser.add_argument("-d", "--path", type=base.unicode_decoder, help=(u"path to the directory where the file must be stored"))
-        self.parser.add_argument("-N", "--namespace", type=base.unicode_decoder, help=(u"namespace of the file"))
-        self.parser.add_argument("-n", "--name", type=base.unicode_decoder, default=u'', help=(u"name to use (DEFAULT: use source file name)"))
-
-    def start(self):
-        """Send files to jabber contact"""
-        self.send_files()
-
-    def onProgressStarted(self, metadata):
-        self.disp(_(u'File copy started'),2)
-
-    def onProgressFinished(self, metadata):
-        self.disp(_(u'File sent successfully'),2)
-
-    def onProgressError(self, error_msg):
-        if error_msg == C.PROGRESS_ERROR_DECLINED:
-            self.disp(_(u'The file has been refused by your contact'))
-        else:
-            self.disp(_(u'Error while sending file: {}').format(error_msg),error=True)
-
-    def gotId(self, data, file_):
-        """Called when a progress id has been received
-
-        @param pid(unicode): progress id
-        @param file_(str): file path
-        """
-        #FIXME: this show progress only for last progress_id
-        self.disp(_(u"File request sent to {jid}".format(jid=self.full_dest_jid)), 1)
-        try:
-            self.progress_id = data['progress']
-        except KeyError:
-            # TODO: if 'xmlui' key is present, manage xmlui message display
-            self.disp(_(u"Can't send file to {jid}".format(jid=self.full_dest_jid)), error=True)
-            self.host.quit(2)
-
-    def error(self, failure):
-        self.disp(_("Error while trying to send a file: {reason}").format(reason=failure), error=True)
-        self.host.quit(1)
-
-    def send_files(self):
-        for file_ in self.args.files:
-            if not os.path.exists(file_):
-                self.disp(_(u"file [{}] doesn't exist !").format(file_), error=True)
-                self.host.quit(1)
-            if not self.args.bz2 and os.path.isdir(file_):
-                self.disp(_(u"[{}] is a dir ! Please send files inside or use compression").format(file_))
-                self.host.quit(1)
-
-        self.full_dest_jid = self.host.get_full_jid(self.args.jid)
-        extra = {}
-        if self.args.path:
-            extra[u'path'] = self.args.path
-        if self.args.namespace:
-            extra[u'namespace'] = self.args.namespace
-
-        if self.args.bz2:
-            with tempfile.NamedTemporaryFile('wb', delete=False) as buf:
-                self.host.addOnQuitCallback(os.unlink, buf.name)
-                self.disp(_(u"bz2 is an experimental option, use with caution"))
-                #FIXME: check free space
-                self.disp(_(u"Starting compression, please wait..."))
-                sys.stdout.flush()
-                bz2 = tarfile.open(mode="w:bz2", fileobj=buf)
-                archive_name = u'{}.tar.bz2'.format(os.path.basename(self.args.files[0]) or u'compressed_files')
-                for file_ in self.args.files:
-                    self.disp(_(u"Adding {}").format(file_), 1)
-                    bz2.add(file_)
-                bz2.close()
-                self.disp(_(u"Done !"), 1)
-
-                self.host.bridge.fileSend(self.full_dest_jid, buf.name, self.args.name or archive_name, '', extra, self.profile,
-                                          callback=lambda pid, file_=buf.name: self.gotId(pid, file_), errback=self.error)
-        else:
-            for file_ in self.args.files:
-                path = os.path.abspath(file_)
-                self.host.bridge.fileSend(self.full_dest_jid, path, self.args.name, '', extra, self.profile,
-                                          callback=lambda pid, file_=file_: self.gotId(pid, file_), errback=self.error)
-
-
-class Request(base.CommandBase):
-
-    def __init__(self, host):
-        super(Request, self).__init__(host, 'request', use_progress=True, use_verbose=True, help=_('request a file from a contact'))
-        self.need_loop=True
-
-    @property
-    def filename(self):
-        return self.args.name or self.args.hash or u"output"
-
-    def add_parser_options(self):
-        self.parser.add_argument("jid", type=base.unicode_decoder, help=_(u"the destination jid"))
-        self.parser.add_argument("-D", "--dest", type=base.unicode_decoder, help=_(u"destination path where the file will be saved (default: [current_dir]/[name|hash])"))
-        self.parser.add_argument("-n", "--name", type=base.unicode_decoder, default=u'', help=_(u"name of the file"))
-        self.parser.add_argument("-H", "--hash", type=base.unicode_decoder, default=u'', help=_(u"hash of the file"))
-        self.parser.add_argument("-a", "--hash-algo", type=base.unicode_decoder, default=u'sha-256', help=_(u"hash algorithm use for --hash (default: sha-256)"))
-        self.parser.add_argument("-d", "--path", type=base.unicode_decoder, help=(u"path to the directory containing the file"))
-        self.parser.add_argument("-N", "--namespace", type=base.unicode_decoder, help=(u"namespace of the file"))
-        self.parser.add_argument("-f", "--force", action='store_true', help=_(u"overwrite existing file without confirmation"))
-
-    def onProgressStarted(self, metadata):
-        self.disp(_(u'File copy started'),2)
-
-    def onProgressFinished(self, metadata):
-        self.disp(_(u'File received successfully'),2)
-
-    def onProgressError(self, error_msg):
-        if error_msg == C.PROGRESS_ERROR_DECLINED:
-            self.disp(_(u'The file request has been refused'))
-        else:
-            self.disp(_(u'Error while requesting file: {}').format(error_msg), error=True)
-
-    def gotId(self, progress_id):
-        """Called when a progress id has been received
-
-        @param progress_id(unicode): progress id
-        """
-        self.progress_id = progress_id
-
-    def error(self, failure):
-        self.disp(_("Error while trying to send a file: {reason}").format(reason=failure), error=True)
-        self.host.quit(1)
-
-    def start(self):
-        if not self.args.name and not self.args.hash:
-            self.parser.error(_(u'at least one of --name or --hash must be provided'))
-        # extra = dict(self.args.extra)
-        if self.args.dest:
-            path = os.path.abspath(os.path.expanduser(self.args.dest))
-            if os.path.isdir(path):
-                path = os.path.join(path, self.filename)
-        else:
-            path = os.path.abspath(self.filename)
-
-        if os.path.exists(path) and not self.args.force:
-            message = _(u'File {path} already exists! Do you want to overwrite?').format(path=path)
-            confirm = raw_input(u"{} (y/N) ".format(message).encode('utf-8'))
-            if confirm not in (u"y", u"Y"):
-                self.disp(_(u"file request cancelled"))
-                self.host.quit(2)
-
-        self.full_dest_jid = self.host.get_full_jid(self.args.jid)
-        extra = {}
-        if self.args.path:
-            extra[u'path'] = self.args.path
-        if self.args.namespace:
-            extra[u'namespace'] = self.args.namespace
-        self.host.bridge.fileJingleRequest(self.full_dest_jid,
-                                           path,
-                                           self.args.name,
-                                           self.args.hash,
-                                           self.args.hash_algo if self.args.hash else u'',
-                                           extra,
-                                           self.profile,
-                                           callback=self.gotId,
-                                           errback=partial(self.errback,
-                                               msg=_(u"can't request file: {}"),
-                                               exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class Receive(base.CommandAnswering):
-
-    def __init__(self, host):
-        super(Receive, self).__init__(host, 'receive', use_progress=True, use_verbose=True, help=_('wait for a file to be sent by a contact'))
-        self._overwrite_refused = False # True when one overwrite as already been refused
-        self.action_callbacks = {C.META_TYPE_FILE: self.onFileAction,
-                                 C.META_TYPE_OVERWRITE: self.onOverwriteAction}
-
-    def onProgressStarted(self, metadata):
-        self.disp(_(u'File copy started'),2)
-
-    def onProgressFinished(self, metadata):
-        self.disp(_(u'File received successfully'),2)
-        if metadata.get('hash_verified', False):
-            try:
-                self.disp(_(u'hash checked: {algo}:{checksum}').format(
-                    algo=metadata['hash_algo'],
-                    checksum=metadata['hash']),
-                    1)
-            except KeyError:
-                self.disp(_(u'hash is checked but hash value is missing', 1), error=True)
-        else:
-            self.disp(_(u"hash can't be verified"), 1)
-
-    def onProgressError(self, error_msg):
-        self.disp(_(u'Error while receiving file: {}').format(error_msg),error=True)
-
-    def getXmluiId(self, action_data):
-        # FIXME: we temporarily use ElementTree, but a real XMLUI managing module
-        #        should be available in the futur
-        # TODO: XMLUI module
-        try:
-            xml_ui = action_data['xmlui']
-        except KeyError:
-            self.disp(_(u"Action has no XMLUI"), 1)
-        else:
-            ui = ET.fromstring(xml_ui.encode('utf-8'))
-            xmlui_id = ui.get('submit')
-            if not xmlui_id:
-                self.disp(_(u"Invalid XMLUI received"), error=True)
-            return xmlui_id
-
-    def onFileAction(self, action_data, action_id, security_limit, profile):
-        xmlui_id = self.getXmluiId(action_data)
-        if xmlui_id is None:
-            return self.host.quitFromSignal(1)
-        try:
-            from_jid = jid.JID(action_data['meta_from_jid'])
-        except KeyError:
-            self.disp(_(u"Ignoring action without from_jid data"), 1)
-            return
-        try:
-            progress_id = action_data['meta_progress_id']
-        except KeyError:
-            self.disp(_(u"ignoring action without progress id"), 1)
-            return
-
-        if not self.bare_jids or from_jid.bare in self.bare_jids:
-            if self._overwrite_refused:
-                self.disp(_(u"File refused because overwrite is needed"), error=True)
-                self.host.bridge.launchAction(xmlui_id, {'cancelled': C.BOOL_TRUE}, profile_key=profile)
-                return self.host.quitFromSignal(2)
-            self.progress_id = progress_id
-            xmlui_data = {'path': self.path}
-            self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile)
-
-    def onOverwriteAction(self, action_data, action_id, security_limit, profile):
-        xmlui_id = self.getXmluiId(action_data)
-        if xmlui_id is None:
-            return self.host.quitFromSignal(1)
-        try:
-            progress_id = action_data['meta_progress_id']
-        except KeyError:
-            self.disp(_(u"ignoring action without progress id"), 1)
-            return
-        self.disp(_(u"Overwriting needed"), 1)
-
-        if progress_id == self.progress_id:
-            if self.args.force:
-                self.disp(_(u"Overwrite accepted"), 2)
-            else:
-                self.disp(_(u"Refused to overwrite"), 2)
-                self._overwrite_refused = True
-
-            xmlui_data = {'answer': C.boolConst(self.args.force)}
-            self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile)
-
-    def add_parser_options(self):
-        self.parser.add_argument("jids", type=base.unicode_decoder, nargs="*", help=_(u'jids accepted (accept everything if none is specified)'))
-        self.parser.add_argument("-m", "--multiple", action="store_true", help=_(u"accept multiple files (you'll have to stop manually)"))
-        self.parser.add_argument("-f", "--force", action="store_true", help=_(u"force overwritting of existing files (/!\\ name is choosed by sender)"))
-        self.parser.add_argument("--path", default='.', metavar='DIR', help=_(u"destination path (default: working directory)"))
-
-    def start(self):
-        self.bare_jids = [jid.JID(jid_).bare for jid_ in self.args.jids]
-        self.path = os.path.abspath(self.args.path)
-        if not os.path.isdir(self.path):
-            self.disp(_(u"Given path is not a directory !", error=True))
-            self.host.quit(2)
-        if self.args.multiple:
-            self.host.quit_on_progress_end = False
-        self.disp(_(u"waiting for incoming file request"),2)
-
-
-class Upload(base.CommandBase):
-
-    def __init__(self, host):
-        super(Upload, self).__init__(host, 'upload', use_progress=True, use_verbose=True, help=_('upload a file'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("file", type=str, help=_("file to upload"))
-        self.parser.add_argument("jid", type=base.unicode_decoder, nargs='?',  help=_("jid of upload component (nothing to autodetect)"))
-        self.parser.add_argument("--ignore-tls-errors", action="store_true", help=_("ignore invalide TLS certificate"))
-
-    def onProgressStarted(self, metadata):
-        self.disp(_(u'File upload started'),2)
-
-    def onProgressFinished(self, metadata):
-        self.disp(_(u'File uploaded successfully'),2)
-        try:
-            url = metadata['url']
-        except KeyError:
-            self.disp(u'download URL not found in metadata')
-        else:
-            self.disp(_(u'URL to retrieve the file:'),1)
-            # XXX: url is display alone on a line to make parsing easier
-            self.disp(url)
-
-    def onProgressError(self, error_msg):
-        self.disp(_(u'Error while uploading file: {}').format(error_msg),error=True)
-
-    def gotId(self, data, file_):
-        """Called when a progress id has been received
-
-        @param pid(unicode): progress id
-        @param file_(str): file path
-        """
-        try:
-            self.progress_id = data['progress']
-        except KeyError:
-            # TODO: if 'xmlui' key is present, manage xmlui message display
-            self.disp(_(u"Can't upload file"), error=True)
-            self.host.quit(2)
-
-    def error(self, failure):
-        self.disp(_("Error while trying to upload a file: {reason}").format(reason=failure), error=True)
-        self.host.quit(1)
-
-    def start(self):
-        file_ = self.args.file
-        if not os.path.exists(file_):
-            self.disp(_(u"file [{}] doesn't exist !").format(file_), error=True)
-            self.host.quit(1)
-        if os.path.isdir(file_):
-            self.disp(_(u"[{}] is a dir! Can't upload a dir").format(file_))
-            self.host.quit(1)
-
-        self.full_dest_jid = self.host.get_full_jid(self.args.jid) if self.args.jid is not None else ''
-        options = {}
-        if self.args.ignore_tls_errors:
-            options['ignore_tls_errors'] = C.BOOL_TRUE
-
-        path = os.path.abspath(file_)
-        self.host.bridge.fileUpload(path, '', self.full_dest_jid, options, self.profile, callback=lambda pid, file_=file_: self.gotId(pid, file_), errback=self.error)
-
-
-class ShareList(base.CommandBase):
-
-    def __init__(self, host):
-        extra_outputs = {'default': self.default_output}
-        super(ShareList, self).__init__(host, 'list', use_output=C.OUTPUT_LIST_DICT, extra_outputs=extra_outputs, help=_(u'retrieve files shared by an entity'), use_verbose=True)
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("-d", "--path", default=u'', help=_(u"path to the directory containing the files"))
-        self.parser.add_argument("jid", type=base.unicode_decoder, nargs='?',  help=_("jid of sharing entity (nothing to check our own jid)"))
-
-    def file_gen(self, files_data):
-        for file_data in files_data:
-            yield file_data[u'name']
-            yield file_data.get(u'size', '')
-            yield file_data.get(u'hash','')
-
-    def _name_filter(self, name, row):
-        if row.type == C.FILE_TYPE_DIRECTORY:
-            return A.color(C.A_DIRECTORY, name)
-        elif row.type == C.FILE_TYPE_FILE:
-            return A.color(C.A_FILE, name)
-        else:
-            self.disp(_(u'unknown file type: {type}').format(type=row.type), error=True)
-            return name
-
-    def _size_filter(self, size, row):
-        if not size:
-            return u''
-        size = int(size)
-        # cf. https://stackoverflow.com/a/1094933 (thanks)
-        suffix = u'o'
-        for unit in [u'', u'Ki', u'Mi', u'Gi', u'Ti', u'Pi', u'Ei', u'Zi']:
-            if abs(size) < 1024.0:
-                return A.color(A.BOLD, u"{:.2f}".format(size), unit, suffix)
-            size /= 1024.0
-
-        return A.color(A.BOLD, u"{:.2f}".format(size), u'Yi', suffix)
-
-    def default_output(self, files_data):
-        """display files a way similar to ls"""
-        files_data.sort(key=lambda d: d['name'].lower())
-        show_header = False
-        if self.verbosity == 0:
-            headers = (u'name', u'type')
-        elif self.verbosity == 1:
-            headers = (u'name', u'type', u'size')
-        elif self.verbosity > 1:
-            show_header = True
-            headers = (u'name', u'type', u'size', u'hash')
-        table = common.Table.fromDict(self.host,
-                                      files_data,
-                                      headers,
-                                      filters={u'name': self._name_filter,
-                                               u'size': self._size_filter},
-                                      defaults={u'size': u'',
-                                                u'hash': u''},
-                                      )
-        table.display_blank(show_header=show_header, hide_cols=['type'])
-
-    def _FISListCb(self, files_data):
-        self.output(files_data)
-        self.host.quit()
-
-    def start(self):
-        self.host.bridge.FISList(
-            self.args.jid,
-            self.args.path,
-            {},
-            self.profile,
-            callback=self._FISListCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't retrieve shared files: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class SharePath(base.CommandBase):
-
-    def __init__(self, host):
-        super(SharePath, self).__init__(host, 'path', help=_(u'share a file or directory'), use_verbose=True)
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("-n", "--name", type=base.unicode_decoder, default=u'', help=_(u"virtual name to use (default: use directory/file name)"))
-        perm_group = self.parser.add_mutually_exclusive_group()
-        perm_group.add_argument("-j", "--jid", type=base.unicode_decoder, action='append', dest="jids", default=[], help=_(u"jid of contacts allowed to retrieve the files"))
-        perm_group.add_argument("--public", action='store_true', help=_(u"share publicly the file(s) (/!\\ *everybody* will be able to access them)"))
-        self.parser.add_argument("path", type=base.unicode_decoder, help=_(u"path to a file or directory to share"))
-
-
-    def _FISSharePathCb(self, name):
-        self.disp(_(u'{path} shared under the name "{name}"').format(
-            path = self.path,
-            name = name))
-        self.host.quit()
-
-    def start(self):
-        self.path = os.path.abspath(self.args.path)
-        if self.args.public:
-            access = {u'read': {u'type': u'public'}}
-        else:
-            jids = self.args.jids
-            if jids:
-                access = {u'read': {u'type': 'whitelist',
-                                         u'jids': jids}}
-            else:
-                access = {}
-        self.host.bridge.FISSharePath(
-            self.args.name,
-            self.path,
-            json.dumps(access, ensure_ascii=False),
-            self.profile,
-            callback=self._FISSharePathCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't share path: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class Share(base.CommandBase):
-    subcommands = (ShareList, SharePath)
-
-    def __init__(self, host):
-        super(Share, self).__init__(host, 'share', use_profile=False, help=_(u'files sharing management'))
-
-
-class File(base.CommandBase):
-    subcommands = (Send, Request, Receive, Upload, Share)
-
-    def __init__(self, host):
-        super(File, self).__init__(host, 'file', use_profile=False, help=_(u'files sending/receiving/management'))
--- a/frontends/src/jp/cmd_forums.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,163 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SàT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-
-import base
-from sat.core.i18n import _
-from sat_frontends.jp.constants import Const as C
-from sat_frontends.jp import common
-from sat.tools.common.ansi import ANSI as A
-from functools import partial
-import json
-
-__commands__ = ["Forums"]
-
-FORUMS_TMP_DIR = u"forums"
-
-
-class Edit(base.CommandBase, common.BaseEdit):
-    use_items=False
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'edit', use_pubsub=True, use_draft=True, use_verbose=True, help=_(u'edit forums'))
-        common.BaseEdit.__init__(self, self.host, FORUMS_TMP_DIR)
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("-k", "--key", type=base.unicode_decoder, default=u'',
-                                 help=_(u"forum key (DEFAULT: default forums)"))
-
-    def getTmpSuff(self):
-        """return suffix used for content file"""
-        return u'json'
-
-    def forumsSetCb(self):
-        self.disp(_(u'forums have been edited'), 1)
-        self.host.quit()
-
-    def publish(self, forums_raw):
-        self.host.bridge.forumsSet(
-            forums_raw,
-            self.args.service,
-            self.args.node,
-            self.args.key,
-            self.profile,
-            callback=self.forumsSetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't set forums: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-    def forumsGetCb(self, forums_json):
-        content_file_obj, content_file_path = self.getTmpFile()
-        forums_json = forums_json.strip()
-        if forums_json:
-            # we loads and dumps to have pretty printed json
-            forums = json.loads(forums_json)
-            json.dump(forums, content_file_obj, ensure_ascii=False, indent=4)
-            content_file_obj.seek(0)
-        self.runEditor("forums_editor_args", content_file_path, content_file_obj)
-
-    def forumsGetEb(self, failure_):
-        # FIXME: error handling with bridge is broken, need to be properly fixed
-        if failure_.condition == u'item-not-found':
-            self.forumsGetCb(u'')
-        else:
-            self.errback(failure_,
-                         msg=_(u"can't get forums structure: {}"),
-                         exit_code=C.EXIT_BRIDGE_ERRBACK)
-
-    def start(self):
-        self.host.bridge.forumsGet(
-            self.args.service,
-            self.args.node,
-            self.args.key,
-            self.profile,
-            callback=self.forumsGetCb,
-            errback=self.forumsGetEb)
-
-
-class Get(base.CommandBase):
-
-    def __init__(self, host):
-        extra_outputs = {'default': self.default_output}
-        base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_COMPLEX, extra_outputs=extra_outputs, use_pubsub=True, use_verbose=True, help=_(u'get forums structure'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("-k", "--key", type=base.unicode_decoder, default=u'',
-                                 help=_(u"forum key (DEFAULT: default forums)"))
-
-    def default_output(self, forums, level=0):
-        for forum in forums:
-            keys = list(forum.keys())
-            keys.sort()
-            try:
-                keys.remove(u'title')
-            except ValueError:
-                pass
-            else:
-                keys.insert(0, u'title')
-            try:
-                keys.remove(u'sub-forums')
-            except ValueError:
-                pass
-            else:
-                keys.append(u'sub-forums')
-
-            for key in keys:
-                value = forum[key]
-                if key == 'sub-forums':
-                    self.default_output(value, level+1)
-                else:
-                    if self.host.verbosity < 1 and key != u'title':
-                        continue
-                    head_color = C.A_LEVEL_COLORS[level % len(C.A_LEVEL_COLORS)]
-                    self.disp(A.color(level * 4 * u' ',
-                                      head_color,
-                                      key,
-                                      A.RESET,
-                                      u': ',
-                                      value))
-
-    def forumsGetCb(self, forums_raw):
-        if not forums_raw:
-            self.disp(_(u'no schema found'), 1)
-            self.host.quit(1)
-        forums = json.loads(forums_raw)
-        self.output(forums)
-        self.host.quit()
-
-    def start(self):
-        self.host.bridge.forumsGet(
-            self.args.service,
-            self.args.node,
-            self.args.key,
-            self.profile,
-            callback=self.forumsGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get forums: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-
-class Forums(base.CommandBase):
-    subcommands = (Get, Edit)
-
-    def __init__(self, host):
-        super(Forums, self).__init__(host, 'forums', use_profile=False, help=_(u'Forums structure edition'))
--- a/frontends/src/jp/cmd_identity.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SàT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-
-import base
-from sat.core.i18n import _
-from sat_frontends.jp.constants import Const as C
-from functools import partial
-
-__commands__ = ["Identity"]
-
-# TODO: move date parsing to base, it may be useful for other commands
-
-
-class Get(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self,
-                                  host,
-                                  'get',
-                                  use_output=C.OUTPUT_DICT,
-                                  use_verbose=True,
-                                  help=_(u'get identity data'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("jid", type=base.unicode_decoder, help=_(u"entity to check"))
-
-    def identityGetCb(self, data):
-        self.output(data)
-        self.host.quit()
-
-    def start(self):
-        jid_ = self.host.check_jids([self.args.jid])[0]
-        self.host.bridge.identityGet(
-            jid_,
-            self.profile,
-            callback=self.identityGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get identity data: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class Set(base.CommandBase):
-    def __init__(self, host):
-        super(Set, self).__init__(host, 'set', help=_('modify an existing event'))
-
-    def add_parser_options(self):
-        self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields',
-                                 metavar=(u"KEY", u"VALUE"), required=True, help=_(u"identity field(s) to set"))
-        self.need_loop=True
-
-    def start(self):
-        fields = dict(self.args.fields)
-        self.host.bridge.identitySet(
-            fields,
-            self.profile,
-            callback=self.host.quit,
-            errback=partial(self.errback,
-                            msg=_(u"can't set identity data data: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class Identity(base.CommandBase):
-    subcommands = (Get, Set)
-
-    def __init__(self, host):
-        super(Identity, self).__init__(host, 'identity', use_profile=False, help=_('identity management'))
--- a/frontends/src/jp/cmd_info.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,193 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SAT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-import base
-from sat.core.i18n import _
-from sat.tools.common.ansi import ANSI as A
-from sat_frontends.jp.constants import Const as C
-from sat_frontends.jp import common
-
-__commands__ = ["Info"]
-
-
-class Disco(base.CommandBase):
-
-    def __init__(self, host):
-        extra_outputs = {'default': self.default_output}
-        super(Disco, self).__init__(host, 'disco', use_output='complex', extra_outputs=extra_outputs, help=_('service discovery'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument(u"jid", type=base.unicode_decoder, help=_(u"entity to discover"))
-        self.parser.add_argument(u"-t", u"--type", type=str, choices=('infos', 'items', 'both'), default='both', help=_(u"type of data to discover"))
-        self.parser.add_argument(u"-n", u"--node", type=base.unicode_decoder, default=u'', help=_(u"node to use"))
-        self.parser.add_argument(u"-C", u"--no-cache", dest='use_cache', action="store_false", help=_(u"ignore cache"))
-
-    def start(self):
-        self.get_infos = self.args.type in ('infos', 'both')
-        self.get_items = self.args.type in ('items', 'both')
-        jids = self.host.check_jids([self.args.jid])
-        jid = jids[0]
-        if not self.get_infos:
-            self.gotInfos(None, jid)
-        else:
-            self.host.bridge.discoInfos(jid, node=self.args.node, use_cache=self.args.use_cache, profile_key=self.host.profile, callback=lambda infos: self.gotInfos(infos, jid), errback=self.error)
-
-    def error(self, failure):
-        print (_("Error while doing discovery [%s]") % failure)
-        self.host.quit(1)
-
-    def gotInfos(self, infos, jid):
-        if not self.get_items:
-            self.gotItems(infos, None)
-        else:
-            self.host.bridge.discoItems(jid, node=self.args.node, use_cache=self.args.use_cache, profile_key=self.host.profile, callback=lambda items: self.gotItems(infos, items), errback=self.error)
-
-    def gotItems(self, infos, items):
-        data = {}
-
-        if self.get_infos:
-            features, identities, extensions = infos
-            features.sort()
-            identities.sort(key=lambda identity: identity[2])
-            data.update({
-                u'features': features,
-                u'identities': identities,
-                u'extensions': extensions})
-
-        if self.get_items:
-            items.sort(key=lambda item: item[2])
-            data[u'items'] = items
-
-        self.output(data)
-        self.host.quit()
-
-    def default_output(self, data):
-        features = data.get(u'features', [])
-        identities = data.get(u'identities', [])
-        extensions = data.get(u'extensions', {})
-        items = data.get(u'items', [])
-
-        identities_table = common.Table(self.host,
-                                        identities,
-                                        headers=(_(u'category'),
-                                                 _(u'type'),
-                                                 _(u'name')),
-                                        use_buffer=True)
-
-        extensions_tpl = []
-        extensions_types = extensions.keys()
-        extensions_types.sort()
-        for type_ in extensions_types:
-            fields = []
-            for field in extensions[type_]:
-                field_lines = []
-                data, values = field
-                data_keys = data.keys()
-                data_keys.sort()
-                for key in data_keys:
-                    field_lines.append(A.color(u'\t', C.A_SUBHEADER, key, data[key]))
-                for value in values:
-                    field_lines.append(A.color(u'\t', A.BOLD, value))
-                fields.append(u'\n'.join(field_lines))
-            extensions_tpl.append(u'{type_}\n{fields}'.format(type_=type_,
-                                                              fields='\n\n'.join(fields)))
-
-        items_table = common.Table(self.host,
-                                   items,
-                                   headers=(_(u'entity'),
-                                            _(u'node'),
-                                            _(u'name')),
-                                   use_buffer=True)
-
-        template = []
-        if features:
-            template.append(A.color(C.A_HEADER, _(u"Features")) + u"\n\n{features}")
-        if identities:
-            template.append(A.color(C.A_HEADER, _(u"Identities")) + u"\n\n{identities}")
-        if extensions:
-            template.append(A.color(C.A_HEADER, _(u"Extensions")) + u"\n\n{extensions}")
-        if items:
-            template.append(A.color(C.A_HEADER, _(u"Items")) + u"\n\n{items}")
-
-        print u"\n\n".join(template).format(features = u'\n'.join(features),
-                                            identities = identities_table.display().string,
-                                            extensions = u'\n'.join(extensions_tpl),
-                                            items = items_table.display().string,
-                                           )
-
-
-class Version(base.CommandBase):
-
-    def __init__(self, host):
-        super(Version, self).__init__(host, 'version', help=_('software version'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("jid", type=str, help=_("Entity to request"))
-
-    def start(self):
-        jids = self.host.check_jids([self.args.jid])
-        jid = jids[0]
-        self.host.bridge.getSoftwareVersion(jid, self.host.profile, callback=self.gotVersion, errback=self.error)
-
-    def error(self, failure):
-        print (_("Error while trying to get version [%s]") % failure)
-        self.host.quit(1)
-
-    def gotVersion(self, data):
-        infos = []
-        name, version, os = data
-        if name:
-            infos.append(_("Client name: %s") %  name)
-        if version:
-            infos.append(_("Client version: %s") %  version)
-        if os:
-            infos.append(_("Operating System: %s") %  os)
-
-        print "\n".join(infos)
-        self.host.quit()
-
-
-class Session(base.CommandBase):
-
-    def __init__(self, host):
-        super(Session, self).__init__(host, 'session', use_output='dict', help=_('running session'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        pass
-
-    def start(self):
-        self.host.bridge.sessionInfosGet(self.host.profile, callback=self._sessionInfoGetCb, errback=self._sessionInfoGetEb)
-
-    def _sessionInfoGetCb(self, data):
-        self.output(data)
-        self.host.quit()
-
-    def _sessionInfoGetEb(self, error_data):
-        self.disp(_(u'Error getting session infos: {}').format(error_data), error=True)
-        self.host.quit(C.EXIT_BRIDGE_ERRBACK)
-
-
-class Info(base.CommandBase):
-    subcommands = (Disco, Version, Session)
-
-    def __init__(self, host):
-        super(Info, self).__init__(host, 'info', use_profile=False, help=_('Get various pieces of information on entities'))
--- a/frontends/src/jp/cmd_input.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,227 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SàT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-
-import base
-from sat.core.i18n import _
-from sat.core import exceptions
-from sat_frontends.jp.constants import Const as C
-from sat.tools.common.ansi import ANSI as A
-import subprocess
-import argparse
-import sys
-
-__commands__ = ["Input"]
-OPT_STDIN = 'stdin'
-OPT_SHORT = 'short'
-OPT_LONG = 'long'
-OPT_POS = 'positional'
-OPT_IGNORE = 'ignore'
-OPT_TYPES = (OPT_STDIN, OPT_SHORT, OPT_LONG, OPT_POS, OPT_IGNORE)
-OPT_EMPTY_SKIP = 'skip'
-OPT_EMPTY_IGNORE = 'ignore'
-OPT_EMPTY_CHOICES = (OPT_EMPTY_SKIP, OPT_EMPTY_IGNORE)
-
-
-class InputCommon(base.CommandBase):
-
-    def __init__(self, host, name, help):
-        base.CommandBase.__init__(self, host, name, use_verbose=True, use_profile=False, help=help)
-        self.idx = 0
-        self.reset()
-
-    def reset(self):
-        self.args_idx = 0
-        self._stdin = []
-        self._opts = []
-        self._pos = []
-        self._values_ori = []
-
-    def add_parser_options(self):
-        self.parser.add_argument("--encoding", default='utf-8', help=_(u"encoding of the input data"))
-        self.parser.add_argument("-i", "--stdin", action='append_const', const=(OPT_STDIN, None), dest='arguments', help=_(u"standard input"))
-        self.parser.add_argument("-s", "--short", type=self.opt(OPT_SHORT), action='append', dest='arguments', help=_(u"short option"))
-        self.parser.add_argument("-l", "--long", type=self.opt(OPT_LONG), action='append', dest='arguments', help=_(u"long option"))
-        self.parser.add_argument("-p", "--positional", type=self.opt(OPT_POS), action='append', dest='arguments', help=_(u"positional argument"))
-        self.parser.add_argument("-x", "--ignore", action='append_const', const=(OPT_IGNORE, None), dest='arguments', help=_(u"ignore value"))
-        self.parser.add_argument("-D", "--debug", action='store_true', help=_(u"don't actually run commands but echo what would be launched"))
-        self.parser.add_argument("--log", type=argparse.FileType('wb'), help=_(u"log stdout to FILE"))
-        self.parser.add_argument("--log-err", type=argparse.FileType('wb'), help=_(u"log stderr to FILE"))
-        self.parser.add_argument("command", nargs=argparse.REMAINDER)
-
-    def opt(self, type_):
-        return lambda s: (type_, s)
-
-    def addValue(self, value):
-        """add a parsed value according to arguments sequence"""
-        self._values_ori.append(value)
-        arguments = self.args.arguments
-        try:
-            arg_type, arg_name = arguments[self.args_idx]
-        except IndexError:
-            self.disp(_(u"arguments in input data and in arguments sequence don't match"), error=True)
-            self.host.quit(C.EXIT_DATA_ERROR)
-        self.args_idx += 1
-        while self.args_idx < len(arguments):
-            next_arg = arguments[self.args_idx]
-            if next_arg[0] not in OPT_TYPES:
-                # value will not be used if False or None, so we skip filter
-                if value not in (False, None):
-                    # we have a filter
-                    filter_type, filter_arg = arguments[self.args_idx]
-                    value = self.filter(filter_type, filter_arg, value)
-            else:
-                break
-            self.args_idx += 1
-
-        if value is None:
-            # we ignore this argument
-            return
-
-        if value is False:
-            # we skip the whole row
-            if self.args.debug:
-                self.disp(A.color(C.A_SUBHEADER, _(u'values: '), A.RESET, u', '.join(self._values_ori)), 2)
-                self.disp(A.color(A.BOLD, _(u'**SKIPPING**\n')))
-            self.reset()
-            self.idx += 1
-            raise exceptions.CancelError
-
-        if not isinstance(value, list):
-            value = [value]
-
-        for v in value:
-            if arg_type == OPT_STDIN:
-                self._stdin.append(v.encode('utf-8'))
-            elif arg_type == OPT_SHORT:
-                self._opts.append('-{}'.format(arg_name))
-                self._opts.append(v.encode('utf-8'))
-            elif arg_type == OPT_LONG:
-                self._opts.append('--{}'.format(arg_name))
-                self._opts.append(v.encode('utf-8'))
-            elif arg_type == OPT_POS:
-                self._pos.append(v.encode('utf-8'))
-            elif arg_type == OPT_IGNORE:
-                pass
-            else:
-                self.parser.error(_(u"Invalid argument, an option type is expected, got {type_}:{name}").format(
-                                  type_=arg_type, name=arg_name))
-
-    def runCommand(self):
-        """run requested command with parsed arguments"""
-        if self.args_idx != len(self.args.arguments):
-            self.disp(_(u"arguments in input data and in arguments sequence don't match"), error=True)
-            self.host.quit(C.EXIT_DATA_ERROR)
-        self.disp(A.color(C.A_HEADER, _(u'command {idx}').format(idx=self.idx)), no_lf=not self.args.debug)
-        stdin = ''.join(self._stdin)
-        if self.args.debug:
-            self.disp(A.color(C.A_SUBHEADER, _(u'values: '), A.RESET, u', '.join(self._values_ori)), 2)
-
-            if stdin:
-                self.disp(A.color(C.A_SUBHEADER, u'--- STDIN ---'))
-                self.disp(stdin.decode('utf-8'))
-                self.disp(A.color(C.A_SUBHEADER, u'-------------'))
-            self.disp(u'{indent}{prog} {static} {options} {positionals}'.format(
-                indent = 4*u' ',
-                prog=sys.argv[0],
-                static = ' '.join(self.args.command).decode('utf-8'),
-                options = u' '.join([o.decode('utf-8') for o in self._opts]),
-                positionals = u' '.join([p.decode('utf-8') for p in self._pos])
-                ))
-            self.disp(u'\n')
-        else:
-            self.disp(u' (' + u', '.join(self._values_ori) + u')', 2, no_lf=True)
-            args = [sys.argv[0]] + self.args.command + self._opts + self._pos
-            p = subprocess.Popen(args,
-                                  stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-            (stdout, stderr) = p.communicate(stdin)
-            log = self.args.log
-            log_err = self.args.log_err
-            log_tpl = '{command}\n{buff}\n\n'
-            if log:
-                log.write(log_tpl.format(command=' '.join(args), buff=stdout))
-            if log_err:
-                log_err.write(log_tpl.format(command=' '.join(args), buff=stderr))
-            ret = p.wait()
-            if ret == 0:
-                self.disp(A.color(C.A_SUCCESS, _(u'OK')))
-            else:
-                self.disp(A.color(C.A_FAILURE, _(u'FAILED')))
-
-        self.reset()
-        self.idx += 1
-
-    def filter(self, filter_type, filter_arg, value):
-        """change input value
-
-        @param filter_type(unicode): name of the filter
-        @param filter_arg(unicode, None): argument of the filter
-        @param value(unicode): value to filter
-        @return (unicode, False, None): modified value
-            False to skip the whole row
-            None to ignore this argument (but continue row with other ones)
-        """
-        raise NotImplementedError
-
-
-class Csv(InputCommon):
-
-    def __init__(self, host):
-        super(Csv, self).__init__(host, 'csv', _(u'comma-separated values'))
-
-    def add_parser_options(self):
-        InputCommon.add_parser_options(self)
-        self.parser.add_argument("-r", "--row", type=int, default=0, help=_(u"starting row (previous ones will be ignored)"))
-        self.parser.add_argument("-S", "--split", action='append_const', const=('split', None), dest='arguments', help=_(u"split value in several options"))
-        self.parser.add_argument("-E", "--empty", action='append', type=self.opt('empty'), dest='arguments',
-                                 help=_(u"action to do on empty value ({choices})").format(choices=u', '.join(OPT_EMPTY_CHOICES)))
-
-    def filter(self, filter_type, filter_arg, value):
-        if filter_type == 'split':
-            return value.split()
-        elif filter_type == 'empty':
-            if filter_arg == OPT_EMPTY_IGNORE:
-                return value if value else None
-            elif filter_arg == OPT_EMPTY_SKIP:
-                return value if value else False
-            else:
-                self.parser.error(_(u"--empty value must be one of {choices}").format(choices=u', '.join(OPT_EMPTY_CHOICES)))
-
-        super(Csv, self).filter(filter_type, filter_arg, value)
-
-    def start(self):
-        import csv
-        reader = csv.reader(sys.stdin)
-        for idx, row in enumerate(reader):
-            try:
-                if idx < self.args.row:
-                    continue
-                for value in row:
-                    self.addValue(value.decode(self.args.encoding))
-                self.runCommand()
-            except exceptions.CancelError:
-                # this row has been cancelled, we skip it
-                continue
-
-
-class Input(base.CommandBase):
-    subcommands = (Csv,)
-
-    def __init__(self, host):
-        super(Input, self).__init__(host, 'input', use_profile=False, help=_(u'launch command with external input'))
--- a/frontends/src/jp/cmd_invitation.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,219 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SàT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-
-import base
-from sat.core.i18n import _
-from sat_frontends.jp.constants import Const as C
-from sat.tools.common.ansi import ANSI as A
-from sat.tools.common import data_format
-from functools import partial
-
-__commands__ = ["Invitation"]
-
-
-class Create(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'create', use_profile=False, use_output=C.OUTPUT_DICT, help=_(u'create and send an invitation'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("-j", "--jid", type=base.unicode_decoder, default='', help='jid of the invitee (default: generate one)')
-        self.parser.add_argument("-P", "--password", type=base.unicode_decoder, default='', help='password of the invitee profile/XMPP account (default: generate one)')
-        self.parser.add_argument("-n", "--name", type=base.unicode_decoder, default='', help='name of the invitee')
-        self.parser.add_argument("-N", "--host-name", type=base.unicode_decoder, default='', help='name of the host')
-        self.parser.add_argument("-e", "--email", action="append", type=base.unicode_decoder, default=[], help='email(s) to send the invitation to (if --no-email is set, email will just be saved)')
-        self.parser.add_argument("--no-email", action="store_true", help='do NOT send invitation email')
-        self.parser.add_argument("-l", "--lang", type=base.unicode_decoder, default='', help='main language spoken by the invitee')
-        self.parser.add_argument("-u", "--url", type=base.unicode_decoder, default='', help='template to construct the URL')
-        self.parser.add_argument("-s", "--subject", type=base.unicode_decoder, default='', help='subject of the invitation email (default: generic subject)')
-        self.parser.add_argument("-b", "--body", type=base.unicode_decoder, default='', help='body of the invitation email (default: generic body)')
-        self.parser.add_argument("-x", "--extra", metavar=('KEY', 'VALUE'), type=base.unicode_decoder, action='append', nargs=2, default=[], help='extra data to associate with invitation/invitee')
-        self.parser.add_argument("-p", "--profile", type=base.unicode_decoder, default='', help="profile doing the invitation (default: don't associate profile)")
-
-    def invitationCreateCb(self, invitation_data):
-        self.output(invitation_data)
-        self.host.quit(C.EXIT_OK)
-
-    def invitationCreateEb(self, failure_):
-        self.disp(u"can't create invitation: {reason}".format(
-            reason=failure_), error=True)
-        self.host.quit(C.EXIT_BRIDGE_ERRBACK)
-
-    def start(self):
-        extra = dict(self.args.extra)
-        email = self.args.email[0] if self.args.email else None
-        emails_extra = self.args.email[1:]
-        if self.args.no_email:
-            if email:
-                extra['email'] = email
-                data_format.iter2dict(u'emails_extra', emails_extra)
-        else:
-            if not email:
-                self.parser.error(_(u'you need to specify an email address to send email invitation'))
-
-        self.host.bridge.invitationCreate(
-            email,
-            emails_extra,
-            self.args.jid,
-            self.args.password,
-            self.args.name,
-            self.args.host_name,
-            self.args.lang,
-            self.args.url,
-            self.args.subject,
-            self.args.body,
-            extra,
-            self.args.profile,
-            callback=self.invitationCreateCb,
-            errback=self.invitationCreateEb)
-
-
-class Get(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'get', use_profile=False, use_output=C.OUTPUT_DICT, help=_(u'get invitation data'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("id", type=base.unicode_decoder,
-                                 help=_(u"invitation UUID"))
-        self.parser.add_argument("-j", "--with-jid", action="store_true", help=_(u"start profile session and retrieve jid"))
-
-    def output_data(self, data, jid_=None):
-        if jid_ is not None:
-            data['jid'] = jid_
-        self.output(data)
-        self.host.quit()
-
-    def invitationGetCb(self, invitation_data):
-        if self.args.with_jid:
-            profile = invitation_data[u'guest_profile']
-            def session_started(dummy):
-                self.host.bridge.asyncGetParamA(
-                    u'JabberID',
-                    u'Connection',
-                    profile_key=profile,
-                    callback=lambda jid_: self.output_data(invitation_data, jid_),
-                    errback=partial(self.errback,
-                                    msg=_(u"can't retrieve jid: {}"),
-                                    exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-            self.host.bridge.profileStartSession(
-                invitation_data[u'password'],
-                profile,
-                callback=session_started,
-                errback=partial(self.errback,
-                                msg=_(u"can't start session: {}"),
-                                exit_code=C.EXIT_BRIDGE_ERRBACK))
-        else:
-            self.output_data(invitation_data)
-
-    def start(self):
-        self.host.bridge.invitationGet(
-            self.args.id,
-            callback=self.invitationGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get invitation data: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class Modify(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'modify', use_profile=False, help=_(u'modify existing invitation'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("--replace", action='store_true', help='replace the whole data')
-        self.parser.add_argument("-n", "--name", type=base.unicode_decoder, default='', help='name of the invitee')
-        self.parser.add_argument("-N", "--host-name", type=base.unicode_decoder, default='', help='name of the host')
-        self.parser.add_argument("-e", "--email", type=base.unicode_decoder, default='', help='email to send the invitation to (if --no-email is set, email will just be saved)')
-        self.parser.add_argument("-l", "--lang", dest="language", type=base.unicode_decoder, default='',
-            help='main language spoken by the invitee')
-        self.parser.add_argument("-x", "--extra", metavar=('KEY', 'VALUE'), type=base.unicode_decoder, action='append', nargs=2, default=[], help='extra data to associate with invitation/invitee')
-        self.parser.add_argument("-p", "--profile", type=base.unicode_decoder, default='', help="profile doing the invitation (default: don't associate profile")
-        self.parser.add_argument("id", type=base.unicode_decoder,
-                                 help=_(u"invitation UUID"))
-
-    def invitationModifyCb(self):
-        self.disp(_(u'invitations have been modified correctly'))
-        self.host.quit(C.EXIT_OK)
-
-    def invitationModifyEb(self, failure_):
-        self.disp(u"can't create invitation: {reason}".format(
-            reason=failure_), error=True)
-        self.host.quit(C.EXIT_BRIDGE_ERRBACK)
-
-    def start(self):
-        extra = dict(self.args.extra)
-        for arg_name in ('name', 'host_name', 'email', 'language', 'profile'):
-            value = getattr(self.args, arg_name)
-            if not value:
-                continue
-            if arg_name in extra:
-                self.parser.error(_(u"you can't set {arg_name} in both optional argument and extra").format(arg_name=arg_name))
-            extra[arg_name] = value
-        self.host.bridge.invitationModify(
-            self.args.id,
-            extra,
-            self.args.replace,
-            callback=self.invitationModifyCb,
-            errback=self.invitationModifyEb)
-
-
-class List(base.CommandBase):
-
-    def __init__(self, host):
-        extra_outputs = {'default': self.default_output}
-        base.CommandBase.__init__(self, host, 'list', use_profile=False, use_output=C.OUTPUT_COMPLEX, extra_outputs=extra_outputs, help=_(u'list invitations data'))
-        self.need_loop=True
-
-    def default_output(self, data):
-        for idx, datum in enumerate(data.iteritems()):
-            if idx:
-                self.disp(u"\n")
-            key, invitation_data = datum
-            self.disp(A.color(C.A_HEADER, key))
-            indent = u'  '
-            for k, v in invitation_data.iteritems():
-                self.disp(indent + A.color(C.A_SUBHEADER, k + u':') + u' ' + unicode(v))
-
-    def add_parser_options(self):
-        self.parser.add_argument("-p", "--profile", default=C.PROF_KEY_NONE, help=_(u"return only invitations linked to this profile"))
-
-    def invitationListCb(self, data):
-        self.output(data)
-        self.host.quit()
-
-    def start(self):
-        self.host.bridge.invitationList(
-            self.args.profile,
-            callback=self.invitationListCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't list invitations: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class Invitation(base.CommandBase):
-    subcommands = (Create, Get, Modify, List)
-
-    def __init__(self, host):
-        super(Invitation, self).__init__(host, 'invitation', use_profile=False, help=_(u'invitation of user(s) without XMPP account'))
--- a/frontends/src/jp/cmd_merge_request.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,165 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SàT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-
-import base
-from sat.core.i18n import _
-from sat_frontends.jp.constants import Const as C
-from sat_frontends.jp import xmlui_manager
-from sat_frontends.jp import common
-from functools import partial
-import os.path
-
-__commands__ = ["MergeRequest"]
-
-
-class Set(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'set', use_pubsub=True,
-                                  pubsub_defaults = {u'service': _(u'auto'), u'node': _(u'auto')},
-                                  help=_(u'publish or update a merge request'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("-i", "--item", type=base.unicode_decoder, default=u'', help=_(u"id or URL of the request to update, or nothing for a new one"))
-        self.parser.add_argument("-r", "--repository", metavar="PATH", type=base.unicode_decoder, default=u'.', help=_(u"path of the repository (DEFAULT: current directory)"))
-        self.parser.add_argument("-f", "--force", action="store_true", help=_(u"publish merge request without confirmation"))
-        self.parser.add_argument("-l", "--label", dest="labels", type=base.unicode_decoder, action='append', help=_(u"labels to categorize your request"))
-
-    def mergeRequestSetCb(self, published_id):
-        if published_id:
-            self.disp(u"Merge request published at {pub_id}".format(pub_id=published_id))
-        else:
-            self.disp(u"Merge request published")
-        self.host.quit(C.EXIT_OK)
-
-    def sendRequest(self):
-        extra = {'update': 'true'} if self.args.item else {}
-        values = {}
-        if self.args.labels is not None:
-            values[u'labels'] = self.args.labels
-        self.host.bridge.mergeRequestSet(
-            self.args.service,
-            self.args.node,
-            self.repository,
-            u'auto',
-            values,
-            u'',
-            self.args.item,
-            extra,
-            self.profile,
-            callback=self.mergeRequestSetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't create merge request: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-    def askConfirmation(self):
-        if not self.args.force:
-            message = _(u"You are going to publish your changes to service [{service}], are you sure ?").format(
-                service=self.args.service)
-            self.host.confirmOrQuit(message, _(u"merge request publication cancelled"))
-        self.sendRequest()
-
-    def start(self):
-        self.repository = os.path.expanduser(os.path.abspath(self.args.repository))
-        common.URIFinder(self, self.repository, 'merge requests', self.askConfirmation)
-
-
-class Get(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'get', use_verbose=True,
-                                  use_pubsub=True, pubsub_flags={C.MULTI_ITEMS},
-                                  pubsub_defaults = {u'service': _(u'auto'), u'node': _(u'auto')},
-                                  help=_(u'get a merge request'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        pass
-
-    def mergeRequestGetCb(self, requests_data):
-        if self.verbosity >= 1:
-            whitelist = None
-        else:
-            whitelist = {'id', 'title', 'body'}
-        for request_xmlui in requests_data[0]:
-            xmlui = xmlui_manager.create(self.host, request_xmlui, whitelist=whitelist)
-            xmlui.show(values_only=True)
-            self.disp(u'')
-        self.host.quit(C.EXIT_OK)
-
-    def getRequests(self):
-        extra = {}
-        self.host.bridge.mergeRequestsGet(
-            self.args.service,
-            self.args.node,
-            self.args.max,
-            self.args.items,
-            u'',
-            extra,
-            self.profile,
-            callback=self.mergeRequestGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get merge request: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-    def start(self):
-        common.URIFinder(self, os.getcwd(), 'merge requests', self.getRequests, meta_map={})
-
-
-class Import(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'import',
-                                  use_pubsub=True, pubsub_flags={C.SINGLE_ITEM, C.ITEM},
-                                  pubsub_defaults = {u'service': _(u'auto'), u'node': _(u'auto')},
-                                  help=_(u'import a merge request'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("-r", "--repository", metavar="PATH", type=base.unicode_decoder, default=u'.', help=_(u"path of the repository (DEFAULT: current directory)"))
-
-    def mergeRequestImportCb(self):
-        self.host.quit(C.EXIT_OK)
-
-    def importRequest(self):
-        extra = {}
-        self.host.bridge.mergeRequestsImport(
-            self.repository,
-            self.args.item,
-            self.args.service,
-            self.args.node,
-            extra,
-            self.profile,
-            callback=self.mergeRequestImportCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't import merge request: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-    def start(self):
-        self.repository = os.path.expanduser(os.path.abspath(self.args.repository))
-        common.URIFinder(self, self.repository, 'merge requests', self.importRequest, meta_map={})
-
-
-class MergeRequest(base.CommandBase):
-    subcommands = (Set, Get, Import)
-
-    def __init__(self, host):
-        super(MergeRequest, self).__init__(host, 'merge-request', use_profile=False, help=_('merge-request management'))
--- a/frontends/src/jp/cmd_message.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SAT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-from sat_frontends.jp import base
-import sys
-from sat.core.i18n import _
-from sat.core.constants import Const as C
-from sat.tools.utils import clean_ustr
-
-__commands__ = ["Message"]
-
-
-class Send(base.CommandBase):
-
-    def __init__(self, host):
-        super(Send, self).__init__(host, 'send', help=_('send a message to a contact'))
-
-    def add_parser_options(self):
-        self.parser.add_argument("-l", "--lang", type=str, default='', help=_(u"language of the message"))
-        self.parser.add_argument("-s", "--separate", action="store_true", help=_(u"separate xmpp messages: send one message per line instead of one message alone."))
-        self.parser.add_argument("-n", "--new-line", action="store_true", help=_(u"add a new line at the beginning of the input (usefull for ascii art ;))"))
-        self.parser.add_argument("-S", "--subject", type=base.unicode_decoder, help=_(u"subject of the message"))
-        self.parser.add_argument("-L", "--subject_lang", type=str, default='', help=_(u"language of subject"))
-        self.parser.add_argument("-t", "--type", choices=C.MESS_TYPE_STANDARD + (C.MESS_TYPE_AUTO,), default=C.MESS_TYPE_AUTO, help=_("type of the message"))
-        syntax = self.parser.add_mutually_exclusive_group()
-        syntax.add_argument("-x", "--xhtml", action="store_true", help=_(u"XHTML body"))
-        syntax.add_argument("-r", "--rich", action="store_true", help=_(u"rich body"))
-        self.parser.add_argument("jid", type=base.unicode_decoder, help=_(u"the destination jid"))
-
-    def start(self):
-        if self.args.xhtml and self.args.separate:
-            self.disp(u"argument -s/--separate is not compatible yet with argument -x/--xhtml", error=True)
-            self.host.quit(2)
-
-        jids = self.host.check_jids([self.args.jid])
-        jid = jids[0]
-        self.sendStdin(jid)
-
-    def sendStdin(self, dest_jid):
-        """Send incomming data on stdin to jabber contact
-
-        @param dest_jid: destination jid
-        """
-        header = "\n" if self.args.new_line else ""
-        stdin_lines = [stream.decode('utf-8','ignore') for stream in sys.stdin.readlines()]
-        extra = {}
-        if self.args.subject is None:
-            subject = {}
-        else:
-            subject = {self.args.subject_lang: self.args.subject}
-
-        if self.args.xhtml or self.args.rich:
-            key = u"xhtml" if self.args.xhtml else u"rich"
-            if self.args.lang:
-                key = u"{}_{}".format(key, self.args.lang)
-            extra[key] = clean_ustr(u"".join(stdin_lines))
-            stdin_lines = []
-
-        if self.args.separate:  #we send stdin in several messages
-            if header:
-                self.host.bridge.messageSend(dest_jid, {self.args.lang: header}, subject, self.args.type, profile_key=self.profile, callback=lambda: None, errback=lambda ignore: ignore)
-
-            for line in stdin_lines:
-                self.host.bridge.messageSend(dest_jid, {self.args.lang: line.replace("\n","")}, subject, self.args.type, extra, profile_key=self.host.profile, callback=lambda: None, errback=lambda ignore: ignore)
-
-        else:
-            msg = {self.args.lang: header + clean_ustr(u"".join(stdin_lines))} if not (self.args.xhtml or self.args.rich) else {}
-            self.host.bridge.messageSend(dest_jid, msg, subject, self.args.type, extra, profile_key=self.host.profile, callback=lambda: None, errback=lambda ignore: ignore)
-
-
-class Message(base.CommandBase):
-    subcommands = (Send, )
-
-    def __init__(self, host):
-        super(Message, self).__init__(host, 'message', use_profile=False, help=_('messages handling'))
--- a/frontends/src/jp/cmd_param.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SAT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-# Copyright (C) 2013-2016 Adrien Cossa (souliane@mailoo.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-
-import base
-from sat.core.i18n import _
-__commands__ = ["Param"]
-
-
-class Get(base.CommandBase):
-    def __init__(self, host):
-        super(Get, self).__init__(host, 'get', need_connect=False, help=_('Get a parameter value'))
-
-    def add_parser_options(self):
-        self.parser.add_argument("category", nargs='?', type=base.unicode_decoder, help=_(u"Category of the parameter"))
-        self.parser.add_argument("name", nargs='?', type=base.unicode_decoder, help=_(u"Name of the parameter"))
-        self.parser.add_argument("-a", "--attribute", type=str, default="value", help=_(u"Name of the attribute to get"))
-        self.parser.add_argument("--security-limit", type=int, default=-1, help=_(u"Security limit"))
-
-    def start(self):
-        if self.args.category is None:
-            categories = self.host.bridge.getParamsCategories()
-            print u"\n".join(categories)
-        elif self.args.name is None:
-            try:
-                values_dict = self.host.bridge.asyncGetParamsValuesFromCategory(self.args.category, self.args.security_limit, self.profile)
-            except Exception as e:
-                print u"Can't find requested parameters: {}".format(e)
-                self.host.quit(1)
-            for name, value in values_dict.iteritems():
-                print u"{}\t{}".format(name, value)
-        else:
-            try:
-                value = self.host.bridge.asyncGetParamA(self.args.name, self.args.category, self.args.attribute,
-                                                                  self.args.security_limit, self.profile)
-            except Exception as e:
-                print u"Can't find requested parameter: {}".format(e)
-                self.host.quit(1)
-            print value
-
-
-class Set(base.CommandBase):
-    def __init__(self, host):
-        super(Set, self).__init__(host, 'set', need_connect=False, help=_('Set a parameter value'))
-
-    def add_parser_options(self):
-        self.parser.add_argument("category", type=base.unicode_decoder, help=_(u"Category of the parameter"))
-        self.parser.add_argument("name", type=base.unicode_decoder, help=_(u"Name of the parameter"))
-        self.parser.add_argument("value", type=base.unicode_decoder, help=_(u"Name of the parameter"))
-        self.parser.add_argument("--security-limit", type=int, default=-1, help=_(u"Security limit"))
-
-    def start(self):
-        try:
-            self.host.bridge.setParam(self.args.name, self.args.value, self.args.category, self.args.security_limit, self.profile)
-        except Exception as e:
-            print u"Can set requested parameter: {}".format(e)
-
-
-class SaveTemplate(base.CommandBase):
-    def __init__(self, host):
-        super(SaveTemplate, self).__init__(host, 'save', use_profile=False, help=_('Save parameters template to xml file'))
-
-    def add_parser_options(self):
-        self.parser.add_argument("filename", type=str, help=_("Output file"))
-
-    def start(self):
-        """Save parameters template to xml file"""
-        if self.host.bridge.saveParamsTemplate(self.args.filename):
-            print _("Parameters saved to file %s") % self.args.filename
-        else:
-            print _("Can't save parameters to file %s") % self.args.filename
-
-
-class LoadTemplate(base.CommandBase):
-
-    def __init__(self, host):
-        super(LoadTemplate, self).__init__(host, 'load', use_profile=False, help=_('Load parameters template from xml file'))
-
-    def add_parser_options(self):
-        self.parser.add_argument("filename", type=str, help=_("Input file"))
-
-    def start(self):
-        """Load parameters template from xml file"""
-        if self.host.bridge.loadParamsTemplate(self.args.filename):
-            print _("Parameters loaded from file %s") % self.args.filename
-        else:
-            print _("Can't load parameters from file %s") % self.args.filename
-
-
-class Param(base.CommandBase):
-    subcommands = (Get, Set, SaveTemplate, LoadTemplate)
-
-    def __init__(self, host):
-        super(Param, self).__init__(host, 'param', use_profile=False, help=_('Save/load parameters template'))
--- a/frontends/src/jp/cmd_pipe.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,151 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SAT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-from sat_frontends.jp import base
-
-from sat_frontends.jp.constants import Const as C
-import sys
-from sat.core.i18n import _
-from sat_frontends.tools import jid
-import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI
-from functools import partial
-import socket
-import SocketServer
-import errno
-
-__commands__ = ["Pipe"]
-
-START_PORT = 9999
-
-class PipeOut(base.CommandBase):
-
-    def __init__(self, host):
-        super(PipeOut, self).__init__(host, 'out', help=_('send a pipe a stream'))
-        self.need_loop = True
-
-    def add_parser_options(self):
-        self.parser.add_argument("jid", type=base.unicode_decoder, help=_("the destination jid"))
-
-    def streamOutCb(self, port):
-        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        s.connect(('127.0.0.1', int(port)))
-        while True:
-            buf = sys.stdin.read(4096)
-            if not buf:
-                break
-            try:
-                s.sendall(buf)
-            except socket.error as e:
-                if e.errno == errno.EPIPE:
-                    sys.stderr.write(str(e) + '\n')
-                    self.host.quit(1)
-                else:
-                    raise e
-        self.host.quit()
-
-    def start(self):
-        """ Create named pipe, and send stdin to it """
-        self.host.bridge.streamOut(
-            self.host.get_full_jid(self.args.jid),
-            self.profile,
-            callback=self.streamOutCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't start stream: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class StreamServer(SocketServer.BaseRequestHandler):
-
-    def handle(self):
-        while True:
-            data = self.request.recv(4096)
-            if not data:
-                break
-            sys.stdout.write(data)
-            try:
-                sys.stdout.flush()
-            except IOError as e:
-                sys.stderr.write(str(e) + '\n')
-                break
-        # calling shutdown will do a deadlock as we don't use separate thread
-        # this is a workaround (cf. https://stackoverflow.com/a/36017741)
-        self.server._BaseServer__shutdown_request = True
-
-
-class PipeIn(base.CommandAnswering):
-
-    def __init__(self, host):
-        super(PipeIn, self).__init__(host, 'in', help=_('receive a pipe stream'))
-        self.action_callbacks = {"STREAM": self.onStreamAction}
-
-    def add_parser_options(self):
-        self.parser.add_argument("jids", type=base.unicode_decoder, nargs="*", help=_('Jids accepted (none means "accept everything")'))
-
-    def getXmluiId(self, action_data):
-        # FIXME: we temporarily use ElementTree, but a real XMLUI managing module
-        #        should be available in the future
-        # TODO: XMLUI module
-        try:
-            xml_ui = action_data['xmlui']
-        except KeyError:
-            self.disp(_(u"Action has no XMLUI"), 1)
-        else:
-            ui = ET.fromstring(xml_ui.encode('utf-8'))
-            xmlui_id = ui.get('submit')
-            if not xmlui_id:
-                self.disp(_(u"Invalid XMLUI received"), error=True)
-            return xmlui_id
-
-    def onStreamAction(self, action_data, action_id, security_limit, profile):
-        xmlui_id = self.getXmluiId(action_data)
-        if xmlui_id is None:
-            return self.host.quitFromSignal(1)
-        try:
-            from_jid = jid.JID(action_data['meta_from_jid'])
-        except KeyError:
-            self.disp(_(u"Ignoring action without from_jid data"), 1)
-            return
-
-        if not self.bare_jids or from_jid.bare in self.bare_jids:
-            host, port = "localhost", START_PORT
-            while True:
-                try:
-                    server = SocketServer.TCPServer((host, port), StreamServer)
-                except socket.error as e:
-                    if e.errno == errno.EADDRINUSE:
-                        port += 1
-                    else:
-                        raise e
-                else:
-                    break
-            xmlui_data = {'answer': C.BOOL_TRUE,
-                          'port': unicode(port)}
-            self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile)
-            server.serve_forever()
-            self.host.quitFromSignal()
-
-    def start(self):
-        self.bare_jids = [jid.JID(jid_).bare for jid_ in self.args.jids]
-
-
-class Pipe(base.CommandBase):
-    subcommands = (PipeOut, PipeIn)
-
-    def __init__(self, host):
-        super(Pipe, self).__init__(host, 'pipe', use_profile=False, help=_('stream piping through XMPP'))
--- a/frontends/src/jp/cmd_profile.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,209 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SAT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-"""This module permits to manage profiles. It can list, create, delete
-and retrieve information about a profile."""
-
-from sat_frontends.jp.constants import Const as C
-from sat.core.log import getLogger
-log = getLogger(__name__)
-from sat.core.i18n import _
-from sat_frontends.jp import base
-from functools import partial
-
-__commands__ = ["Profile"]
-
-PROFILE_HELP = _('The name of the profile')
-
-
-class ProfileConnect(base.CommandBase):
-    """Dummy command to use profile_session parent, i.e. to be able to connect without doing anything else"""
-
-    def __init__(self, host):
-        # it's weird to have a command named "connect" with need_connect=False, but it can be handy to be able
-        # to launch just the session, so some paradoxes don't hurt
-        super(ProfileConnect, self).__init__(host, 'connect', need_connect=False, help=(u'connect a profile'))
-
-    def add_parser_options(self):
-        pass
-
-
-class ProfileDisconnect(base.CommandBase):
-
-    def __init__(self, host):
-        super(ProfileDisconnect, self).__init__(host, 'disconnect', need_connect=False, help=(u'disconnect a profile'))
-        self.need_loop = True
-
-    def add_parser_options(self):
-        pass
-
-    def start(self):
-        self.host.bridge.disconnect(self.args.profile, callback=self.host.quit)
-
-
-class ProfileDefault(base.CommandBase):
-    def __init__(self, host):
-        super(ProfileDefault, self).__init__(host, 'default', use_profile=False, help=(u'print default profile'))
-
-    def add_parser_options(self):
-        pass
-
-    def start(self):
-        print self.host.bridge.profileNameGet('@DEFAULT@')
-
-
-class ProfileDelete(base.CommandBase):
-    def __init__(self, host):
-        super(ProfileDelete, self).__init__(host, 'delete', use_profile=False, help=(u'delete a profile'))
-
-    def add_parser_options(self):
-        self.parser.add_argument('profile', type=str, help=PROFILE_HELP)
-        self.parser.add_argument('-f', '--force', action='store_true', help=_(u'delete profile without confirmation'))
-
-    def start(self):
-        if self.args.profile not in self.host.bridge.profilesListGet():
-            log.error("Profile %s doesn't exist." % self.args.profile)
-            self.host.quit(1)
-        if not self.args.force:
-            message = u"Are you sure to delete profile [{}] ?".format(self.args.profile)
-            res = raw_input("{} (y/N)? ".format(message))
-            if res not in ("y", "Y"):
-                self.disp(_(u"Profile deletion cancelled"))
-                self.host.quit(2)
-
-        self.host.bridge.asyncDeleteProfile(self.args.profile, callback=lambda dummy: None)
-
-
-class ProfileInfo(base.CommandBase):
-    def __init__(self, host):
-        super(ProfileInfo, self).__init__(host, 'info', need_connect=False, help=_(u'get information about a profile'))
-        self.need_loop = True
-        self.to_show = [(_(u"jid"), "Connection", "JabberID"),]
-        self.largest = max([len(item[0]) for item in self.to_show])
-
-
-    def add_parser_options(self):
-        self.parser.add_argument('--show-password', action='store_true', help=_(u'show the XMPP password IN CLEAR TEXT'))
-
-    def showNextValue(self, label=None, category=None, value=None):
-        """Show next value from self.to_show and quit on last one"""
-        if label is not None:
-            print((u"{label:<"+unicode(self.largest+2)+"}{value}").format(label=label+": ", value=value))
-        try:
-            label, category, name = self.to_show.pop(0)
-        except IndexError:
-            self.host.quit()
-        else:
-            self.host.bridge.asyncGetParamA(name, category, profile_key=self.host.profile,
-                                            callback=lambda value: self.showNextValue(label, category, value))
-
-    def start(self):
-        if self.args.show_password:
-            self.to_show.append((_(u"XMPP password"), "Connection", "Password"))
-        self.showNextValue()
-
-
-class ProfileList(base.CommandBase):
-    def __init__(self, host):
-        super(ProfileList, self).__init__(host, 'list', use_profile=False, use_output='list', help=(u'list profiles'))
-
-    def add_parser_options(self):
-        group = self.parser.add_mutually_exclusive_group()
-        group.add_argument('-c', '--clients', action='store_true', help=_(u'get clients profiles only'))
-        group.add_argument('-C', '--components', action='store_true', help=(u'get components profiles only'))
-
-
-    def start(self):
-        if self.args.clients:
-            clients, components = True, False
-        elif self.args.components:
-            clients, components = False, True
-        else:
-            clients, components = True, True
-        self.output(self.host.bridge.profilesListGet(clients, components))
-
-
-class ProfileCreate(base.CommandBase):
-    def __init__(self, host):
-        super(ProfileCreate, self).__init__(host, 'create', use_profile=False, help=(u'create a new profile'))
-        self.need_loop = True
-
-    def add_parser_options(self):
-        self.parser.add_argument('profile', type=str, help=_(u'the name of the profile'))
-        self.parser.add_argument('-p', '--password', type=str, default='', help=_(u'the password of the profile'))
-        self.parser.add_argument('-j', '--jid', type=str, help=_(u'the jid of the profile'))
-        self.parser.add_argument('-x', '--xmpp-password', type=str, help=_(u'the password of the XMPP account (use profile password if not specified)'),
-                                 metavar='PASSWORD')
-        self.parser.add_argument('-C', '--component', type=base.unicode_decoder, default='',
-                                 help=_(u'set to component import name (entry point) if this is a component'))
-
-    def _session_started(self, dummy):
-        if self.args.jid:
-            self.host.bridge.setParam("JabberID", self.args.jid, "Connection", profile_key=self.args.profile)
-        xmpp_pwd = self.args.password or self.args.xmpp_password
-        if xmpp_pwd:
-            self.host.bridge.setParam("Password", xmpp_pwd, "Connection", profile_key=self.args.profile)
-        self.host.quit()
-
-    def _profile_created(self):
-        self.host.bridge.profileStartSession(self.args.password, self.args.profile, callback=self._session_started, errback=None)
-
-    def start(self):
-        """Create a new profile"""
-        if self.args.profile in self.host.bridge.profilesListGet():
-            log.error("Profile %s already exists." % self.args.profile)
-            self.host.quit(1)
-        self.host.bridge.profileCreate(self.args.profile, self.args.password, self.args.component,
-                                       callback=self._profile_created,
-                                       errback=partial(self.errback,
-                                                       msg=_(u"can't create profile: {}"),
-                                                       exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class ProfileModify(base.CommandBase):
-    def __init__(self, host):
-        super(ProfileModify, self).__init__(host, 'modify', need_connect=False, help=_(u'modify an existing profile'))
-
-    def add_parser_options(self):
-        profile_pwd_group = self.parser.add_mutually_exclusive_group()
-        profile_pwd_group.add_argument('-w', '--password', type=base.unicode_decoder, help=_(u'change the password of the profile'))
-        profile_pwd_group.add_argument('--disable-password', action='store_true', help=_(u'disable profile password (dangerous!)'))
-        self.parser.add_argument('-j', '--jid', type=base.unicode_decoder, help=_(u'the jid of the profile'))
-        self.parser.add_argument('-x', '--xmpp-password', type=base.unicode_decoder, help=_(u'change the password of the XMPP account'),
-                                 metavar='PASSWORD')
-        self.parser.add_argument('-D', '--default', action='store_true', help=_(u'set as default profile'))
-
-    def start(self):
-        if self.args.disable_password:
-            self.args.password = ''
-        if self.args.password is not None:
-            self.host.bridge.setParam("Password", self.args.password, "General", profile_key=self.host.profile)
-        if self.args.jid is not None:
-            self.host.bridge.setParam("JabberID", self.args.jid, "Connection", profile_key=self.host.profile)
-        if self.args.xmpp_password is not None:
-            self.host.bridge.setParam("Password", self.args.xmpp_password, "Connection", profile_key=self.host.profile)
-        if self.args.default:
-            self.host.bridge.profileSetDefault(self.host.profile)
-
-
-class Profile(base.CommandBase):
-    subcommands = (ProfileConnect, ProfileDisconnect, ProfileCreate, ProfileDefault, ProfileDelete, ProfileInfo, ProfileList, ProfileModify)
-
-    def __init__(self, host):
-        super(Profile, self).__init__(host, 'profile', use_profile=False, help=_(u'profile commands'))
--- a/frontends/src/jp/cmd_pubsub.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1198 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SàT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-
-import base
-from sat.core.i18n import _
-from sat.core import exceptions
-from sat_frontends.jp.constants import Const as C
-from sat_frontends.jp import common
-from sat_frontends.jp import arg_tools
-from functools import partial
-from sat.tools.common import uri
-from sat.tools.common.ansi import ANSI as A
-from sat_frontends.tools import jid, strings
-import argparse
-import os.path
-import re
-import subprocess
-import sys
-
-__commands__ = ["Pubsub"]
-
-PUBSUB_TMP_DIR = u"pubsub"
-PUBSUB_SCHEMA_TMP_DIR = PUBSUB_TMP_DIR + "_schema"
-ALLOWED_SUBSCRIPTIONS_OWNER = ('subscribed', 'pending', 'none')
-
-# TODO: need to split this class in several modules, plugin should handle subcommands
-
-
-class NodeInfo(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'info', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'retrieve node configuration'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("-k", "--key", type=base.unicode_decoder, action='append', dest='keys',
-                                 help=_(u"data key to filter"))
-
-    def removePrefix(self, key):
-        return key[7:] if key.startswith(u"pubsub#") else key
-
-    def filterKey(self, key):
-        return any((key == k or key == u'pubsub#' + k) for k in self.args.keys)
-
-    def psNodeConfigurationGetCb(self, config_dict):
-        key_filter = (lambda k: True) if not self.args.keys else self.filterKey
-        config_dict = {self.removePrefix(k):v for k,v in config_dict.iteritems() if key_filter(k)}
-        self.output(config_dict)
-        self.host.quit()
-
-    def psNodeConfigurationGetEb(self, failure_):
-        self.disp(u"can't get node configuration: {reason}".format(
-            reason=failure_), error=True)
-        self.host.quit(C.EXIT_BRIDGE_ERRBACK)
-
-    def start(self):
-        self.host.bridge.psNodeConfigurationGet(
-            self.args.service,
-            self.args.node,
-            self.profile,
-            callback=self.psNodeConfigurationGetCb,
-            errback=self.psNodeConfigurationGetEb)
-
-
-class NodeCreate(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'create', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'create a node'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields',
-                                 default=[], metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set"))
-        self.parser.add_argument("-F", "--full-prefix", action="store_true", help=_(u"don't prepend \"pubsub#\" prefix to field names"))
-
-    def psNodeCreateCb(self, node_id):
-        if self.host.verbosity:
-            announce = _(u'node created successfully: ')
-        else:
-            announce = u''
-        self.disp(announce + node_id)
-        self.host.quit()
-
-    def psNodeCreateEb(self, failure_):
-        self.disp(u"can't create: {reason}".format(
-            reason=failure_), error=True)
-        self.host.quit(C.EXIT_BRIDGE_ERRBACK)
-
-    def start(self):
-        if not self.args.full_prefix:
-            options = {u'pubsub#' + k: v for k,v in self.args.fields}
-        else:
-            options = dict(self.args.fields)
-        self.host.bridge.psNodeCreate(
-            self.args.service,
-            self.args.node,
-            options,
-            self.profile,
-            callback=self.psNodeCreateCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't create node: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class NodeDelete(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'delete', use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'delete a node'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument('-f', '--force', action='store_true', help=_(u'delete node without confirmation'))
-
-    def psNodeDeleteCb(self):
-        self.disp(_(u'node [{node}] deleted successfully').format(node=self.args.node))
-        self.host.quit()
-
-    def start(self):
-        if not self.args.force:
-            if not self.args.service:
-                message = _(u"Are you sure to delete pep node [{node_id}] ?").format(
-                    node_id=self.args.node)
-            else:
-                message = _(u"Are you sure to delete node [{node_id}] on service [{service}] ?").format(
-                    node_id=self.args.node, service=self.args.service)
-            self.host.confirmOrQuit(message, _(u"node deletion cancelled"))
-
-        self.host.bridge.psNodeDelete(
-            self.args.service,
-            self.args.node,
-            self.profile,
-            callback=self.psNodeDeleteCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't delete node: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class NodeSet(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'set', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'set node configuration'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields',
-                                 required=True, metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set (required)"))
-
-    def psNodeConfigurationSetCb(self):
-        self.disp(_(u'node configuration successful'), 1)
-        self.host.quit()
-
-    def psNodeConfigurationSetEb(self, failure_):
-        self.disp(u"can't set node configuration: {reason}".format(
-            reason=failure_), error=True)
-        self.host.quit(C.EXIT_BRIDGE_ERRBACK)
-
-    def getKeyName(self, k):
-        if not k.startswith(u'pubsub#'):
-            return u'pubsub#' + k
-        else:
-            return k
-
-    def start(self):
-        self.host.bridge.psNodeConfigurationSet(
-            self.args.service,
-            self.args.node,
-            {self.getKeyName(k): v for k,v in self.args.fields},
-            self.profile,
-            callback=self.psNodeConfigurationSetCb,
-            errback=self.psNodeConfigurationSetEb)
-
-
-class NodeAffiliationsGet(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'retrieve node affiliations (for node owner)'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        pass
-
-    def psNodeAffiliationsGetCb(self, affiliations):
-        self.output(affiliations)
-        self.host.quit()
-
-    def psNodeAffiliationsGetEb(self, failure_):
-        self.disp(u"can't get node affiliations: {reason}".format(
-            reason=failure_), error=True)
-        self.host.quit(C.EXIT_BRIDGE_ERRBACK)
-
-    def start(self):
-        self.host.bridge.psNodeAffiliationsGet(
-            self.args.service,
-            self.args.node,
-            self.profile,
-            callback=self.psNodeAffiliationsGetCb,
-            errback=self.psNodeAffiliationsGetEb)
-
-
-class NodeAffiliationsSet(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'set affiliations (for node owner)'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        # XXX: we use optional argument syntax for a required one because list of list of 2 elements
-        #      (uses to construct dicts) don't work with positional arguments
-        self.parser.add_argument("-a",
-                                 "--affiliation",
-                                 dest="affiliations",
-                                 metavar=('JID', 'AFFILIATION'),
-                                 required=True,
-                                 type=base.unicode_decoder,
-                                 action="append",
-                                 nargs=2,
-                                 help=_(u"entity/affiliation couple(s)"))
-
-    def psNodeAffiliationsSetCb(self):
-        self.disp(_(u"affiliations have been set"), 1)
-        self.host.quit()
-
-    def psNodeAffiliationsSetEb(self, failure_):
-        self.disp(u"can't set node affiliations: {reason}".format(
-            reason=failure_), error=True)
-        self.host.quit(C.EXIT_BRIDGE_ERRBACK)
-
-    def start(self):
-        affiliations = dict(self.args.affiliations)
-        self.host.bridge.psNodeAffiliationsSet(
-            self.args.service,
-            self.args.node,
-            affiliations,
-            self.profile,
-            callback=self.psNodeAffiliationsSetCb,
-            errback=self.psNodeAffiliationsSetEb)
-
-
-class NodeAffiliations(base.CommandBase):
-    subcommands = (NodeAffiliationsGet, NodeAffiliationsSet)
-
-    def __init__(self, host):
-        super(NodeAffiliations, self).__init__(host, 'affiliations', use_profile=False, help=_(u'set or retrieve node affiliations'))
-
-
-class NodeSubscriptionsGet(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'retrieve node subscriptions (for node owner)'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        pass
-
-    def psNodeSubscriptionsGetCb(self, subscriptions):
-        self.output(subscriptions)
-        self.host.quit()
-
-    def psNodeSubscriptionsGetEb(self, failure_):
-        self.disp(u"can't get node subscriptions: {reason}".format(
-            reason=failure_), error=True)
-        self.host.quit(C.EXIT_BRIDGE_ERRBACK)
-
-    def start(self):
-        self.host.bridge.psNodeSubscriptionsGet(
-            self.args.service,
-            self.args.node,
-            self.profile,
-            callback=self.psNodeSubscriptionsGetCb,
-            errback=self.psNodeSubscriptionsGetEb)
-
-
-class StoreSubscriptionAction(argparse.Action):
-    """Action which handle subscription parameter for owner
-
-    list is given by pairs: jid and subscription state
-    if subscription state is not specified, it default to "subscribed"
-    """
-
-    def __call__(self, parser, namespace, values, option_string):
-        dest_dict = getattr(namespace, self.dest)
-        while values:
-            jid_s = values.pop(0)
-            try:
-                subscription = values.pop(0)
-            except IndexError:
-                subscription = 'subscribed'
-            if subscription not in ALLOWED_SUBSCRIPTIONS_OWNER:
-                parser.error(_(u"subscription must be one of {}").format(u', '.join(ALLOWED_SUBSCRIPTIONS_OWNER)))
-            dest_dict[jid_s] = subscription
-
-
-class NodeSubscriptionsSet(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'set/modify subscriptions (for node owner)'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        # XXX: we use optional argument syntax for a required one because list of list of 2 elements
-        #      (uses to construct dicts) don't work with positional arguments
-        self.parser.add_argument("-S",
-                                 "--subscription",
-                                 dest="subscriptions",
-                                 default={},
-                                 nargs='+',
-                                 metavar=('JID [SUSBSCRIPTION]'),
-                                 required=True,
-                                 type=base.unicode_decoder,
-                                 action=StoreSubscriptionAction,
-                                 help=_(u"entity/subscription couple(s)"))
-
-    def psNodeSubscriptionsSetCb(self):
-        self.disp(_(u"subscriptions have been set"), 1)
-        self.host.quit()
-
-    def psNodeSubscriptionsSetEb(self, failure_):
-        self.disp(u"can't set node subscriptions: {reason}".format(
-            reason=failure_), error=True)
-        self.host.quit(C.EXIT_BRIDGE_ERRBACK)
-
-    def start(self):
-        self.host.bridge.psNodeSubscriptionsSet(
-            self.args.service,
-            self.args.node,
-            self.args.subscriptions,
-            self.profile,
-            callback=self.psNodeSubscriptionsSetCb,
-            errback=self.psNodeSubscriptionsSetEb)
-
-
-class NodeSubscriptions(base.CommandBase):
-    subcommands = (NodeSubscriptionsGet, NodeSubscriptionsSet)
-
-    def __init__(self, host):
-        super(NodeSubscriptions, self).__init__(host, 'subscriptions', use_profile=False, help=_(u'get or modify node subscriptions'))
-
-
-class NodeSchemaSet(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'set/replace a schema'))
-        self.need_loop = True
-
-    def add_parser_options(self):
-        self.parser.add_argument('schema', help=_(u"schema to set (must be XML)"))
-
-    def psSchemaSetCb(self):
-        self.disp(_(u'schema has been set'), 1)
-        self.host.quit()
-
-    def start(self):
-        self.host.bridge.psSchemaSet(
-            self.args.service,
-            self.args.node,
-            self.args.schema,
-            self.profile,
-            callback=self.psSchemaSetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't set schema: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class NodeSchemaEdit(base.CommandBase, common.BaseEdit):
-    use_items=False
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'edit', use_pubsub=True, pubsub_flags={C.NODE}, use_draft=True, use_verbose=True, help=_(u'edit a schema'))
-        common.BaseEdit.__init__(self, self.host, PUBSUB_SCHEMA_TMP_DIR)
-        self.need_loop=True
-
-    def add_parser_options(self):
-        pass
-
-    def psSchemaSetCb(self):
-        self.disp(_(u'schema has been set'), 1)
-        self.host.quit()
-
-    def publish(self, schema):
-        self.host.bridge.psSchemaSet(
-            self.args.service,
-            self.args.node,
-            schema,
-            self.profile,
-            callback=self.psSchemaSetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't set schema: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-    def psSchemaGetCb(self, schema):
-        try:
-            from lxml import etree
-        except ImportError:
-            self.disp(u"lxml module must be installed to use edit, please install it with \"pip install lxml\"", error=True)
-            self.host.quit(1)
-        content_file_obj, content_file_path = self.getTmpFile()
-        schema = schema.strip()
-        if schema:
-            parser = etree.XMLParser(remove_blank_text=True)
-            schema_elt = etree.fromstring(schema, parser)
-            content_file_obj.write(etree.tostring(schema_elt, encoding="utf-8", pretty_print=True))
-            content_file_obj.seek(0)
-        self.runEditor("pubsub_schema_editor_args", content_file_path, content_file_obj)
-
-    def start(self):
-        self.host.bridge.psSchemaGet(
-            self.args.service,
-            self.args.node,
-            self.profile,
-            callback=self.psSchemaGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't edit schema: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class NodeSchemaGet(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_XML, use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'get schema'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        pass
-
-    def psSchemaGetCb(self, schema):
-        if not schema:
-            self.disp(_(u'no schema found'), 1)
-            self.host.quit(1)
-        self.output(schema)
-        self.host.quit()
-
-    def start(self):
-        self.host.bridge.psSchemaGet(
-            self.args.service,
-            self.args.node,
-            self.profile,
-            callback=self.psSchemaGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get schema: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class NodeSchema(base.CommandBase):
-    subcommands = (NodeSchemaSet, NodeSchemaEdit, NodeSchemaGet)
-
-    def __init__(self, host):
-        super(NodeSchema, self).__init__(host, 'schema', use_profile=False, help=_(u"data schema manipulation"))
-
-
-class Node(base.CommandBase):
-    subcommands = (NodeInfo, NodeCreate, NodeDelete, NodeSet, NodeAffiliations, NodeSubscriptions, NodeSchema)
-
-    def __init__(self, host):
-        super(Node, self).__init__(host, 'node', use_profile=False, help=_('node handling'))
-
-
-class Set(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'publish a new item or update an existing one'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("item", type=base.unicode_decoder, nargs='?', default=u'', help=_(u"id, URL of the item to update, keyword, or nothing for new item"))
-
-    def psItemsSendCb(self, published_id):
-        if published_id:
-            self.disp(u"Item published at {pub_id}".format(pub_id=published_id))
-        else:
-            self.disp(u"Item published")
-        self.host.quit(C.EXIT_OK)
-
-    def start(self):
-        try:
-            from lxml import etree
-        except ImportError:
-            self.disp(u"lxml module must be installed to use edit, please install it with \"pip install lxml\"", error=True)
-            self.host.quit(1)
-        try:
-            element = etree.parse(sys.stdin).getroot()
-        except Exception as e:
-            self.parser.error(_(u"Can't parse the payload XML in input: {msg}").format(msg=e))
-        if element.tag in ('item', '{http://jabber.org/protocol/pubsub}item'):
-            if len(element) > 1:
-                self.parser.error(_(u"<item> can only have one child element (the payload)"))
-            element = element[0]
-        payload = etree.tostring(element, encoding='unicode')
-
-        self.host.bridge.psItemSend(self.args.service,
-                                    self.args.node,
-                                    payload,
-                                    self.args.item,
-                                    {},
-                                    self.profile,
-                                    callback=self.psItemsSendCb,
-                                    errback=partial(self.errback,
-                                                    msg=_(u"can't send item: {}"),
-                                                    exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class Get(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_LIST_XML, use_pubsub=True, pubsub_flags={C.NODE, C.MULTI_ITEMS}, help=_(u'get pubsub item(s)'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("-S", "--sub-id", type=base.unicode_decoder, default=u'',
-                                 help=_(u"subscription id"))
-        # TODO: a key(s) argument to select keys to display
-        # TODO: add MAM filters
-
-
-    def psItemsGetCb(self, ps_result):
-        self.output(ps_result[0])
-        self.host.quit(C.EXIT_OK)
-
-    def psItemsGetEb(self, failure_):
-        self.disp(u"can't get pubsub items: {reason}".format(
-            reason=failure_), error=True)
-        self.host.quit(C.EXIT_BRIDGE_ERRBACK)
-
-    def start(self):
-        self.host.bridge.psItemsGet(
-            self.args.service,
-            self.args.node,
-            self.args.max,
-            self.args.items,
-            self.args.sub_id,
-            {},
-            self.profile,
-            callback=self.psItemsGetCb,
-            errback=self.psItemsGetEb)
-
-class Delete(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'delete', use_pubsub=True, pubsub_flags={C.NODE, C.SINGLE_ITEM}, help=_(u'delete an item'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("-f", "--force", action='store_true', help=_(u"delete without confirmation"))
-        self.parser.add_argument("-N", "--notify", action='store_true', help=_(u"notify deletion"))
-
-    def psItemsDeleteCb(self):
-        self.disp(_(u'item {item_id} has been deleted').format(item_id=self.args.item))
-        self.host.quit(C.EXIT_OK)
-
-    def start(self):
-        if not self.args.item:
-            self.parser.error(_(u"You need to specify an item to delete"))
-        if not self.args.force:
-            message = _(u"Are you sure to delete item {item_id} ?").format(item_id=self.args.item)
-            self.host.confirmOrQuit(message, _(u"item deletion cancelled"))
-        self.host.bridge.psRetractItem(
-            self.args.service,
-            self.args.node,
-            self.args.item,
-            self.args.notify,
-            self.profile,
-            callback=self.psItemsDeleteCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't delete item: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class Edit(base.CommandBase, common.BaseEdit):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'edit', use_verbose=True, use_pubsub=True,
-                                  pubsub_flags={C.NODE, C.SINGLE_ITEM}, use_draft=True, help=_(u'edit an existing or new pubsub item'))
-        common.BaseEdit.__init__(self, self.host, PUBSUB_TMP_DIR)
-
-    def add_parser_options(self):
-        pass
-
-    def edit(self, content_file_path, content_file_obj):
-        # we launch editor
-        self.runEditor("pubsub_editor_args", content_file_path, content_file_obj)
-
-    def publish(self, content):
-        published_id = self.host.bridge.psItemSend(self.pubsub_service, self.pubsub_node, content, self.pubsub_item or '', {}, self.profile)
-        if published_id:
-            self.disp(u"Item published at {pub_id}".format(pub_id=published_id))
-        else:
-            self.disp(u"Item published")
-
-    def getItemData(self, service, node, item):
-        try:
-            from lxml import etree
-        except ImportError:
-            self.disp(u"lxml module must be installed to use edit, please install it with \"pip install lxml\"", error=True)
-            self.host.quit(1)
-        items = [item] if item is not None else []
-        item_raw =  self.host.bridge.psItemsGet(service, node, 1, items, "", {}, self.profile)[0][0]
-        parser = etree.XMLParser(remove_blank_text=True)
-        item_elt = etree.fromstring(item_raw, parser)
-        item_id = item_elt.get('id')
-        try:
-            payload = item_elt[0]
-        except IndexError:
-            self.disp(_(u'Item has not payload'), 1)
-            return  u''
-        return etree.tostring(payload, encoding="unicode", pretty_print=True), item_id
-
-    def start(self):
-        self.pubsub_service, self.pubsub_node, self.pubsub_item, content_file_path, content_file_obj = self.getItemPath()
-        self.edit(content_file_path, content_file_obj)
-
-
-class Subscribe(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'subscribe', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'subscribe to a node'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        pass
-
-    def psSubscribeCb(self, sub_id):
-        self.disp(_(u'subscription done'), 1)
-        if sub_id:
-            self.disp(_(u'subscription id: {sub_id}').format(sub_id=sub_id))
-        self.host.quit()
-
-    def start(self):
-        self.host.bridge.psSubscribe(
-            self.args.service,
-            self.args.node,
-            {},
-            self.profile,
-            callback=self.psSubscribeCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't subscribe to node: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class Unsubscribe(base.CommandBase):
-    # TODO: voir pourquoi NodeNotFound sur subscribe juste après unsubscribe
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'unsubscribe', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'unsubscribe from a node'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        pass
-
-    def psUnsubscribeCb(self):
-        self.disp(_(u'subscription removed'), 1)
-        self.host.quit()
-
-    def start(self):
-        self.host.bridge.psUnsubscribe(
-            self.args.service,
-            self.args.node,
-            self.profile,
-            callback=self.psUnsubscribeCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't unsubscribe from node: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class Subscriptions(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'subscriptions', use_output=C.OUTPUT_LIST_DICT, use_pubsub=True, help=_(u'retrieve all subscriptions on a service'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        pass
-
-    def psSubscriptionsGetCb(self, subscriptions):
-        self.output(subscriptions)
-        self.host.quit()
-
-    def start(self):
-        self.host.bridge.psSubscriptionsGet(
-            self.args.service,
-            self.args.node,
-            self.profile,
-            callback=self.psSubscriptionsGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't retrieve subscriptions: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class Affiliations(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'affiliations', use_output=C.OUTPUT_DICT, use_pubsub=True, help=_(u'retrieve all affiliations on a service'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        pass
-
-    def psAffiliationsGetCb(self, affiliations):
-        self.output(affiliations)
-        self.host.quit()
-
-    def psAffiliationsGetEb(self, failure_):
-        self.disp(u"can't get node affiliations: {reason}".format(
-            reason=failure_), error=True)
-        self.host.quit(C.EXIT_BRIDGE_ERRBACK)
-
-    def start(self):
-        self.host.bridge.psAffiliationsGet(
-            self.args.service,
-            self.args.node,
-            self.profile,
-            callback=self.psAffiliationsGetCb,
-            errback=self.psAffiliationsGetEb)
-
-
-class Search(base.CommandBase):
-    """this command to a search without using MAM, i.e. by checking every items if dound by itself, so it may be heavy in resources both for server and client"""
-    RE_FLAGS = re.MULTILINE | re.UNICODE
-    EXEC_ACTIONS = (u'exec', u'external')
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'search', use_output=C.OUTPUT_XML, use_pubsub=True, pubsub_flags={C.MULTI_ITEMS, C.NO_MAX},
-                                  use_verbose=True, help=_(u'search items corresponding to filters'))
-        self.need_loop=True
-
-    @property
-    def etree(self):
-        """load lxml.etree only if needed"""
-        if self._etree is None:
-            from lxml import etree
-            self._etree = etree
-        return self._etree
-
-    def filter_opt(self, value, type_):
-        value = base.unicode_decoder(value)
-        return (type_, value)
-
-    def filter_flag(self, value, type_):
-        value = C.bool(value)
-        return (type_, value)
-
-    def add_parser_options(self):
-        self.parser.add_argument("-D", "--max-depth", type=int, default=0, help=_(u"maximum depth of recursion (will search linked nodes if > 0, default: 0)"))
-        self.parser.add_argument("-m", "--max", type=int, default=30, help=_(u"maximum number of items to get per node ({} to get all items, default: 30)".format(C.NO_LIMIT)))
-        self.parser.add_argument("-N", "--namespace", action='append', nargs=2, default=[],
-                                 metavar="NAME NAMESPACE", help=_(u"namespace to use for xpath"))
-
-        # filters
-        filter_text = partial(self.filter_opt, type_=u'text')
-        filter_re = partial(self.filter_opt, type_=u'regex')
-        filter_xpath = partial(self.filter_opt, type_=u'xpath')
-        filter_python = partial(self.filter_opt, type_=u'python')
-        filters = self.parser.add_argument_group(_(u'filters'), _(u'only items corresponding to following filters will be kept'))
-        filters.add_argument("-t", "--text",
-                             action='append', dest='filters', type=filter_text,
-                             metavar='TEXT',
-                             help=_(u"full text filter, item must contain this string (XML included)"))
-        filters.add_argument("-r", "--regex",
-                             action='append', dest='filters', type=filter_re,
-                             metavar='EXPRESSION',
-                             help=_(u"like --text but using a regular expression"))
-        filters.add_argument("-x", "--xpath",
-                             action='append', dest='filters', type=filter_xpath,
-                             metavar='XPATH',
-                             help=_(u"filter items which has elements matching this xpath"))
-        filters.add_argument("-P", "--python",
-                             action='append', dest='filters', type=filter_python,
-                             metavar='PYTHON_CODE',
-                             help=_(u'Python expression which much return a bool (True to keep item, False to reject it). "item" is raw text item, "item_xml" is lxml\'s etree.Element'))
-
-        # filters flags
-        flag_case = partial(self.filter_flag, type_=u'ignore-case')
-        flag_invert = partial(self.filter_flag, type_=u'invert')
-        flag_dotall = partial(self.filter_flag, type_=u'dotall')
-        flag_matching = partial(self.filter_flag, type_=u'only-matching')
-        flags = self.parser.add_argument_group(_(u'filters flags'), _(u'filters modifiers (change behaviour of following filters)'))
-        flags.add_argument("-C", "--ignore-case",
-                           action='append', dest='filters', type=flag_case,
-                           const=('ignore-case', True), nargs='?',
-                           metavar='BOOLEAN',
-                           help=_(u"(don't) ignore case in following filters (default: case sensitive)"))
-        flags.add_argument("-I", "--invert",
-                           action='append', dest='filters', type=flag_invert,
-                           const=('invert', True), nargs='?',
-                           metavar='BOOLEAN',
-                           help=_(u"(don't) invert effect of following filters (default: don't invert)"))
-        flags.add_argument("-A", "--dot-all",
-                           action='append', dest='filters', type=flag_dotall,
-                           const=('dotall', True), nargs='?',
-                           metavar='BOOLEAN',
-                           help=_(u"(don't) use DOTALL option for regex (default: don't use)"))
-        flags.add_argument("-o", "--only-matching",
-                           action='append', dest='filters', type=flag_matching,
-                           const=('only-matching', True), nargs='?',
-                           metavar='BOOLEAN',
-                           help=_(u"keep only the matching part of the item"))
-
-        # action
-        self.parser.add_argument("action",
-                                 default="print",
-                                 nargs='?',
-                                 choices=('print', 'exec', 'external'),
-                                 help=_(u"action to do on found items (default: print)"))
-        self.parser.add_argument("command", nargs=argparse.REMAINDER)
-
-    def psItemsGetEb(self, failure_, service, node):
-        self.disp(u"can't get pubsub items at {service} (node: {node}): {reason}".format(
-            service=service,
-            node=node,
-            reason=failure_), error=True)
-        self.to_get -= 1
-
-    def getItems(self, depth, service, node, items):
-        search = partial(self.search, depth=depth)
-        errback = partial(self.psItemsGetEb, service=service, node=node)
-        self.host.bridge.psItemsGet(
-            service,
-            node,
-            self.args.max,
-            items,
-            "",
-            {},
-            self.profile,
-            callback=search,
-            errback=errback
-            )
-        self.to_get += 1
-
-    def _checkPubsubURL(self, match, found_nodes):
-        """check that the matched URL is an xmpp: one
-
-        @param found_nodes(list[unicode]): found_nodes
-            this list will be filled while xmpp: URIs are discovered
-        """
-        url = match.group(0)
-        if url.startswith(u'xmpp'):
-            try:
-                url_data = uri.parseXMPPUri(url)
-            except ValueError:
-                return
-            if url_data[u'type'] == u'pubsub':
-                found_node = {u'service': url_data[u'path'],
-                              u'node': url_data[u'node']}
-                if u'item' in url_data:
-                    found_node[u'item'] = url_data[u'item']
-                found_nodes.append(found_node)
-
-    def getSubNodes(self, item, depth):
-        """look for pubsub URIs in item, and getItems on the linked nodes"""
-        found_nodes = []
-        checkURI = partial(self._checkPubsubURL, found_nodes=found_nodes)
-        strings.RE_URL.sub(checkURI, item)
-        for data in found_nodes:
-            self.getItems(depth+1,
-                          data[u'service'],
-                          data[u'node'],
-                          [data[u'item']] if u'item' in data else []
-                          )
-
-    def parseXml(self, item):
-        try:
-            return self.etree.fromstring(item)
-        except self.etree.XMLSyntaxError:
-            self.disp(_(u"item doesn't looks like XML, you have probably used --only-matching somewhere before and we have no more XML"), error=True)
-            self.host.quit(C.EXIT_BAD_ARG)
-
-    def filter(self, item):
-        """apply filters given on command line
-
-        if only-matching is used, item may be modified
-        @return (tuple[bool, unicode]): a tuple with:
-            - keep: True if item passed the filters
-            - item: it is returned in case of modifications
-        """
-        ignore_case = False
-        invert = False
-        dotall = False
-        only_matching = False
-        item_xml = None
-        for type_, value in self.args.filters:
-            keep = True
-
-            ## filters
-
-            if type_ == u'text':
-                if ignore_case:
-                    if value.lower() not in item.lower():
-                        keep = False
-                else:
-                    if value not in item:
-                        keep = False
-                if keep and only_matching:
-                    # doesn't really make sens to keep a fixed string
-                    # so we raise an error
-                    self.host.disp(_(u"--only-matching used with fixed --text string, are you sure?"), error=True)
-                    self.host.quit(C.EXIT_BAD_ARG)
-            elif type_ == u'regex':
-                flags = self.RE_FLAGS
-                if ignore_case:
-                    flags |= re.IGNORECASE
-                if dotall:
-                    flags |= re.DOTALL
-                match = re.search(value, item, flags)
-                keep = match != None
-                if keep and only_matching:
-                    item = match.group()
-                    item_xml = None
-            elif type_ == u'xpath':
-                if item_xml is None:
-                    item_xml = self.parseXml(item)
-                try:
-                    elts = item_xml.xpath(value, namespaces=self.args.namespace)
-                except self.etree.XPathEvalError as e:
-                    self.disp(_(u"can't use xpath: {reason}").format(reason=e), error=True)
-                    self.host.quit(C.EXIT_BAD_ARG)
-                keep = bool(elts)
-                if keep and only_matching:
-                    item_xml = elts[0]
-                    try:
-                        item = self.etree.tostring(item_xml, encoding='unicode')
-                    except TypeError:
-                        # we have a string only, not an element
-                        item = unicode(item_xml)
-                        item_xml = None
-            elif type_ == u'python':
-                if item_xml is None:
-                    item_xml = self.parseXml(item)
-                cmd_ns = {u'item': item,
-                          u'item_xml': item_xml
-                         }
-                try:
-                    keep = eval(value, cmd_ns)
-                except SyntaxError as e:
-                    self.disp(unicode(e), error=True)
-                    self.host.quit(C.EXIT_BAD_ARG)
-
-            ## flags
-
-            elif type_ == u'ignore-case':
-                ignore_case = value
-            elif type_ == u'invert':
-                invert = value
-                # we need to continue, else loop would end here
-                continue
-            elif type_ == u'dotall':
-                dotall = value
-            elif type_ == u'only-matching':
-                only_matching = value
-            else:
-                raise exceptions.InternalError(_(u"unknown filter type {type}").format(type=type_))
-
-            if invert:
-                keep = not keep
-            if not keep:
-                return False, item
-
-        return True, item
-
-    def doItemAction(self, item, metadata):
-        """called when item has been kepts and the action need to be done
-
-        @param item(unicode): accepted item
-        """
-        action = self.args.action
-        if action == u'print' or self.host.verbosity > 0:
-            try:
-                self.output(item)
-            except self.etree.XMLSyntaxError:
-                # item is not valid XML, but a string
-                # can happen when --only-matching is used
-                self.disp(item)
-        if action in self.EXEC_ACTIONS:
-            item_elt = self.parseXml(item)
-            if action == u'exec':
-                use = {'service': metadata[u'service'],
-                       'node': metadata[u'node'],
-                       'item': item_elt.get('id'),
-                       'profile': self.profile
-                       }
-                # we need to send a copy of self.args.command
-                # else it would be modified
-                parser_args, use_args = arg_tools.get_use_args(self.host,
-                    self.args.command,
-                    use,
-                    verbose=self.host.verbosity > 1
-                    )
-                cmd_args = sys.argv[0:1] + parser_args + use_args
-            else:
-                cmd_args = self.args.command
-
-
-            self.disp(u'COMMAND: {command}'.format(
-                command = u' '.join([arg_tools.escape(a) for a in cmd_args])), 2)
-            if action == u'exec':
-                ret = subprocess.call(cmd_args)
-            else:
-                p = subprocess.Popen(cmd_args, stdin=subprocess.PIPE)
-                p.communicate(item.encode('utf-8'))
-                ret = p.wait()
-            if ret != 0:
-                self.disp(A.color(C.A_FAILURE, _(u"executed command failed with exit code {code}").format(code=ret)))
-
-    def search(self, items_data, depth):
-        """callback of getItems
-
-        this method filters items, get sub nodes if needed,
-        do the requested action, and exit the command when everything is done
-        @param items_data(tuple): result of getItems
-        @param depth(int): current depth level
-            0 for first node, 1 for first children, and so on
-        """
-        items, metadata = items_data
-        for item in items:
-            if depth < self.args.max_depth:
-                self.getSubNodes(item, depth)
-            keep, item = self.filter(item)
-            if not keep:
-                continue
-            self.doItemAction(item, metadata)
-
-        # we check if we got all getItems results
-        self.to_get -= 1
-        if self.to_get == 0:
-            # yes, we can quit
-            self.host.quit()
-        assert self.to_get > 0
-
-    def start(self):
-        if self.args.command:
-            if self.args.action not in self.EXEC_ACTIONS:
-                self.parser.error(_(u"Command can only be used with {actions} actions").format(
-                    actions=u', '.join(self.EXEC_ACTIONS)))
-        else:
-            if self.args.action in self.EXEC_ACTIONS:
-                self.parser.error(_(u"you need to specify a command to execute"))
-        if not self.args.node:
-            # TODO: handle get service affiliations when node is not set
-            self.parser.error(_(u"empty node is not handled yet"))
-        # to_get is increased on each get and decreased on each answer
-        # when it reach 0 again, the command is finished
-        self.to_get = 0
-        self._etree = None
-        if self.args.filters is None:
-            self.args.filters = []
-        self.args.namespace = dict(self.args.namespace + [('pubsub', "http://jabber.org/protocol/pubsub")])
-        self.getItems(0, self.args.service, self.args.node, self.args.items)
-
-
-class Uri(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'uri', use_profile=False, use_pubsub=True, pubsub_flags={C.NODE, C.SINGLE_ITEM}, help=_(u'build URI'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument("-p", "--profile", type=base.unicode_decoder, default=C.PROF_KEY_DEFAULT, help=_(u"profile (used when no server is specified)"))
-
-    def display_uri(self, jid_):
-        uri_args = {}
-        if not self.args.service:
-            self.args.service = jid.JID(jid_).bare
-
-        for key in ('node', 'service', 'item'):
-            value = getattr(self.args, key)
-            if key == 'service':
-                key = 'path'
-            if value:
-                uri_args[key] = value
-        self.disp(uri.buildXMPPUri(u'pubsub', **uri_args))
-        self.host.quit()
-
-    def start(self):
-        if not self.args.service:
-            self.host.bridge.asyncGetParamA(
-                u'JabberID',
-                u'Connection',
-                profile_key=self.args.profile,
-                callback=self.display_uri,
-                errback=partial(self.errback,
-                                msg=_(u"can't retrieve jid: {}"),
-                                exit_code=C.EXIT_BRIDGE_ERRBACK))
-        else:
-            self.display_uri(None)
-
-
-class HookCreate(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'create', use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'create a Pubsub hook'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument('-t', '--type', default=u'python', choices=('python', 'python_file', 'python_code'), help=_(u"hook type"))
-        self.parser.add_argument('-P', '--persistent', action='store_true', help=_(u"make hook persistent across restarts"))
-        self.parser.add_argument("hook_arg", type=base.unicode_decoder, help=_(u"argument of the hook (depend of the type)"))
-
-    @staticmethod
-    def checkArgs(self):
-        if self.args.type == u'python_file':
-            self.args.hook_arg = os.path.abspath(self.args.hook_arg)
-            if not os.path.isfile(self.args.hook_arg):
-                self.parser.error(_(u"{path} is not a file").format(path=self.args.hook_arg))
-
-    def start(self):
-        self.checkArgs(self)
-        self.host.bridge.psHookAdd(
-            self.args.service,
-            self.args.node,
-            self.args.type,
-            self.args.hook_arg,
-            self.args.persistent,
-            self.profile,
-            callback=self.host.quit,
-            errback=partial(self.errback,
-                            msg=_(u"can't create hook: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class HookDelete(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'delete', use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'delete a Pubsub hook'))
-        self.need_loop=True
-
-    def add_parser_options(self):
-        self.parser.add_argument('-t', '--type', default=u'', choices=('', 'python', 'python_file', 'python_code'), help=_(u"hook type to remove, empty to remove all (default: remove all)"))
-        self.parser.add_argument('-a', '--arg', dest='hook_arg', type=base.unicode_decoder, default=u'', help=_(u"argument of the hook to remove, empty to remove all (default: remove all)"))
-
-    def psHookRemoveCb(self, nb_deleted):
-        self.disp(_(u'{nb_deleted} hook(s) have been deleted').format(
-            nb_deleted = nb_deleted))
-        self.host.quit()
-
-    def start(self):
-        HookCreate.checkArgs(self)
-        self.host.bridge.psHookRemove(
-            self.args.service,
-            self.args.node,
-            self.args.type,
-            self.args.hook_arg,
-            self.profile,
-            callback=self.psHookRemoveCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't delete hook: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class HookList(base.CommandBase):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'list', use_output=C.OUTPUT_LIST_DICT, help=_(u'list hooks of a profile'))
-        self.need_loop = True
-
-    def add_parser_options(self):
-        pass
-
-    def psHookListCb(self, data):
-        if not data:
-            self.disp(_(u'No hook found.'))
-        self.output(data)
-        self.host.quit()
-
-    def start(self):
-        self.host.bridge.psHookList(
-            self.profile,
-            callback=self.psHookListCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't list hooks: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
-
-class Hook(base.CommandBase):
-    subcommands = (HookCreate, HookDelete, HookList)
-
-    def __init__(self, host):
-        super(Hook, self).__init__(host, 'hook', use_profile=False, use_verbose=True, help=_('trigger action on Pubsub notifications'))
-
-
-class Pubsub(base.CommandBase):
-    subcommands = (Set, Get, Delete, Edit, Subscribe, Unsubscribe, Subscriptions, Node, Affiliations, Search, Hook, Uri)
-
-    def __init__(self, host):
-        super(Pubsub, self).__init__(host, 'pubsub', use_profile=False, help=_('PubSub nodes/items management'))
--- a/frontends/src/jp/cmd_roster.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,214 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SAT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-# Copyright (C) 2003-2016 Adrien Cossa (souliane@mailoo.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-import base
-from sat_frontends.jp.constants import Const as C
-from sat.core.i18n import _
-
-from twisted.words.protocols.jabber import jid
-from collections import OrderedDict
-
-__commands__ = ["Roster"]
-
-
-
-class Purge(base.CommandBase):
-
-    def __init__(self, host):
-        super(Purge, self).__init__(host, 'purge', help=_('Purge the roster from its contacts with no subscription'))
-        self.need_loop = True
-
-    def add_parser_options(self):
-        self.parser.add_argument("--no_from", action="store_true", help=_("Also purge contacts with no 'from' subscription"))
-        self.parser.add_argument("--no_to", action="store_true", help=_("Also purge contacts with no 'to' subscription"))
-
-    def start(self):
-        self.host.bridge.getContacts(profile_key=self.host.profile, callback=self.gotContacts, errback=self.error)
-
-    def error(self, failure):
-        print (_("Error while retrieving the contacts [%s]") % failure)
-        self.host.quit(1)
-
-    def ask_confirmation(self, no_sub, no_from, no_to):
-        """Ask the confirmation before removing contacts.
-
-        @param no_sub (list[unicode]): list of contacts with no subscription
-        @param no_from (list[unicode]): list of contacts with no 'from' subscription
-        @param no_to (list[unicode]): list of contacts with no 'to' subscription
-        @return bool
-        """
-        if no_sub:
-            print "There's no subscription between profile [%s] and the following contacts:" % self.host.profile
-            print "    " + "\n    ".join(no_sub)
-        if no_from:
-            print "There's no 'from' subscription between profile [%s] and the following contacts:" % self.host.profile
-            print "    " + "\n    ".join(no_from)
-        if no_to:
-            print "There's no 'to' subscription between profile [%s] and the following contacts:" % self.host.profile
-            print "    " + "\n    ".join(no_to)
-        message = "REMOVE them from profile [%s]'s roster" % self.host.profile
-        while True:
-            res = raw_input("%s (y/N)? " % message)
-            if not res or res.lower() == 'n':
-                return False
-            if res.lower() == 'y':
-                return True
-
-    def gotContacts(self, contacts):
-        """Process the list of contacts.
-
-        @param contacts(list[tuple]): list of contacts with their attributes and groups
-        """
-        no_sub, no_from, no_to = [], [], []
-        for contact, attrs, groups in contacts:
-            from_, to = C.bool(attrs["from"]), C.bool(attrs["to"])
-            if not from_:
-                if not to:
-                    no_sub.append(contact)
-                elif self.args.no_from:
-                    no_from.append(contact)
-            elif not to and self.args.no_to:
-                no_to.append(contact)
-        if not no_sub and not no_from and not no_to:
-            print "Nothing to do - there's a from and/or to subscription(s) between profile [%s] and each of its contacts" % self.host.profile
-        elif self.ask_confirmation(no_sub, no_from, no_to):
-            for contact in no_sub + no_from + no_to:
-                self.host.bridge.delContact(contact, profile_key=self.host.profile, callback=lambda dummy: None, errback=lambda failure: None)
-        self.host.quit()
-
-
-class Stats(base.CommandBase):
-
-    def __init__(self, host):
-        super(Stats, self).__init__(host, 'stats', help=_('Show statistics about a roster'))
-        self.need_loop = True
-
-    def add_parser_options(self):
-        pass
-
-    def start(self):
-        self.host.bridge.getContacts(profile_key=self.host.profile, callback=self.gotContacts, errback=self.error)
-
-    def error(self, failure):
-        print (_("Error while retrieving the contacts [%s]") % failure)
-        self.host.quit(1)
-
-    def gotContacts(self, contacts):
-        """Process the list of contacts.
-
-        @param contacts(list[tuple]): list of contacts with their attributes and groups
-        """
-        hosts = {}
-        unique_groups = set()
-        no_sub, no_from, no_to, no_group, total_group_subscription = 0, 0, 0, 0, 0
-        for contact, attrs, groups in contacts:
-            from_, to = C.bool(attrs["from"]), C.bool(attrs["to"])
-            if not from_:
-                if not to:
-                    no_sub += 1
-                else:
-                    no_from += 1
-            elif not to:
-                no_to += 1
-            host = jid.JID(contact).host
-            hosts.setdefault(host, 0)
-            hosts[host] += 1
-            if groups:
-                unique_groups.update(groups)
-                total_group_subscription += len(groups)
-            if not groups:
-                no_group += 1
-        hosts = OrderedDict(sorted(hosts.items(), key=lambda item:-item[1]))
-
-        print
-        print "Total number of contacts: %d" % len(contacts)
-        print "Number of different hosts: %d" % len(hosts)
-        print
-        for host, count in hosts.iteritems():
-            print "Contacts on {host}: {count} ({rate:.1f}%)".format(host=host, count=count, rate=100 * float(count) / len(contacts))
-        print
-        print "Contacts with no 'from' subscription: %d" % no_from
-        print "Contacts with no 'to' subscription: %d" % no_to
-        print "Contacts with no subscription at all: %d" % no_sub
-        print
-        print "Total number of groups: %d" % len(unique_groups)
-        try:
-            contacts_per_group = float(total_group_subscription) / len(unique_groups)
-        except ZeroDivisionError:
-            contacts_per_group = 0
-        print "Average contacts per group: {:.1f}".format(contacts_per_group)
-        try:
-            groups_per_contact = float(total_group_subscription) / len(contacts)
-        except ZeroDivisionError:
-            groups_per_contact = 0
-        print "Average groups' subscriptions per contact: {:.1f}".format(groups_per_contact)
-        print "Contacts not assigned to any group: %d" % no_group
-        self.host.quit()
-
-
-class Get(base.CommandBase):
-
-    def __init__(self, host):
-        super(Get, self).__init__(host, 'get', help=_('Retrieve the roster contacts'))
-        self.need_loop = True
-
-    def add_parser_options(self):
-        self.parser.add_argument("--subscriptions", action="store_true", help=_("Show the contacts' subscriptions"))
-        self.parser.add_argument("--groups", action="store_true", help=_("Show the contacts' groups"))
-        self.parser.add_argument("--name", action="store_true", help=_("Show the contacts' names"))
-
-    def start(self):
-        self.host.bridge.getContacts(profile_key=self.host.profile, callback=self.gotContacts, errback=self.error)
-
-    def error(self, failure):
-        print (_("Error while retrieving the contacts [%s]") % failure)
-        self.host.quit(1)
-
-    def gotContacts(self, contacts):
-        """Process the list of contacts.
-
-        @param contacts(list[tuple]): list of contacts with their attributes and groups
-        """
-        field_count = 1  # only display the contact by default
-        if self.args.subscriptions:
-            field_count += 3  # ask, from, to
-        if self.args.name:
-            field_count += 1
-        if self.args.groups:
-            field_count += 1
-        for contact, attrs, groups in contacts:
-            args = [contact]
-            if self.args.subscriptions:
-                args.append("ask" if C.bool(attrs["ask"]) else "")
-                args.append("from" if C.bool(attrs["from"]) else "")
-                args.append("to" if C.bool(attrs["to"]) else "")
-            if self.args.name:
-                args.append(unicode(attrs.get("name", "")))
-            if self.args.groups:
-                args.append(u"\t".join(groups) if groups else "")
-            print u";".join(["{}"] * field_count).format(*args).encode("utf-8")
-        self.host.quit()
-
-
-class Roster(base.CommandBase):
-    subcommands = (Get, Stats, Purge)
-
-    def __init__(self, host):
-        super(Roster, self).__init__(host, 'roster', use_profile=True, help=_("Manage an entity's roster"))
--- a/frontends/src/jp/cmd_shell.py	Mon Apr 02 08:56:24 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,264 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-# jp: a SàT command line tool
-# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
-
-# This program 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.
-
-# 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-
-import base
-import cmd
-import sys
-from sat.core.i18n import _
-from sat.core import exceptions
-from sat_frontends.jp.constants import Const as C
-from sat_frontends.jp import arg_tools
-from sat.tools.common.ansi import ANSI as A
-import shlex
-import subprocess
-
-__commands__ = ["Shell"]
-INTRO = _(u"""Welcome to {app_name} shell, the Salut à Toi shell !
-
-This enrironment helps you using several {app_name} commands with similar parameters.
-
-To quit, just enter "quit" or press C-d.
-Enter "help" or "?" to know what to do
-""").format(app_name = C.APP_NAME)
-
-
-class Shell(base.CommandBase, cmd.Cmd):
-
-    def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'shell', help=_(u'launch jp in shell (REPL) mode'))
-        cmd.Cmd.__init__(self)
-
-    def parse_args(self, args):
-        """parse line arguments"""
-        return shlex.split(args, posix=True)
-
-    def update_path(self):
-        self._cur_parser = self.host.parser
-        self.help = u''
-        for idx, path_elt in enumerate(self.path):
-            try:
-                self._cur_parser = arg_tools.get_cmd_choices(path_elt, self._cur_parser)
-            except exceptions.NotFound:
-                self.disp(_(u'bad command path'), error=True)
-                self.path=self.path[:idx]
-                break
-            else:
-                self.help = self._cur_parser
-
-        self.prompt = A.color(C.A_PROMPT_PATH, u'/'.join(self.path)) + A.color(C.A_PROMPT_SUF, u'> ')
-        try:
-            self.actions = arg_tools.get_cmd_choices(parser=self._cur_parser).keys()
-        except exceptions.NotFound:
-            self.actions = []
-
-    def add_parser_options(self):
-        pass
-
-    def format_args(self, args):
-        """format argument to be printed with quotes if needed"""
-        for arg in args:
-            if " " in arg:
-                yield arg_tools.escape(arg)
-            else:
-                yield arg
-
-    def run_cmd(self, args, external=False):
-        """run command and retur exit code
-
-        @param args[list[string]]: arguments of the command
-            must not include program name
-        @param external(bool): True if it's an external command (i.e. not jp)
-        @return (int): exit code (0 success, any other int failure)
-        """
-        # FIXME: we have to use subprocess
-        # and relaunch whole python for now
-        # because if host.quit() is called in D-Bus callback
-        # GLib quit the whole app without possibility to stop it
-        # didn't found a nice way to work around it so far
-        # Situation should be better when we'll move away from python-dbus
-        if self.verbose:
-            self.disp(_(u"COMMAND {external}=> {args}").format(
-                external = _(u'(external) ') if external else u'',
-                args=u' '.join(self.format_args(args))))
-        if not external:
-            args = sys.argv[0:1] + args
-        ret_code= subprocess.call(args)
-        # XXX: below is a way to launch the command without creating a new process
-        #      may be used when a solution to the aforementioned issue is there
-        # try:
-        #     self.host.run(args)
-        # except SystemExit as e:
-        #     ret_code = e.code
-        # except Exception as e:
-        #     self.disp(A.color(C.A_FAILURE, u'command failed with an exception: {msg}'.format(msg=e)), error=True)
-        #     ret_code = 1
-        # else:
-        #     ret_code = 0
-
-        if ret_code != 0:
-            self.disp(A.color(C.A_FAILURE, u'command failed with an error code of {err_no}'.format(err_no=ret_code)), error=True)
-        return ret_code
-
-    def default(self, args):
-        """called when no shell command is recognized
-
-        will launch the command with args on the line
-        (i.e. will launch do [args])
-        """
-        if args=='EOF':
-            self.do_quit('')
-        self.do_do(args)
-
-    def do_help(self, args):
-        """show help message"""
-        if not args:
-            self.disp(A.color(C.A_HEADER, _(u'Shell commands:')), no_lf=True)
-        super(Shell, self).do_help(args)
-        if not args:
-            self.disp(A.color(C.A_HEADER, _(u'Action commands:')))
-            help_list = self._cur_parser.format_help().split('\n\n')
-            print('\n\n'.join(help_list[1 if self.path else 2:]))
-
-    def do_debug(self, args):
-        """launch internal debugger"""
-        try:
-            import ipdb as pdb
-        except ImportError:
-            import pdb
-        pdb.set_trace()
-
-    def do_verbose(self, args):
-        """show verbose mode, or (de)activate it"""
-        args = self.parse_args(args)
-        if args:
-            self.verbose = C.bool(args[0])
-        self.disp(_(u'verbose mode is {status}').format(
-            status = _(u'ENABLED') if self.verbose else _(u'DISABLED')))
-
-    def do_cmd(self, args):
-        """change command path"""
-        if args == '..':
-            self.path = self.path[:-1]
-        else:
-            if not args or args[0] == '/':
-                self.path = []
-            args = '/'.join(args.split())
-            for path_elt in args.split('/'):
-                path_elt = path_elt.strip()
-                if not path_elt:
-                    continue
-                self.path.append(path_elt)
-        self.update_path()
-
-    def do_version(self, args):
-        """show current SàT/jp version"""
-        try:
-            self.host.run(['--version'])
-        except SystemExit:
-            pass
-
-    def do_shell(self, args):
-        """launch an external command (you can use ![command] too)"""
-        args = self.parse_args(args)
-        self.run_cmd(args, external=True)
-
-    def do_do(self, args):
-        """lauch a command"""
-        args = self.parse_args(args)
-        if (self._not_default_profile and
-            not '-p' in args and
-            not '--profile' in args and
-            not 'profile' in self.use):
-            # profile is not specified and we are not using the default profile
-            # so we need to add it in arguments to use current user profile
-            if self.verbose:
-                self.disp(_(u'arg profile={profile} (logged profile)').format(profile=self.profile))
-            use = self.use.copy()
-            use['profile'] = self.profile
-        else:
-            use = self.use
-
-
-        # args may be modified by use_args
-        # to remove subparsers from it
-        parser_args, use_args = arg_tools.get_use_args(self.host,
-                                          args,
-                                          use,
-                                          verbose=self.verbose,
-                                          parser=self._cur_parser
-                                          )
-        cmd_args = self.path + parser_args + use_args
-        self.run_cmd(cmd_args)
-
-    def do_use(self, args):
-        """fix an argument"""
-        args = self.parse_args(args)
-        if not args:
-            if not self.use:
-                self.disp(_(u'no argument in USE'))
-            else:
-                self.disp(_(u'arguments in USE:'))
-                for arg, value in self.use.iteritems():
-                    self.disp(_(A.color(C.A_SUBHEADER, arg, A.RESET, u' = ', arg_tools.escape(value))))
-        elif len(args) != 2:
-            self.disp(u'bad syntax, please use:\nuse [arg] [value]', error=True)
-        else:
-            self.use[args[0]] = u' '.join(args[1:])
-            if self.verbose:
-                self.disp('set {name} = {value}'.format(
-                    name = args[0], value=arg_tools.escape(args[1])))
-
-    def do_use_clear(self, args):
-        """unset one or many argument(s) in USE, or all of them if no arg is specified"""
-        args = self.parse_args(args)
-        if not args:
-            self.use.clear()
-        else:
-            for arg in args:
-                try:
-                    del self.use[arg]
-                except KeyError:
-                    self.disp(A.color(C.A_FAILURE, _(u'argument {name} not found').format(name=arg)), error=True)
-                else:
-                    if self.verbose:
-                        self.disp(_(u'argument {name} removed').format(name=arg))
-
-    def do_whoami(self, args):
-        u"""print profile currently used"""
-        self.disp(self.profile)
-
-    def do_quit(self, args):