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
This commit is contained in:
Goffi 2016-03-01 01:54:21 +01:00
commit dc8f07aec4

View file

@ -21,11 +21,138 @@
import base
from sat.core.i18n import _
from sat.core.constants import Const as C
from sat.tools import config
import json
URL_REDIRECT_PREFIX = 'url_redirect_'
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_'
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):
def __init__(self, host):