# HG changeset patch # User Goffi # Date 1456793661 -3600 # Node ID 397ef87958b902b60f8d44069985683dce075d65 # Parent fc6eeacf31bc519b701197b582c3db6d6111f902 jp (blog): edit command, first draft: - can edit a new or existing item, by default edit a new item - item are selected with a shortcut, for now only "new" and "last" are handled. URI handling is planed, specially xmpp: URIs - file are edited using EDITOR env variable, or editor in [jp] section in sat.conf - current syntax is used, file extension is choosed according to syntax, to make syntax coloration and other goodies in editor available - if file is empty or not modified, nothing is published - for now, title, tags and commend desactivation are handled with optional arguments, but other are planed, and a metadata system should come soon diff -r fc6eeacf31bc -r 397ef87958b9 frontends/src/jp/cmd_blog.py --- a/frontends/src/jp/cmd_blog.py Tue Mar 01 01:47:32 2016 +0100 +++ b/frontends/src/jp/cmd_blog.py Tue Mar 01 01:54:21 2016 +0100 @@ -21,10 +21,137 @@ import base from sat.core.i18n import _ from sat.core.constants import Const as C +from sat.tools import config import json +import os.path +import os +import time +import tempfile +import subprocess +from sat.tools import common + +__commands__ = ["Blog"] + +SYNTAX_EXT = { '': 'txt', # used when the syntax is not found + "XHTML": "xhtml", + "markdown": "md" + } +CONF_SYNTAX_EXT = 'syntax_ext_dict' +BLOG_TMP_DIR="blog" + URL_REDIRECT_PREFIX = 'url_redirect_' -__commands__ = ["Blog"] + +class Edit(base.CommandBase): + + def __init__(self, host): + super(Edit, self).__init__(host, 'edit', use_verbose=True, help=_(u'edit an existing or new blog post')) + + 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")) + 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("--no-comment", action='store_true', help=_(u"disable comments")) + + def getTmpFile(self, sat_conf, tmp_suff): + local_dir = config.getConfig(sat_conf, '', 'local_dir', Exception) + tmp_dir = os.path.join(local_dir, BLOG_TMP_DIR) + if not os.path.exists(tmp_dir): + try: + os.makedirs(tmp_dir) + except OSError as e: + self.disp(u"Can't create {path} directory: {reason}".format( + path=tmp_dir, reason=e), error=True) + self.host.quit(1) + + try: + return tempfile.mkstemp(suffix=tmp_suff, + prefix=time.strftime('blog_%Y-%m-%d_%H:%M:%S_'), + dir=tmp_dir, text=True) + except OSError as e: + self.disp(u"Can't create temporary file: {reason}".format(reason=e), error=True) + self.host.quit(1) + + def edit(self, sat_conf, tmp_file, tmp_file_obj, item_id=None): + """Edit the file contening the content using editor, and publish it""" + # we first calculate hash to check for modifications + import hashlib + tmp_file_obj.seek(0) + ori_hash = hashlib.sha1(tmp_file_obj.read()).digest() + tmp_file_obj.close() + + # the we launch editor + editor = config.getConfig(sat_conf, 'jp', 'editor') or os.getenv('EDITOR', 'vi') + editor_exit = subprocess.call([editor, tmp_file]) + + # we send the file if edition was a success + if editor_exit != 0: + self.disp(u"Editor exited with an error code, so temporary file has not be deleted, and blog item is not published.\nTou can find temporary file at {path}".format( + path=tmp_file), error=True) + else: + with open(tmp_file, 'rb') as f: + content = f.read() + + if len(content) == 0: + self.disp(u"Content is empty, cancelling the blog edition") + + # time to re-check the hash + elif ori_hash == hashlib.sha1(content).digest(): + self.disp(u"The content has not been modified, cancelling the blog edition") + + else: + # we can now send the blog + mb_data = { + 'content_rich': content.decode('utf-8'), + 'allow_comments': C.boolConst(not self.args.no_comment), + } + if item_id: + mb_data['id'] = item_id + if self.args.tag: + common.iter2dict('tag', self.args.tag, mb_data) + + if self.args.title is not None: + mb_data['title'] = self.args.title + try: + self.host.bridge.mbSend('', '', mb_data, self.profile) + except Exception as e: + self.disp(u"Error while sending your blog, the temporary file has been kept at {path}: {reason}".format( + path=tmp_file, reason=e), error=True) + self.host.quit(1) + + os.unlink(tmp_file) + + def start(self): + # we get current syntax to determine file extension + current_syntax = self.host.bridge.getParamA("Syntax", "Composition", "value", self.profile) + self.disp(u"Current syntax: {}".format(current_syntax), 1) + sat_conf = config.parseMainConf() + # if there are user defined extension, we use them + SYNTAX_EXT.update(config.getConfig(sat_conf, 'jp', CONF_SYNTAX_EXT, {})) + + # we now create a temporary file + tmp_suff = '.' + SYNTAX_EXT.get(current_syntax, SYNTAX_EXT['']) + fd, tmp_file = self.getTmpFile(sat_conf, tmp_suff) + + item_lower = self.args.item.lower() + if item_lower == 'new': + self.disp(u'Editing a new blog item', 2) + self.edit(sat_conf, tmp_file, os.fdopen(fd)) + elif item_lower == 'last': + self.disp(u'Editing last published item', 2) + try: + mb_data = self.host.bridge.mbGet('', '', 1, [], {}, self.profile)[0][0] + except Exception as e: + self.disp(u"Error while retrieving last comment: {}".format(e)) + self.host.quit(1) + + content = mb_data['content_xhtml'] + if current_syntax != 'XHTML': + content = self.host.bridge.syntaxConvert(content, 'XHTML', current_syntax, False, self.profile) + f = os.fdopen(fd, 'w+b') + f.write(content.encode('utf-8')) + f.seek(0) + self.edit(sat_conf, tmp_file, f, mb_data['id']) class Import(base.CommandAnswering):