Mercurial > libervia-backend
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')) |