comparison frontends/src/jp/cmd_blog.py @ 1874:658824755a0c

jp (blog): preview command, first draft
author Goffi <goffi@goffi.org>
date Thu, 03 Mar 2016 18:28:53 +0100
parents 6ec54626610c
children 1088bf7b28e7
comparison
equal deleted inserted replaced
1873:6ec54626610c 1874:658824755a0c
28 import os 28 import os
29 import time 29 import time
30 import tempfile 30 import tempfile
31 import subprocess 31 import subprocess
32 import shlex 32 import shlex
33 import glob
33 from sat.tools import common 34 from sat.tools import common
34 35
35 __commands__ = ["Blog"] 36 __commands__ = ["Blog"]
36 37
37 # extensions to use with known syntaxes 38 # extensions to use with known syntaxes
52 'nano': ' -F {content_file} {metadata_file}', 53 'nano': ' -F {content_file} {metadata_file}',
53 } 54 }
54 55
55 CONF_SYNTAX_EXT = 'syntax_ext_dict' 56 CONF_SYNTAX_EXT = 'syntax_ext_dict'
56 BLOG_TMP_DIR="blog" 57 BLOG_TMP_DIR="blog"
58 METADATA_SUFF = '_metadata.json'
57 # key to remove from metadata tmp file if they exist 59 # key to remove from metadata tmp file if they exist
58 KEY_TO_REMOVE_METADATA = ('id','content', 'content_xhtml', 'comments_node', 'comments_service') 60 KEY_TO_REMOVE_METADATA = ('id','content', 'content_xhtml', 'comments_node', 'comments_service')
59 61
60 URL_REDIRECT_PREFIX = 'url_redirect_' 62 URL_REDIRECT_PREFIX = 'url_redirect_'
61 63
62 64
65 def getTmpDir(sat_conf):
66 """Return directory used to store temporary files
67
68 @param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration
69 @return (str): path to the dir
70 """
71 local_dir = config.getConfig(sat_conf, '', 'local_dir', Exception)
72 return os.path.join(local_dir, BLOG_TMP_DIR)
73
74
63 class Edit(base.CommandBase): 75 class Edit(base.CommandBase):
64 76
65 def __init__(self, host): 77 def __init__(self, host):
66 super(Edit, self).__init__(host, 'edit', use_verbose=True, help=_(u'edit an existing or new blog post')) 78 super(Edit, self).__init__(host, 'edit', use_verbose=True, help=_(u'edit an existing or new blog post'))
67 79
68 def add_parser_options(self): 80 def add_parser_options(self):
69 self.parser.add_argument("item", type=base.unicode_decoder, nargs='?', default=u'new', help=_(u"URL of the item to edit, or keyword")) 81 self.parser.add_argument("item", type=base.unicode_decoder, nargs='?', default=u'new', help=_(u"URL of the item to edit, or keyword"))
70 self.parser.add_argument("-T", '--title', type=base.unicode_decoder, help=_(u"Title of the item")) 82 self.parser.add_argument("-T", '--title', type=base.unicode_decoder, help=_(u"title of the item"))
71 self.parser.add_argument("-t", '--tag', type=base.unicode_decoder, action='append', help=_(u"tag (category) of your item")) 83 self.parser.add_argument("-t", '--tag', type=base.unicode_decoder, action='append', help=_(u"tag (category) of your item"))
72 self.parser.add_argument("--no-comment", action='store_true', help=_(u"disable comments")) 84 self.parser.add_argument("--no-comment", action='store_true', help=_(u"disable comments"))
73 85
74 def getTmpFile(self, sat_conf, tmp_suff): 86 def getTmpFile(self, sat_conf, tmp_suff):
75 """Create a temporary file to received blog item body 87 """Create a temporary file to received blog item body
76 88
77 @param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration 89 @param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration
78 @param tmp_suff (str): suffix to use for the filename 90 @param tmp_suff (str): suffix to use for the filename
79 @return (tuple(file, str)): opened (w+b) file object and file path 91 @return (tuple(file, str)): opened (w+b) file object and file path
80 """ 92 """
81 local_dir = config.getConfig(sat_conf, '', 'local_dir', Exception) 93 tmp_dir = getTmpDir(sat_conf)
82 tmp_dir = os.path.join(local_dir, BLOG_TMP_DIR)
83 if not os.path.exists(tmp_dir): 94 if not os.path.exists(tmp_dir):
84 try: 95 try:
85 os.makedirs(tmp_dir) 96 os.makedirs(tmp_dir)
86 except OSError as e: 97 except OSError as e:
87 self.disp(u"Can't create {path} directory: {reason}".format( 98 self.disp(u"Can't create {path} directory: {reason}".format(
117 common.iter2dict('tag', self.args.tag, mb_data) 128 common.iter2dict('tag', self.args.tag, mb_data)
118 if self.args.title is not None: 129 if self.args.title is not None:
119 mb_data['title'] = self.args.title 130 mb_data['title'] = self.args.title
120 131
121 # the we create the file and write metadata there, as JSON dict 132 # the we create the file and write metadata there, as JSON dict
122 meta_file_path = os.path.splitext(content_file_path)[0] + '_metadata.json' 133 meta_file_path = os.path.splitext(content_file_path)[0] + METADATA_SUFF
123 # XXX: if we port jp one day on Windows, O_BINARY may need to be added here 134 # XXX: if we port jp one day on Windows, O_BINARY may need to be added here
124 if os.path.exists(meta_file_path): 135 if os.path.exists(meta_file_path):
125 self.disp(u"metadata file {} already exists, this should not happen! Cancelling...", error=True) 136 self.disp(u"metadata file {} already exists, this should not happen! Cancelling...", error=True)
126 with os.fdopen(os.open(meta_file_path, os.O_RDWR | os.O_CREAT ,0o600), 'w+b') as f: 137 with os.fdopen(os.open(meta_file_path, os.O_RDWR | os.O_CREAT ,0o600), 'w+b') as f:
127 # we need to use an intermediate unicode buffer to write to the file unicode without escaping characters 138 # we need to use an intermediate unicode buffer to write to the file unicode without escaping characters
240 if current_syntax != 'XHTML': 251 if current_syntax != 'XHTML':
241 content = self.host.bridge.syntaxConvert(content, 'XHTML', current_syntax, False, self.profile) 252 content = self.host.bridge.syntaxConvert(content, 'XHTML', current_syntax, False, self.profile)
242 content_file_obj.write(content.encode('utf-8')) 253 content_file_obj.write(content.encode('utf-8'))
243 content_file_obj.seek(0) 254 content_file_obj.seek(0)
244 self.edit(sat_conf, content_file_path, content_file_obj, mb_data=mb_data) 255 self.edit(sat_conf, content_file_path, content_file_obj, mb_data=mb_data)
256
257
258 class Preview(base.CommandBase):
259
260 def __init__(self, host):
261 super(Preview, self).__init__(host, 'preview', use_verbose=True, help=_(u'preview a blog content'))
262
263 def add_parser_options(self):
264 self.parser.add_argument("file", type=base.unicode_decoder, nargs='?', default=u'current', help=_(u"path to the content file"))
265
266 def start(self):
267 sat_conf = config.parseMainConf()
268
269 # which file do we need to edit?
270 if self.args.file == 'current':
271 # we guess the blog item currently edited by choosing
272 # the most recent file corresponding to temp file pattern
273 # in tmp_dir, excluding metadata files
274 tmp_dir = getTmpDir(sat_conf)
275 available = [path for path in glob.glob(os.path.join(tmp_dir, 'blog_*')) if not path.endswith(METADATA_SUFF)]
276 if not available:
277 self.disp(u"Counldn't find any content draft in {path}".format(path=tmp_dir), error=True)
278 self.host.quit(1)
279 current_path = max(available, key=lambda path: os.stat(path).st_mtime)
280 else:
281 current_path = os.path.abspath(self.args.file)
282
283 # we first try to guess syntax with extension
284 SYNTAX_EXT.update(config.getConfig(sat_conf, 'jp', CONF_SYNTAX_EXT, {}))
285 ext = os.path.splitext(current_path)[1][1:] # we get extension without the '.'
286 syntax = None
287 if ext:
288 for k,v in SYNTAX_EXT.iteritems():
289 if ext == v:
290 syntax = k
291 break
292
293 # if not found, we use current syntax
294 if syntax is None:
295 syntax = self.host.bridge.getParamA("Syntax", "Composition", "value", self.profile)
296 if syntax != 'XHTML':
297 with open(current_path, 'rb') as f:
298 content_xhtml = self.host.bridge.syntaxConvert(f.read(), syntax, 'XHTML', False, self.profile)
299 import webbrowser
300 import urllib
301 with tempfile.NamedTemporaryFile(suffix='.xhtml', delete=False) as f:
302 # XXX: we don't delete file automatically because browser need it (and webbrowser.open can return before it is read)
303 self.disp(u'temporary file created at {}\nthis file will NOT BE DELETED AUTOMATICALLY, please delete it yourself when you have finished'.format(f.name))
304 xhtml = (u'<html xmlns="http://www.w3.org/1999/xhtml">' +
305 u'<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /></head>'+
306 '<body>{}</body>' +
307 u'</html>').format(content_xhtml)
308 f.write(xhtml.encode('utf-8'))
309 url = 'file:{}'.format(urllib.quote(f.name))
310 webbrowser.open_new_tab(url)
245 311
246 312
247 class Import(base.CommandAnswering): 313 class Import(base.CommandAnswering):
248 def __init__(self, host): 314 def __init__(self, host):
249 super(Import, self).__init__(host, 'import', use_progress=True, help=_(u'import an external blog')) 315 super(Import, self).__init__(host, 'import', use_progress=True, help=_(u'import an external blog'))
320 self.host.bridge.blogImport(self.args.importer, self.args.location, options, self.args.service, self.profile, 386 self.host.bridge.blogImport(self.args.importer, self.args.location, options, self.args.service, self.profile,
321 callback=gotId, errback=self.error) 387 callback=gotId, errback=self.error)
322 388
323 389
324 class Blog(base.CommandBase): 390 class Blog(base.CommandBase):
325 subcommands = (Edit, Import) 391 subcommands = (Edit, Preview, Import)
326 392
327 def __init__(self, host): 393 def __init__(self, host):
328 super(Blog, self).__init__(host, 'blog', use_profile=False, help=_('blog/microblog management')) 394 super(Blog, self).__init__(host, 'blog', use_profile=False, help=_('blog/microblog management'))