comparison frontends/src/jp/cmd_blog.py @ 2157:b4a515e36631

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
author Goffi <goffi@goffi.org>
date Thu, 16 Feb 2017 01:02:33 +0100
parents 5fbe09b9b568
children 970a348d3fe9
comparison
equal deleted inserted replaced
2156:8f96c242fa89 2157:b4a515e36631
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 20
21 import base 21 import base
22 from sat.core.i18n import _ 22 from sat.core.i18n import _
23 from sat.core.constants import Const as C 23 from sat_frontends.jp.constants import Const as C
24 from sat.tools.common.ansi import ANSI as A
24 from sat.tools import config 25 from sat.tools import config
25 from ConfigParser import NoSectionError, NoOptionError 26 from ConfigParser import NoSectionError, NoOptionError
26 import json 27 import json
27 import sys 28 import sys
28 import os.path 29 import os.path
61 # key to remove from metadata tmp file if they exist 62 # key to remove from metadata tmp file if they exist
62 KEY_TO_REMOVE_METADATA = ('id','content', 'content_xhtml', 'comments_node', 'comments_service', 'updated') 63 KEY_TO_REMOVE_METADATA = ('id','content', 'content_xhtml', 'comments_node', 'comments_service', 'updated')
63 64
64 URL_REDIRECT_PREFIX = 'url_redirect_' 65 URL_REDIRECT_PREFIX = 'url_redirect_'
65 INOTIFY_INSTALL = '"pip install inotify"' 66 INOTIFY_INSTALL = '"pip install inotify"'
66 SECURE_UNLINK_MAX = 10 * 2 # we double value has there are 2 files per draft (content and metadata) 67 SECURE_UNLINK_MAX = 10 * 2 # we double value as there are 2 files per draft (content and metadata)
67 SECURE_UNLINK_DIR = ".backup" 68 SECURE_UNLINK_DIR = ".backup"
69 HEADER_ANSI = A.BOLD + A.FG_YELLOW
70 NS_MICROBLOG = u'urn:xmpp:microblog:0'
71 MB_KEYS = (u"id",
72 u"atom_id",
73 u"updated",
74 u"published",
75 u"comments", # this key is used for all comments* keys
76 u"tags", # this key is used for all tag* keys
77 u"author",
78 u"author_jid",
79 u"author_email",
80 u"author_jid_verified",
81 u"content",
82 u"content_xhtml",
83 u"title",
84 u"title_xhtml")
85 OUTPUT_OPT_NO_HEADER = u'no-header'
68 86
69 87
70 class BlogCommon(object): 88 class BlogCommon(object):
71 89
72 def __init__(self, host): 90 def __init__(self, host):
167 except ValueError as e: 185 except ValueError as e:
168 self.disp(u"Couldn't parse editor cmd [{cmd}]: {reason}".format(cmd=cmd_line, reason=e)) 186 self.disp(u"Couldn't parse editor cmd [{cmd}]: {reason}".format(cmd=cmd_line, reason=e))
169 return [] 187 return []
170 188
171 189
190 class Get(base.CommandBase):
191
192 def __init__(self, host):
193 extra_outputs = {'default': self.default_output,
194 'fancy': self.fancy_output}
195 base.CommandBase.__init__(self, host, 'get', use_verbose=True, use_output='complex', extra_outputs=extra_outputs, help=_(u'get blog item(s)'))
196 self.need_loop=True
197
198 def add_parser_options(self):
199 self.parser.add_argument("-n", "--node", type=base.unicode_decoder, default=NS_MICROBLOG, help=_(u"PubSub node to request"))
200 self.parser.add_argument("-i", "--item", type=base.unicode_decoder, action='append', default=[], dest='items',
201 help=_(u"item(s) id(s) to get (default: request all items)"))
202 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)))
203 # TODO: a key(s) argument to select keys to display
204 self.parser.add_argument("-k", "--key", type=base.unicode_decoder, action='append', dest='keys',
205 help=_(u"microblog data key(s) to display (default: depend of verbosity)"))
206 self.parser.add_argument("service", type=base.unicode_decoder, nargs='?', default=u'',
207 help=_(u"JID of the PubSub service (default: request profile own blog)"))
208 # TODO: add MAM filters
209
210 def format_comments(self, item, keys):
211 comments_data = data_format.dict2iterdict(u'comments', item, (u'node', u'service'), pop=True)
212 lines = []
213 for data in comments_data:
214 lines.append(data[u'comments'])
215 for k in (u'node', u'service'):
216 if OUTPUT_OPT_NO_HEADER in self.args.output_opts:
217 header = u''
218 else:
219 header = HEADER_ANSI + k + u': ' + A.RESET
220 lines.append(header + data[k])
221 return u'\n'.join(lines)
222
223 def format_tags(self, item, keys):
224 tags = data_format.dict2iter('tag', item, pop=True)
225 return u', '.join(tags)
226
227 def get_keys(self):
228 """return keys to display according to verbosity or explicit key request"""
229 verbosity = self.args.verbose
230 if self.args.keys:
231 if not set(MB_KEYS).issuperset(self.args.keys):
232 self.disp(u"following keys are invalid: {invalid}.\n"
233 u"Valid keys are: {valid}.".format(
234 invalid = u', '.join(set(self.args.keys).difference(MB_KEYS)),
235 valid = u', '.join(sorted(MB_KEYS))),
236 error=True)
237 self.host.quit(C.EXIT_BAD_ARG)
238 return self.args.keys
239 else:
240 if verbosity == 0:
241 return (u'title', u'content')
242 elif verbosity == 1:
243 return (u"title", u"tags", u"author", u"author_jid", u"author_email", u"author_jid_verified", u"content")
244 else:
245 return MB_KEYS
246
247 def default_output(self, data):
248 """simple key/value output"""
249 items, metadata = data
250 keys = self.get_keys()
251
252 # k_cb use format_[key] methods for complex formattings
253 k_cb = {}
254 for k in keys:
255 try:
256 callback = getattr(self, "format_" + k)
257 except AttributeError:
258 pass
259 else:
260 k_cb[k] = callback
261 for idx, item in enumerate(items):
262 for k in keys:
263 if k not in item and k not in k_cb:
264 continue
265 if OUTPUT_OPT_NO_HEADER in self.args.output_opts:
266 header = ''
267 else:
268 header = u"{k_fmt}{key}:{k_fmt_e} {sep}".format(
269 k_fmt = HEADER_ANSI,
270 key = k,
271 k_fmt_e = A.RESET,
272 sep = u'\n' if 'content' in k else u'')
273 value = k_cb[k](item, keys) if k in k_cb else item[k]
274 print header + value
275 # we want a separation line after each item but the last one
276 if idx < len(items)-1:
277 print(u'')
278
279 def fancy_output(self, data):
280 """display blog is a nice to read way
281
282 this output doesn't use keys filter
283 """
284 # thanks to http://stackoverflow.com/a/943921
285 rows, columns = map(int, os.popen('stty size', 'r').read().split())
286 items, metadata = data
287 sep = A.color(A.FG_BLUE, columns * u'▬')
288 if items:
289 print(u'\n' + sep + '\n')
290
291 for idx, item in enumerate(items):
292 title = item.get(u'title')
293 tags = list(data_format.dict2iter('tag', item, pop=True))
294 content = item.get(u'content')
295
296 if title:
297 print(A.color(A.BOLD, A.FG_CYAN, item[u'title']))
298 if tags:
299 print(A.color(A.FG_YELLOW, u', '.join(tags)))
300 if (title or tags) and content:
301 print("")
302 if content:
303 print content
304
305 print(u'\n' + sep + '\n')
306
307
308 def mbGetCb(self, mb_result):
309 self.output(mb_result)
310 self.host.quit(C.EXIT_OK)
311
312 def mbGetEb(self, failure_):
313 self.disp(u"can't get blog items: {reason}".format(
314 reason=failure_), error=True)
315 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
316
317 def start(self):
318 self.host.bridge.mbGet(
319 self.args.service,
320 self.args.node,
321 self.args.max,
322 self.args.items,
323 {},
324 self.profile,
325 callback=self.mbGetCb,
326 errback=self.mbGetEb)
327
328
172 class Edit(base.CommandBase, BlogCommon): 329 class Edit(base.CommandBase, BlogCommon):
173 330
174 def __init__(self, host): 331 def __init__(self, host):
175 base.CommandBase.__init__(self, host, 'edit', use_verbose=True, help=_(u'edit an existing or new blog post')) 332 base.CommandBase.__init__(self, host, 'edit', use_verbose=True, help=_(u'edit an existing or new blog post'))
176 BlogCommon.__init__(self, self.host) 333 BlogCommon.__init__(self, self.host)
652 self.host.bridge.blogImport(self.args.importer, self.args.location, options, self.args.service, self.profile, 809 self.host.bridge.blogImport(self.args.importer, self.args.location, options, self.args.service, self.profile,
653 callback=gotId, errback=self.error) 810 callback=gotId, errback=self.error)
654 811
655 812
656 class Blog(base.CommandBase): 813 class Blog(base.CommandBase):
657 subcommands = (Edit, Preview, Import) 814 subcommands = (Get, Edit, Preview, Import)
658 815
659 def __init__(self, host): 816 def __init__(self, host):
660 super(Blog, self).__init__(host, 'blog', use_profile=False, help=_('blog/microblog management')) 817 super(Blog, self).__init__(host, 'blog', use_profile=False, help=_('blog/microblog management'))