diff --git a/frontends/src/jp/base.py b/frontends/src/jp/base.py index 5a5d8323..1cedb040 100644 --- a/frontends/src/jp/base.py +++ b/frontends/src/jp/base.py @@ -34,9 +34,11 @@ 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 @@ -316,14 +318,46 @@ class Jp(object): 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)")) - for parent_name in ('pubsub', 'pubsub_node_req'): - parent = self.parents[parent_name] = argparse.ArgumentParser(add_help=False) - parent.add_argument("-s", "--service", type=unicode_decoder, default=u'', - help=_(u"JID of the PubSub service (default: PEP service)")) - if parent_name == 'pubsub_node_req': - parent.add_argument("node", type=unicode_decoder, help=_(u"node to request")) - else: - parent.add_argument("-n", "--node", type=unicode_decoder, default=u'', help=_(u"node to request")) + 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): + """generate pubsub options according to flags + + @param flags(iterable[unicode]): see [CommandBase.__init__] + @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: + service_help += _(u" (default: PEP service)") + pubsub_group.add_argument("-s", "--service", type=unicode_decoder, default=u'', + help=service_help) + + node_help = _(u"node to request") + if not flags.node: + node_help += _(u" (DEFAULT: standard node)") + pubsub_group.add_argument("-n", "--node", type=unicode_decoder, default=u'', help=node_help) + + if flags.single_item: + pubsub_group.add_argument("-i", "--item", type=unicode_decoder, help=_(u"item to retrieve")) + 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 flags: + raise exceptions.InternalError('unknowns flags: {flags}'.format(flags=u', '.join(flags))) + + 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})) @@ -402,10 +436,89 @@ class Jp(object): 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: + try: + item, item_magic = self.args.item, self.args.item_magic + except AttributeError: + if not self.args.items: + self.args.items = [uri_item] + else: + if not item and not item_magic: + 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")) + + # 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.func() + self.args._cmd.run() if self._need_loop or self._auto_loop: self._start_loop() except KeyboardInterrupt: @@ -582,6 +695,14 @@ class CommandBase(object): - 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 @@ -632,9 +753,11 @@ class CommandBase(object): else: assert extra_outputs is None - if 'use_pubsub' in kwargs and 'use_pubsub_node_req' in kwargs: - raise exceptions.InternalError(u"use_pubsub and use_pubsub_node_req can't be used at the same time." - u"Use the later one when node is required (else an empty string is used as default)") + self._use_pubsub = kwargs.pop('use_pubsub', False) + if self._use_pubsub: + flags = kwargs.pop('pubsub_flags', None) + parents.add(self.host.make_pubsub_group(flags)) + self._pubsub_flags = flags # other common options use_opts = {k:v for k,v in kwargs.iteritems() if k.startswith('use_')} @@ -650,7 +773,7 @@ class CommandBase(object): if hasattr(self, "subcommands"): self.subparsers = self.parser.add_subparsers() else: - self.parser.set_defaults(func=self.run) + self.parser.set_defaults(_cmd=self) self.add_parser_options() @property diff --git a/frontends/src/jp/cmd_blog.py b/frontends/src/jp/cmd_blog.py index d5bb7507..46d1a9bd 100644 --- a/frontends/src/jp/cmd_blog.py +++ b/frontends/src/jp/cmd_blog.py @@ -76,38 +76,28 @@ MB_KEYS = (u"id", OUTPUT_OPT_NO_HEADER = u'no-header' -class BlogCommon(object): +def guessSyntaxFromPath(host, sat_conf, path): + """Return syntax guessed according to filename extension - def __init__(self, host): - self.host = host + @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 - def addServiceNodeArgs(self): - self.parser.add_argument("-n", "--node", type=base.unicode_decoder, default=u'', help=_(u"PubSub node to request (default: microblog namespace)")) - self.parser.add_argument("-s", "--service", type=base.unicode_decoder, default=u'', help=_(u"JID of the PubSub service (default: request profile own blog)")) - - def guessSyntaxFromPath(self, 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 self.host.bridge.getParamA("Syntax", "Composition", "value", self.profile) + # 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.addServiceNodeArgs() 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")) @@ -134,24 +124,22 @@ class BlogPublishCommon(object): mb_data['title'] = self.args.title -class Set(base.CommandBase, BlogCommon, BlogPublishCommon): +class Set(base.CommandBase, BlogPublishCommon): def __init__(self, host): - base.CommandBase.__init__(self, host, 'set', help=_(u'publish a new blog item or update an existing one')) - BlogCommon.__init__(self, 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) - self.parser.add_argument("item", type=base.unicode_decoder, nargs='?', default=None, help=_(u"id of the item to publish")) def mbSendCb(self): self.disp(u"Item published") self.host.quit(C.EXIT_OK) def start(self): - common.checkURI(self.args) self.pubsub_item = self.args.item mb_data = {} self.setMbDataFromArgs(mb_data) @@ -169,20 +157,17 @@ class Set(base.CommandBase, BlogCommon, BlogPublishCommon): exit_code=C.EXIT_BRIDGE_ERRBACK)) -class Get(base.CommandBase, BlogCommon): +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_output=C.OUTPUT_COMPLEX, extra_outputs=extra_outputs, help=_(u'get blog item(s)')) - BlogCommon.__init__(self, self.host) + 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): - self.addServiceNodeArgs() - self.parser.add_argument("-i", "--item", type=base.unicode_decoder, action='append', default=[], dest='items', - help=_(u"item(s) id(s) to get (default: request all items)")) self.parser.add_argument("-m", "--max", type=int, default=10, help=_(u"maximum number of items to get ({} to get all items)".format(C.NO_LIMIT))) # TODO: a key(s) argument to select keys to display self.parser.add_argument("-k", "--key", type=base.unicode_decoder, action='append', dest='keys', @@ -339,7 +324,6 @@ class Get(base.CommandBase, BlogCommon): self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): - common.checkURI(self.args) self.host.bridge.mbGet( self.args.service, self.args.node, @@ -351,19 +335,23 @@ class Get(base.CommandBase, BlogCommon): errback=self.mbGetEb) -class Edit(base.CommandBase, BlogCommon, BlogPublishCommon, common.BaseEdit): +class Edit(base.CommandBase, BlogPublishCommon, common.BaseEdit): def __init__(self, host): - base.CommandBase.__init__(self, host, 'edit', use_verbose=True, help=_(u'edit an existing or new blog post')) - BlogCommon.__init__(self, 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")) - self.parser.add_argument("item", type=base.unicode_decoder, nargs='?', default=u'new', help=_(u"URL of the item to edit, or keyword")) - common.BaseEdit.add_parser_options(self) def buildMetadataFile(self, content_file_path, mb_data=None): """Build a metadata file using json @@ -436,8 +424,6 @@ class Edit(base.CommandBase, BlogCommon, BlogPublishCommon, common.BaseEdit): def getTmpSuff(self): # we get current syntax to determine file extension - if self.current_syntax is None: - self.current_syntax = self.host.bridge.getParamA("Syntax", "Composition", "value", self.profile) return SYNTAX_EXT.get(self.current_syntax, SYNTAX_EXT['']) def getItemData(self, service, node, item): @@ -466,26 +452,26 @@ class Edit(base.CommandBase, BlogCommon, BlogPublishCommon, common.BaseEdit): 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: + 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) + 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.args.item) + 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, BlogCommon): +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')) - BlogCommon.__init__(self, self.host) 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")) @@ -626,7 +612,7 @@ class Preview(base.CommandBase, BlogCommon): class Import(base.CommandAnswering): def __init__(self, host): - super(Import, self).__init__(host, 'import', use_progress=True, help=_(u'import an external blog')) + 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): @@ -637,10 +623,6 @@ class Import(base.CommandAnswering): 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('--service', type=base.unicode_decoder, default=u'', metavar=u'PUBSUB_SERVICE', - help=_(u"PubSub service where the items must be uploaded (default: server)")) - self.parser.add_argument('-n', '--node', type=base.unicode_decoder, default=u'', metavar=u'PUBSUB_NODE', - help=_(u"PubSub node where the items must be uploaded (default: tickets' defaults)")) self.parser.add_argument("location", type=base.unicode_decoder, nargs='?', help=_(u"importer data location (see importer description), nothing to show importer description")) diff --git a/frontends/src/jp/cmd_event.py b/frontends/src/jp/cmd_event.py index 28640ccf..1e52b059 100644 --- a/frontends/src/jp/cmd_event.py +++ b/frontends/src/jp/cmd_event.py @@ -42,13 +42,13 @@ class Get(base.CommandBase): host, 'get', use_output=C.OUTPUT_DICT, - use_pubsub_node_req=True, + 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): - self.parser.add_argument("-i", "--item", type=base.unicode_decoder, default=u'', help=_(u"ID of the PubSub Item")) + pass def eventInviteeGetCb(self, result): event_date, event_data = result @@ -57,7 +57,6 @@ class Get(base.CommandBase): self.host.quit() def start(self): - common.checkURI(self.args) self.host.bridge.eventGet( self.args.service, self.args.node, @@ -100,7 +99,7 @@ class EventBase(object): class Create(EventBase, base.CommandBase): def __init__(self, host): - super(Create, self).__init__(host, 'create', use_pubsub_node_req=True, help=_('create or replace event')) + 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 @@ -126,12 +125,11 @@ class Create(EventBase, base.CommandBase): class Modify(EventBase, base.CommandBase): def __init__(self, host): - super(Modify, self).__init__(host, 'modify', use_pubsub_node_req=True, help=_('modify an existing event')) + 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): - common.checkURI(self.args) fields = self.parseFields() date = 0 if not self.args.date else self.parseDate() self.host.bridge.eventModify( @@ -154,7 +152,7 @@ class InviteeGet(base.CommandBase): host, 'get', use_output=C.OUTPUT_DICT, - use_pubsub_node_req=True, + use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'get event attendance')) self.need_loop=True @@ -167,7 +165,6 @@ class InviteeGet(base.CommandBase): self.host.quit() def start(self): - common.checkURI(self.args) self.host.bridge.eventInviteeGet( self.args.service, self.args.node, @@ -180,7 +177,7 @@ class InviteeGet(base.CommandBase): class InviteeSet(base.CommandBase): def __init__(self, host): - super(InviteeSet, self).__init__(host, 'set', use_output=C.OUTPUT_DICT, use_pubsub_node_req=True, help=_('set event attendance')) + 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): @@ -188,7 +185,6 @@ class InviteeSet(base.CommandBase): metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set")) def start(self): - common.checkURI(self.args) fields = dict(self.args.fields) if self.args.fields else {} self.host.bridge.eventInviteeSet( self.args.service, @@ -210,7 +206,7 @@ class InviteesList(base.CommandBase): 'list', use_output=C.OUTPUT_DICT_DICT, extra_outputs=extra_outputs, - use_pubsub_node_req=True, + use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'get event attendance')) self.need_loop=True @@ -355,7 +351,6 @@ class InviteesList(base.CommandBase): 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")) - common.checkURI(self.args) if self.args.missing: self.host.bridge.psNodeAffiliationsGet( self.args.service, @@ -372,21 +367,19 @@ class InviteesList(base.CommandBase): class InviteeInvite(base.CommandBase): def __init__(self, host): - base.CommandBase.__init__(self, host, 'invite', use_pubsub_node_req=True, help=_(u'invite someone to the event through email')) + 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("-i", "--item", type=base.unicode_decoder, default=u'', help=_(u"ID of the PubSub Item")) 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("-N", "--host-name", type=base.unicode_decoder, default='', help='name of the host') + 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", type=base.unicode_decoder, default='', help='template to construct the URL') + 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): - common.checkURI(self.args) email = self.args.email[0] if self.args.email else None emails_extra = self.args.email[1:] @@ -399,7 +392,7 @@ class InviteeInvite(base.CommandBase): self.args.name, self.args.host_name, self.args.lang, - self.args.url, + self.args.url_template, self.args.subject, self.args.body, self.args.profile, diff --git a/frontends/src/jp/cmd_forums.py b/frontends/src/jp/cmd_forums.py index 6d4c217b..e80471c7 100644 --- a/frontends/src/jp/cmd_forums.py +++ b/frontends/src/jp/cmd_forums.py @@ -35,12 +35,11 @@ class Edit(base.CommandBase, common.BaseEdit): use_items=False def __init__(self, host): - base.CommandBase.__init__(self, host, 'edit', use_pubsub=True, use_verbose=True, help=_(u'edit forums')) + 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): - common.BaseEdit.add_parser_options(self) self.parser.add_argument("-k", "--key", type=base.unicode_decoder, default=u'', help=_(u"forum key (DEFAULT: default forums)")) @@ -84,7 +83,6 @@ class Edit(base.CommandBase, common.BaseEdit): exit_code=C.EXIT_BRIDGE_ERRBACK) def start(self): - common.checkURI(self.args) self.host.bridge.forumsGet( self.args.service, self.args.node, @@ -146,7 +144,6 @@ class Get(base.CommandBase): self.host.quit() def start(self): - common.checkURI(self.args) self.host.bridge.forumsGet( self.args.service, self.args.node, diff --git a/frontends/src/jp/cmd_merge_request.py b/frontends/src/jp/cmd_merge_request.py index 8d68fb1f..ef6b7500 100644 --- a/frontends/src/jp/cmd_merge_request.py +++ b/frontends/src/jp/cmd_merge_request.py @@ -20,7 +20,6 @@ import base from sat.core.i18n import _ -from sat_frontends.jp import common from sat_frontends.jp.constants import Const as C from functools import partial import os.path @@ -46,7 +45,6 @@ class Set(base.CommandBase): self.host.quit(C.EXIT_OK) def start(self): - common.checkURI(self.args) repository = os.path.expanduser(os.path.abspath(self.args.repository)) extra = {'update': 'true'} if self.args.item else {} self.host.bridge.mergeRequestSet( diff --git a/frontends/src/jp/cmd_pubsub.py b/frontends/src/jp/cmd_pubsub.py index 5336c37d..e1d233ba 100644 --- a/frontends/src/jp/cmd_pubsub.py +++ b/frontends/src/jp/cmd_pubsub.py @@ -46,7 +46,7 @@ ALLOWED_SUBSCRIPTIONS_OWNER = ('subscribed', 'pending', 'none') class NodeInfo(base.CommandBase): def __init__(self, host): - base.CommandBase.__init__(self, host, 'info', use_output=C.OUTPUT_DICT, use_pubsub_node_req=True, help=_(u'retrieve node configuration')) + 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): @@ -71,7 +71,6 @@ class NodeInfo(base.CommandBase): self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): - common.checkURI(self.args) self.host.bridge.psNodeConfigurationGet( self.args.service, self.args.node, @@ -83,7 +82,7 @@ class NodeInfo(base.CommandBase): class NodeCreate(base.CommandBase): def __init__(self, host): - base.CommandBase.__init__(self, host, 'create', use_output=C.OUTPUT_DICT, use_pubsub_node_req=True, use_verbose=True, help=_(u'create a node')) + 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): @@ -123,7 +122,7 @@ class NodeCreate(base.CommandBase): class NodeDelete(base.CommandBase): def __init__(self, host): - base.CommandBase.__init__(self, host, 'delete', use_pubsub_node_req=True, help=_(u'delete a node')) + 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): @@ -134,7 +133,6 @@ class NodeDelete(base.CommandBase): self.host.quit() def start(self): - common.checkURI(self.args) if not self.args.force: if not self.args.service: message = _(u"Are you sure to delete pep node [{node_id}] ?").format( @@ -161,7 +159,7 @@ class NodeDelete(base.CommandBase): class NodeSet(base.CommandBase): def __init__(self, host): - base.CommandBase.__init__(self, host, 'set', use_output=C.OUTPUT_DICT, use_pubsub_node_req=True, use_verbose=True, help=_(u'set node configuration')) + 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): @@ -184,7 +182,6 @@ class NodeSet(base.CommandBase): return k def start(self): - common.checkURI(self.args) self.host.bridge.psNodeConfigurationSet( self.args.service, self.args.node, @@ -197,7 +194,7 @@ class NodeSet(base.CommandBase): class NodeAffiliationsGet(base.CommandBase): def __init__(self, host): - base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_DICT, use_pubsub_node_req=True, help=_(u'retrieve node affiliations (for node owner)')) + 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): @@ -213,7 +210,6 @@ class NodeAffiliationsGet(base.CommandBase): self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): - common.checkURI(self.args) self.host.bridge.psNodeAffiliationsGet( self.args.service, self.args.node, @@ -225,7 +221,7 @@ class NodeAffiliationsGet(base.CommandBase): class NodeAffiliationsSet(base.CommandBase): def __init__(self, host): - base.CommandBase.__init__(self, host, 'set', use_pubsub_node_req=True, use_verbose=True, help=_(u'set affiliations (for node owner)')) + 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): @@ -251,7 +247,6 @@ class NodeAffiliationsSet(base.CommandBase): self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): - common.checkURI(self.args) affiliations = dict(self.args.affiliations) self.host.bridge.psNodeAffiliationsSet( self.args.service, @@ -272,7 +267,7 @@ class NodeAffiliations(base.CommandBase): class NodeSubscriptionsGet(base.CommandBase): def __init__(self, host): - base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_DICT, use_pubsub_node_req=True, help=_(u'retrieve node subscriptions (for node owner)')) + 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): @@ -288,7 +283,6 @@ class NodeSubscriptionsGet(base.CommandBase): self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): - common.checkURI(self.args) self.host.bridge.psNodeSubscriptionsGet( self.args.service, self.args.node, @@ -320,7 +314,7 @@ class StoreSubscriptionAction(argparse.Action): class NodeSubscriptionsSet(base.CommandBase): def __init__(self, host): - base.CommandBase.__init__(self, host, 'set', use_pubsub_node_req=True, use_verbose=True, help=_(u'set/modify subscriptions (for node owner)')) + 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): @@ -347,7 +341,6 @@ class NodeSubscriptionsSet(base.CommandBase): self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): - common.checkURI(self.args) self.host.bridge.psNodeSubscriptionsSet( self.args.service, self.args.node, @@ -367,7 +360,7 @@ class NodeSubscriptions(base.CommandBase): class NodeSchemaSet(base.CommandBase): def __init__(self, host): - base.CommandBase.__init__(self, host, 'set', use_pubsub_node_req=True, use_verbose=True, help=_(u'set/replace a schema')) + 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): @@ -393,12 +386,12 @@ class NodeSchemaEdit(base.CommandBase, common.BaseEdit): use_items=False def __init__(self, host): - base.CommandBase.__init__(self, host, 'edit', use_pubsub_node_req=True, use_verbose=True, help=_(u'edit a schema')) + 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): - common.BaseEdit.add_parser_options(self) + pass def psSchemaSetCb(self): self.disp(_(u'schema has been set'), 1) @@ -431,7 +424,6 @@ class NodeSchemaEdit(base.CommandBase, common.BaseEdit): self.runEditor("pubsub_schema_editor_args", content_file_path, content_file_obj) def start(self): - common.checkURI(self.args) self.host.bridge.psSchemaGet( self.args.service, self.args.node, @@ -445,7 +437,7 @@ class NodeSchemaEdit(base.CommandBase, common.BaseEdit): class NodeSchemaGet(base.CommandBase): def __init__(self, host): - base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_XML, use_pubsub_node_req=True, use_verbose=True, help=_(u'get schema')) + 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): @@ -459,7 +451,6 @@ class NodeSchemaGet(base.CommandBase): self.host.quit() def start(self): - common.checkURI(self.args) self.host.bridge.psSchemaGet( self.args.service, self.args.node, @@ -487,7 +478,7 @@ class Node(base.CommandBase): class Set(base.CommandBase): def __init__(self, host): - base.CommandBase.__init__(self, host, 'set', use_pubsub_node_req=True, help=_(u'publish a new item or update an existing one')) + 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): @@ -506,7 +497,6 @@ class Set(base.CommandBase): 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) - common.checkURI(self.args) try: element = etree.parse(sys.stdin).getroot() except Exception as e: @@ -532,12 +522,10 @@ class Set(base.CommandBase): class Get(base.CommandBase): def __init__(self, host): - base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_LIST_XML, use_pubsub_node_req=True, help=_(u'get pubsub item(s)')) + 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("-i", "--item", type=base.unicode_decoder, action='append', default=[], dest='items', - help=_(u"item(s) id(s) to get (default: request all items)")) self.parser.add_argument("-S", "--sub-id", type=base.unicode_decoder, default=u'', help=_(u"subscription id")) self.parser.add_argument("-m", "--max", type=int, default=10, help=_(u"maximum number of items to get ({} to get all items)".format(C.NO_LIMIT))) @@ -555,7 +543,6 @@ class Get(base.CommandBase): self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): - common.checkURI(self.args) self.host.bridge.psItemsGet( self.args.service, self.args.node, @@ -570,20 +557,18 @@ class Get(base.CommandBase): class Delete(base.CommandBase): def __init__(self, host): - base.CommandBase.__init__(self, host, 'delete', use_pubsub_node_req=True, help=_(u'delete an item')) + 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("item", nargs='?', type=base.unicode_decoder, help=_(u"item to delete")) 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")) + 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): - common.checkURI(self.args) if not self.args.item: self.parser.error(_(u"You need to specify an item to delete")) if not self.args.force: @@ -607,12 +592,12 @@ class Delete(base.CommandBase): class Edit(base.CommandBase, common.BaseEdit): def __init__(self, host): - base.CommandBase.__init__(self, host, 'edit', use_verbose=True, use_pubsub_node_req=True, help=_(u'edit an existing or new pubsub item')) + 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): - self.parser.add_argument("item", type=base.unicode_decoder, nargs='?', default=u'new', help=_(u"URL of the item to edit, or keyword")) - common.BaseEdit.add_parser_options(self) + pass def edit(self, content_file_path, content_file_obj): # we launch editor @@ -644,15 +629,14 @@ class Edit(base.CommandBase, common.BaseEdit): 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.args.item) - + 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_node_req=True, use_verbose=True, help=_(u'subscribe to a node')) + 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): @@ -680,7 +664,7 @@ 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_node_req=True, use_verbose=True, help=_(u'unsubscribe from a node')) + 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): @@ -758,7 +742,8 @@ class Search(base.CommandBase): EXEC_ACTIONS = (u'exec', u'external') def __init__(self, host): - base.CommandBase.__init__(self, host, 'search', use_output=C.OUTPUT_XML, use_pubsub=True, use_verbose=True, help=_(u'search items corresponding to filters')) + base.CommandBase.__init__(self, host, 'search', use_output=C.OUTPUT_XML, use_pubsub=True, pubsub_flags={C.MULTI_ITEMS}, + use_verbose=True, help=_(u'search items corresponding to filters')) self.need_loop=True @property @@ -778,7 +763,6 @@ class Search(base.CommandBase): return (type_, value) def add_parser_options(self): - self.parser.add_argument("-i", "--item", action="append", default=[], dest='items', type=base.unicode_decoder, help=_(u"item id(s)")) 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=[], @@ -1082,18 +1066,16 @@ class Search(base.CommandBase): if self.args.filters is None: self.args.filters = [] self.args.namespace = dict(self.args.namespace + [('pubsub', "http://jabber.org/protocol/pubsub")]) - common.checkURI(self.args) 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_node_req=True, help=_(u'build URI')) + 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("-i", "--item", type=base.unicode_decoder, help=_(u"item to link")) 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_): @@ -1127,7 +1109,7 @@ class Uri(base.CommandBase): class HookCreate(base.CommandBase): def __init__(self, host): - base.CommandBase.__init__(self, host, 'create', use_pubsub_node_req=True, help=_(u'create a Pubsub hook')) + 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): @@ -1143,7 +1125,6 @@ class HookCreate(base.CommandBase): self.parser.error(_(u"{path} is not a file").format(path=self.args.hook_arg)) def start(self): - common.checkURI(self.args) self.checkArgs(self) self.host.bridge.psHookAdd( self.args.service, @@ -1161,7 +1142,7 @@ class HookCreate(base.CommandBase): class HookDelete(base.CommandBase): def __init__(self, host): - base.CommandBase.__init__(self, host, 'delete', use_pubsub_node_req=True, help=_(u'delete a Pubsub hook')) + 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): @@ -1174,7 +1155,6 @@ class HookDelete(base.CommandBase): self.host.quit() def start(self): - common.checkURI(self.args) HookCreate.checkArgs(self) self.host.bridge.psHookRemove( self.args.service, diff --git a/frontends/src/jp/common.py b/frontends/src/jp/common.py index 0d24a63c..98c103c2 100644 --- a/frontends/src/jp/common.py +++ b/frontends/src/jp/common.py @@ -21,7 +21,6 @@ from sat_frontends.jp.constants import Const as C from sat.core.i18n import _ from sat.core import exceptions from sat.tools.common import regex -from sat.tools.common import uri from sat.tools.common.ansi import ANSI as A from sat.tools import config from ConfigParser import NoSectionError, NoOptionError @@ -105,40 +104,12 @@ def parse_args(host, cmd_line, **format_kw): return [] -def checkURI(args): - """check if args.node is an URI - - if a valid xmpp: URI is found, args.service, args.node and args.item will be set - """ - # FIXME: Q&D way to handle xmpp: uris, a generic way is needed - # and it should be merged with code in BaseEdit - if not args.service and args.node.startswith('xmpp:'): - try: - uri_data = uri.parseXMPPUri(args.node) - except ValueError: - pass - else: - if uri_data[u'type'] == 'pubsub': - args.service = uri_data[u'path'] - args.node = uri_data[u'node'] - if u'item' in uri_data: - try: - item = getattr(uri_data, 'item') - except AttributeError: - pass - else: - if item is None: - args.item = uri_data - - class BaseEdit(object): u"""base class for editing commands This class allows to edit file for PubSub or something else. It works with temporary files in SàT local_dir, in a "cat_dir" subdir """ - # use_items(bool): True if items are used, will then add item related options - use_items=True def __init__(self, host, cat_dir, use_metadata=False): """ @@ -154,12 +125,6 @@ class BaseEdit(object): self.cat_dir_str = cat_dir.encode('utf-8') self.use_metadata = use_metadata - def add_parser_options(self): - if self.use_items: - group = self.parser.add_mutually_exclusive_group() - group.add_argument("--force-item", action='store_true', help=_(u"don't use magic and take item argument as an actual item")) - group.add_argument("--last-item", action='store_true', help=_(u"take last item instead of creating a new one if no item id is found")) - def secureUnlink(self, path): """Unlink given path after keeping it for a while @@ -365,145 +330,73 @@ class BaseEdit(object): """return suffix used for content file""" return u'xml' - def getItemPath(self, item): + def getItemPath(self): """retrieve item path (i.e. service and node) from item argument This method is obviously only useful for edition of PubSub based features - service, node and item must be named like this in args - @param item(unicode): item to get or url or magic keyword - item argument can be used to specify : - - HTTP(S) URL - - XMPP URL - - keyword, which can be: - - new: create new item - - last: retrieve last published item - - current: continue current local draft - - file path - - item id """ - force_item = self.args.force_item - if force_item and not item: - self.parser.error(_(u"an item id must be specified if you use --force-item")) - command = item.lower() - pubsub_service = self.args.service - pubsub_node = self.args.node - pubsub_item = None + service = self.args.service + node = self.args.node + item = self.args.item + last_item = self.args.last_item - if not force_item and command not in ('new', 'last', 'current'): - # we have probably an URL, we try to parse it - import urlparse - url = self.args.item - parsed_url = urlparse.urlsplit(url.encode('utf-8')) - if parsed_url.scheme.startswith('http'): - self.disp(u"{} URL found, trying to find associated xmpp: URI".format(parsed_url.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(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) - url = links[0].get('href') - parsed_url = urlparse.urlsplit(url) - - if parsed_url.scheme == 'xmpp': - if self.args.service or self.args.node: - self.parser.error(_(u"You can't use URI and --service or --node at the same time")) - - self.disp(u"XMPP URI used: {}".format(url),2) - # XXX: if we have not xmpp: URI here, we'll take the data as a file path - pubsub_service = parsed_url.path.decode('utf-8') - pubsub_data = urlparse.parse_qs(parsed_url.query) - try: - pubsub_node = pubsub_data['node'][0].decode('utf-8') - except KeyError: - self.disp(u'No node found in xmpp: URI, can\'t retrieve item', error=True) - self.host.quit(1) - pubsub_item = pubsub_data.get('item',[None])[0] - if pubsub_item is not None: - pubsub_item = pubsub_item.decode('utf-8') - if pubsub_item is None and self.args.last_item: - command = 'last' - elif pubsub_item is not None: - command = 'edit' # XXX: edit command is only used internaly, it similar to last, but with the item given in the URL - else: - command = 'new' - - if self.args.last_item: - if pubsub_item is None: - command = 'last' - elif command != 'last': - self.parser.error(_(u"--last-item can't be used with a specified item")) - - if not force_item and command in ('new', 'last', 'edit'): + if self.args.current: + # user wants to continue current draft + content_file_path = self.getCurrentFile(self.profile) + self.disp(u'Continuing edition of current draft', 2) + content_file_obj = open(content_file_path, 'r+b') + # we seek at the end of file in case of an item already exist + # this will write content of the existing item at the end of the draft. + # This way no data should be lost. + content_file_obj.seek(0, os.SEEK_END) + elif self.args.draft_path: + # there is an existing draft that we use + content_file_path = os.path.expanduser(self.args.item) + content_file_obj = open(content_file_path, 'r+b') + # we seek at the end for the same reason as above + content_file_obj.seek(0, os.SEEK_END) + else: # we need a temporary file content_file_obj, content_file_path = self.getTmpFile() - if command == 'new': - self.disp(u'Editing a new item', 2) + + if item or last_item: + self.disp(u'Editing requested published item', 2) + try: if self.use_metadata: + content, metadata, item = self.getItemData(service, node, item) + else: + content, item = self.getItemData(service, node, item) + except Exception as e: + # FIXME: ugly but we have not good may to check errors in bridge + if u'item-not-found' in unicode(e): + # item doesn't exist, we create a new one with requested id metadata = None - elif command in ('last', 'edit'): - self.disp(u'Editing requested published item', 2) - try: - if self.use_metadata: - content, metadata, pubsub_item = self.getItemData(pubsub_service, pubsub_node, pubsub_item) + if last_item: + self.disp(_(u'no item found at all, we create a new one'), 2) else: - content, pubsub_item = self.getItemData(pubsub_service, pubsub_node, pubsub_item) - except Exception as e: - self.disp(u"Error while retrieving last item: {}".format(e)) - self.host.quit(1) + self.disp(_(u'item "{item_id}" not found, we create a new item with this id').format(item_id=item), 2) + content_file_obj.seek(0) + else: + self.disp(u"Error while retrieving item: {}".format(e)) + self.host.quit(C.EXIT_ERROR) + else: + # item exists, we write content + if content_file_obj.tell() != 0: + # we already have a draft, + # we copy item content after it and add an indicator + content_file_obj.write('\n*****\n') content_file_obj.write(content.encode('utf-8')) content_file_obj.seek(0) + self.disp(_(u'item "{item_id}" found, we edit it').format(item_id=item), 2) else: + self.disp(u'Editing a new item', 2) if self.use_metadata: metadata = None - if not force_item and command == 'current': - # user wants to continue current draft - content_file_path = self.getCurrentFile(self.profile) - self.disp(u'Continuing edition of current draft', 2) - content_file_obj = open(content_file_path, 'r+b') - elif not force_item and os.path.isfile(self.args.item): - # there is an existing draft that we use - content_file_path = os.path.expanduser(self.args.item) - content_file_obj = open(content_file_path, 'r+b') - else: - # last chance, it should be an item - content_file_obj, content_file_path = self.getTmpFile() - pubsub_item = self.args.item - - try: - # we try to get existing item - if self.use_metadata: - content, metadata, pubsub_item = self.getItemData(pubsub_service, pubsub_node, self.args.item) - else: - content, pubsub_item = self.getItemData(pubsub_service, pubsub_node, self.args.item) - except Exception as e: - # FIXME: ugly but we have not good may to check errors in bridge - if u'item-not-found' in unicode(e): - # item doesn't exist, we create a new one with requested id - metadata = None - self.disp(_(u'item "{item_id}" not found, we create a new item with this id').format(item_id=pubsub_item), 2) - else: - # item exists, we write content if content file - content_file_obj.write(content.encode('utf-8')) - content_file_obj.seek(0) - self.disp(_(u'item "{item_id}" found, we edit it').format(item_id=pubsub_item), 2) if self.use_metadata: - return pubsub_service, pubsub_node, pubsub_item, content_file_path, content_file_obj, metadata + return service, node, item, content_file_path, content_file_obj, metadata else: - return pubsub_service, pubsub_node, pubsub_item, content_file_path, content_file_obj + return service, node, item, content_file_path, content_file_obj class Table(object): diff --git a/frontends/src/jp/constants.py b/frontends/src/jp/constants.py index 93b8d067..6f1d231d 100644 --- a/frontends/src/jp/constants.py +++ b/frontends/src/jp/constants.py @@ -36,6 +36,12 @@ class Const(constants.Const): OUTPUT_LIST_XML = u'list_xml' # list of XML nodes (as unicode strings) OUTPUT_TYPES = (OUTPUT_TEXT, OUTPUT_DICT, OUTPUT_LIST, OUTPUT_LIST_DICT, OUTPUT_DICT_DICT, OUTPUT_COMPLEX, OUTPUT_XML, OUTPUT_LIST_XML) + # Pubsub options flags + SERVICE = u'service' # service required + NODE = u'node' # node required + SINGLE_ITEM = u'single_item' # only one item is allowed + MULTI_ITEMS = u'multi_items' # multiple items are allowed + # ANSI A_HEADER = A.BOLD + A.FG_YELLOW A_SUBHEADER = A.BOLD + A.FG_RED