# HG changeset patch # User Goffi # Date 1487203353 -3600 # Node ID b4a515e36631a1538723ab6e5aca8bedf4592f4f # Parent 8f96c242fa89d5c617b39c4177d344a0a0d0f6da jp (blog): added blog/get command: - blog/get will retrieve and parse blog items on specified service/node and can request only one or more specific items according to ids of max value - it takes profit of outputs and the new extra_outputs: a default output display microblogs as key/value, a fancy one display it in a more confortable way for reading it the terminal - for default output, keys can be filtered using --key argument diff -r 8f96c242fa89 -r b4a515e36631 frontends/src/jp/cmd_blog.py --- a/frontends/src/jp/cmd_blog.py Thu Feb 16 00:51:33 2017 +0100 +++ b/frontends/src/jp/cmd_blog.py Thu Feb 16 01:02:33 2017 +0100 @@ -20,7 +20,8 @@ import base from sat.core.i18n import _ -from sat.core.constants import Const as C +from sat_frontends.jp.constants import Const as C +from sat.tools.common.ansi import ANSI as A from sat.tools import config from ConfigParser import NoSectionError, NoOptionError import json @@ -63,8 +64,25 @@ URL_REDIRECT_PREFIX = 'url_redirect_' INOTIFY_INSTALL = '"pip install inotify"' -SECURE_UNLINK_MAX = 10 * 2 # we double value has there are 2 files per draft (content and metadata) +SECURE_UNLINK_MAX = 10 * 2 # we double value as there are 2 files per draft (content and metadata) SECURE_UNLINK_DIR = ".backup" +HEADER_ANSI = A.BOLD + A.FG_YELLOW +NS_MICROBLOG = u'urn:xmpp:microblog:0' +MB_KEYS = (u"id", + u"atom_id", + u"updated", + u"published", + 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' class BlogCommon(object): @@ -169,6 +187,145 @@ return [] +class Get(base.CommandBase): + + 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='complex', extra_outputs=extra_outputs, help=_(u'get blog item(s)')) + self.need_loop=True + + def add_parser_options(self): + self.parser.add_argument("-n", "--node", type=base.unicode_decoder, default=NS_MICROBLOG, help=_(u"PubSub node to request")) + 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', + help=_(u"microblog data key(s) to display (default: depend of verbosity)")) + self.parser.add_argument("service", type=base.unicode_decoder, nargs='?', default=u'', + help=_(u"JID of the PubSub service (default: request profile own blog)")) + # TODO: add MAM filters + + 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 = HEADER_ANSI + 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 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"content") + else: + return MB_KEYS + + def default_output(self, data): + """simple key/value output""" + items, 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 = HEADER_ANSI, + 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] + print header + value + # we want a separation line after each item but the last one + if idx < len(items)-1: + print(u'') + + 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 + 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') + tags = list(data_format.dict2iter('tag', item, pop=True)) + content = item.get(u'content') + + if title: + print(A.color(A.BOLD, A.FG_CYAN, item[u'title'])) + if tags: + print(A.color(A.FG_YELLOW, u', '.join(tags))) + if (title or tags) and content: + print("") + if content: + print 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, BlogCommon): def __init__(self, host): @@ -654,7 +811,7 @@ class Blog(base.CommandBase): - subcommands = (Edit, Preview, Import) + subcommands = (Get, Edit, Preview, Import) def __init__(self, host): super(Blog, self).__init__(host, 'blog', use_profile=False, help=_('blog/microblog management'))