# HG changeset patch # User Goffi # Date 1457017026 -3600 # Node ID df1ca137b0cb0c9781e888a8c29f8e81da55a145 # Parent 64a40adccba492dd8bfdee7d4b8012bd4c1ab571 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 diff -r 64a40adccba4 -r df1ca137b0cb frontends/src/jp/base.py --- a/frontends/src/jp/base.py Wed Mar 02 19:53:53 2016 +0100 +++ b/frontends/src/jp/base.py Thu Mar 03 15:57:06 2016 +0100 @@ -38,6 +38,15 @@ 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 diff -r 64a40adccba4 -r df1ca137b0cb frontends/src/jp/cmd_blog.py --- a/frontends/src/jp/cmd_blog.py Wed Mar 02 19:53:53 2016 +0100 +++ b/frontends/src/jp/cmd_blog.py Thu Mar 03 15:57:06 2016 +0100 @@ -22,20 +22,32 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ # 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 @@ 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):