changeset 1866:397ef87958b9

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
author Goffi <goffi@goffi.org>
date Tue, 01 Mar 2016 01:54:21 +0100
parents fc6eeacf31bc
children 47108a4f3a70
files frontends/src/jp/cmd_blog.py
diffstat 1 files changed, 128 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- 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):