jp (blog/edit): editor arguments can now be specified on sat.conf, and default on are applied for known editors:
- vim and gvim will open content and metadata file in a splitted window
- gvim use --nofork option
- installed a workaround for shlex.split not handling unicode before Python 2.7.3
- some fixes
This commit is contained in:
parent
f993fa07df
commit
5704cb4586
2 changed files with 67 additions and 31 deletions
|
|
@ -38,6 +38,15 @@ from sat.core import exceptions
|
|||
import sat_frontends.jp
|
||||
from sat_frontends.jp.constants import Const as C
|
||||
import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI
|
||||
import shlex
|
||||
|
||||
if sys.version_info < (2, 7, 3):
|
||||
# XXX: shlex.split only handle unicode since python 2.7.3
|
||||
# this is a workaround for older versions
|
||||
old_split = shlex.split
|
||||
new_split = (lambda s, *a, **kw: [t.decode('utf-8') for t in old_split(s.encode('utf-8'), *a, **kw)]
|
||||
if isinstance(s, unicode) else old_split(s, *a, **kw))
|
||||
shlex.split = new_split
|
||||
|
||||
try:
|
||||
import progressbar
|
||||
|
|
|
|||
|
|
@ -22,20 +22,32 @@ import base
|
|||
from sat.core.i18n import _
|
||||
from sat.core.constants import Const as C
|
||||
from sat.tools import config
|
||||
from ConfigParser import NoSectionError, NoOptionError
|
||||
import json
|
||||
import os.path
|
||||
import os
|
||||
import time
|
||||
import tempfile
|
||||
import subprocess
|
||||
import shlex
|
||||
from sat.tools import common
|
||||
|
||||
__commands__ = ["Blog"]
|
||||
|
||||
SYNTAX_EXT = { '': 'txt', # used when the syntax is not found
|
||||
"XHTML": "xhtml",
|
||||
"markdown": "md"
|
||||
}
|
||||
# extensions to use with known syntaxes
|
||||
SYNTAX_EXT = {
|
||||
'': 'txt', # used when the syntax is not found
|
||||
"XHTML": "xhtml",
|
||||
"markdown": "md"
|
||||
}
|
||||
|
||||
# defaut arguments used for some known editors
|
||||
VIM_SPLIT_ARGS="-c 'vsplit|wincmd w|next|wincmd w'"
|
||||
EDITOR_ARGS_MAGIC = {
|
||||
'vim': VIM_SPLIT_ARGS + ' {content_file} {metadata_file}',
|
||||
'gvim': VIM_SPLIT_ARGS + ' --nofork {content_file} {metadata_file}',
|
||||
}
|
||||
|
||||
CONF_SYNTAX_EXT = 'syntax_ext_dict'
|
||||
BLOG_TMP_DIR="blog"
|
||||
# key to remove from metadata tmp file if they exist
|
||||
|
|
@ -80,11 +92,11 @@ class Edit(base.CommandBase):
|
|||
self.disp(u"Can't create temporary file: {reason}".format(reason=e), error=True)
|
||||
self.host.quit(1)
|
||||
|
||||
def buildMetadataFile(self, tmp_file_path, mb_data=None):
|
||||
def buildMetadataFile(self, content_file_path, mb_data=None):
|
||||
"""Build a metadata file using json
|
||||
|
||||
The file is named after tmp_file_path, with extension replaced by _metadata.json
|
||||
@param tmp_file_path(str): path to the temporary file which will contain the body
|
||||
The file is named after content_file_path, with extension replaced by _metadata.json
|
||||
@param content_file_path(str): path to the temporary file which will contain the body
|
||||
@param mb_data(dict, None): microblog metadata (for existing items)
|
||||
@return (tuple[dict, str]): merged metadata put originaly in metadata file
|
||||
and path to temporary metadata file
|
||||
|
|
@ -103,7 +115,7 @@ class Edit(base.CommandBase):
|
|||
mb_data['title'] = self.args.title
|
||||
|
||||
# the we create the file and write metadata there, as JSON dict
|
||||
meta_file_path = os.path.splitext(tmp_file_path)[0] + '_metadata.json'
|
||||
meta_file_path = os.path.splitext(content_file_path)[0] + '_metadata.json'
|
||||
# XXX: if we port jp one day on Windows, O_BINARY may need to be added here
|
||||
if os.path.exists(meta_file_path):
|
||||
self.disp(u"metadata file {} already exists, this should not happen! Cancelling...", error=True)
|
||||
|
|
@ -114,46 +126,61 @@ class Edit(base.CommandBase):
|
|||
|
||||
return mb_data, meta_file_path
|
||||
|
||||
def edit(self, sat_conf, tmp_file_path, tmp_file_obj, mb_data=None):
|
||||
def edit(self, sat_conf, content_file_path, content_file_obj, mb_data=None):
|
||||
"""Edit the file contening the content using editor, and publish it"""
|
||||
item_ori_mb_data = mb_data
|
||||
# we first create metadata file
|
||||
meta_ori, meta_file_path = self.buildMetadataFile(tmp_file_path, item_ori_mb_data)
|
||||
meta_ori, meta_file_path = self.buildMetadataFile(content_file_path, item_ori_mb_data)
|
||||
|
||||
# then we calculate hashes to check for modifications
|
||||
import hashlib
|
||||
tmp_file_obj.seek(0)
|
||||
tmp_ori_hash = hashlib.sha1(tmp_file_obj.read()).digest()
|
||||
tmp_file_obj.close()
|
||||
content_file_obj.seek(0)
|
||||
tmp_ori_hash = hashlib.sha1(content_file_obj.read()).digest()
|
||||
content_file_obj.close()
|
||||
|
||||
# then we launch editor
|
||||
editor = config.getConfig(sat_conf, 'jp', 'editor') or os.getenv('EDITOR', 'vi')
|
||||
editor_exit = subprocess.call([editor, tmp_file_path])
|
||||
try:
|
||||
# is there custom arguments in sat.conf ?
|
||||
editor_args = config.getConfig(sat_conf, 'jp', 'blog_editor_args', Exception)
|
||||
except (NoOptionError, NoSectionError):
|
||||
# no, we check if we know the editor and have special arguments
|
||||
editor_args = EDITOR_ARGS_MAGIC.get(os.path.basename(editor), '')
|
||||
try:
|
||||
# we split the arguments and add the known fields
|
||||
# we split arguments first to avoid escaping issues in file names
|
||||
args = [a.format(content_file=content_file_path, metadata_file=meta_file_path) for a in shlex.split(editor_args)]
|
||||
except ValueError as e:
|
||||
self.disp(u"Couldn't parse editor cmd [{cmd}]: {reason}".format(cmd=editor_args, reason=e))
|
||||
args = []
|
||||
if not args:
|
||||
args = [content_file_path]
|
||||
editor_exit = subprocess.call([editor] + args)
|
||||
|
||||
# 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_path), error=True)
|
||||
path=content_file_path), error=True)
|
||||
else:
|
||||
try:
|
||||
with open(tmp_file_path, 'rb') as f:
|
||||
with open(content_file_path, 'rb') as f:
|
||||
content = f.read()
|
||||
with open(meta_file_path, 'rb') as f:
|
||||
mb_data = json.load(f)
|
||||
except OSError:
|
||||
self.disp(u"Can write files at {file_path} and/or {meta_path}, have they been deleted? Cancelling edition".format(
|
||||
file_path=tmp_file_path, meta_path=meta_file_path), error=True)
|
||||
except (OSError, IOError):
|
||||
self.disp(u"Can read files at {content_path} and/or {meta_path}, have they been deleted?\nCancelling edition".format(
|
||||
content_path=content_file_path, meta_path=meta_file_path), error=True)
|
||||
self.host.quit(1)
|
||||
except ValueError:
|
||||
self.disp(u"Can't parse metadata, please check it is correct JSON format. Cancelling edition.\n" +
|
||||
"You can find tmp file at {file_path} and temporary meta file at {meta_path}.".format(
|
||||
file_path=tmp_file_path, meta_path=meta_file_path), error=True)
|
||||
"You can find tmp file at {content_path} and temporary meta file at {meta_path}.".format(
|
||||
content_path=content_file_path, meta_path=meta_file_path), error=True)
|
||||
self.host.quit(1)
|
||||
|
||||
if not C.bool(mb_data.get('publish', "true")):
|
||||
self.disp(u'Publication blocked by "publish" key in metadata, cancelling edition.\n\n' +
|
||||
"temporary file path:\t{file_path}\nmetadata file path:\t{meta_path}".format(
|
||||
file_path=tmp_file_path, meta_path=meta_file_path), error=True)
|
||||
"temporary file path:\t{content_path}\nmetadata file path:\t{meta_path}".format(
|
||||
content_path=content_file_path, meta_path=meta_file_path), error=True)
|
||||
self.host.quit(0)
|
||||
|
||||
if len(content) == 0:
|
||||
|
|
@ -174,11 +201,11 @@ class Edit(base.CommandBase):
|
|||
try:
|
||||
self.host.bridge.mbSend('', '', mb_data, self.profile)
|
||||
except Exception as e:
|
||||
self.disp(u"Error while sending your blog, the temporary files have been kept at {file_path} and {meta_path}: {reason}".format(
|
||||
file_path=tmp_file_path, meta_path=meta_file_path, reason=e), error=True)
|
||||
self.disp(u"Error while sending your blog, the temporary files have been kept at {content_path} and {meta_path}: {reason}".format(
|
||||
content_path=content_file_path, meta_path=meta_file_path, reason=e), error=True)
|
||||
self.host.quit(1)
|
||||
|
||||
os.unlink(tmp_file_path)
|
||||
os.unlink(content_file_path)
|
||||
os.unlink(meta_file_path)
|
||||
|
||||
def start(self):
|
||||
|
|
@ -191,12 +218,12 @@ class Edit(base.CommandBase):
|
|||
|
||||
# we now create a temporary file
|
||||
tmp_suff = '.' + SYNTAX_EXT.get(current_syntax, SYNTAX_EXT[''])
|
||||
tmp_file_obj, tmp_file_path = self.getTmpFile(sat_conf, tmp_suff)
|
||||
content_file_obj, content_file_path = 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_path, tmp_file_obj)
|
||||
self.edit(sat_conf, content_file_path, content_file_obj)
|
||||
elif item_lower == 'last':
|
||||
self.disp(u'Editing last published item', 2)
|
||||
try:
|
||||
|
|
@ -208,9 +235,9 @@ class Edit(base.CommandBase):
|
|||
content = mb_data['content_xhtml']
|
||||
if current_syntax != 'XHTML':
|
||||
content = self.host.bridge.syntaxConvert(content, 'XHTML', current_syntax, False, self.profile)
|
||||
tmp_file_obj.write(content.encode('utf-8'))
|
||||
tmp_file_obj.seek(0)
|
||||
self.edit(sat_conf, tmp_file_path, tmp_file_obj, mb_data=mb_data)
|
||||
content_file_obj.write(content.encode('utf-8'))
|
||||
content_file_obj.seek(0)
|
||||
self.edit(sat_conf, content_file_path, content_file_obj, mb_data=mb_data)
|
||||
|
||||
|
||||
class Import(base.CommandAnswering):
|
||||
|
|
|
|||
Loading…
Reference in a new issue