Mercurial > libervia-backend
comparison frontends/src/jp/cmd_blog.py @ 1882:ed444e9aa078
jp (blog/preview): added inotify support, so preview can be updated each time the file is modified (actually IN_CLOSE_WRITE is used, but this may change in the future)
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 05 Mar 2016 17:59:10 +0100 |
parents | 386d61abfac2 |
children | abb2f253188e |
comparison
equal
deleted
inserted
replaced
1881:386d61abfac2 | 1882:ed444e9aa078 |
---|---|
58 METADATA_SUFF = '_metadata.json' | 58 METADATA_SUFF = '_metadata.json' |
59 # key to remove from metadata tmp file if they exist | 59 # key to remove from metadata tmp file if they exist |
60 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') |
61 | 61 |
62 URL_REDIRECT_PREFIX = 'url_redirect_' | 62 URL_REDIRECT_PREFIX = 'url_redirect_' |
63 INOTIFY_INSTALL = '"pip install inotify"' | |
63 | 64 |
64 | 65 |
65 class BlogCommon(object): | 66 class BlogCommon(object): |
66 def getTmpDir(self, sat_conf): | 67 def getTmpDir(self, sat_conf): |
67 """Return directory used to store temporary files | 68 """Return directory used to store temporary files |
311 | 312 |
312 | 313 |
313 class Preview(base.CommandBase, BlogCommon): | 314 class Preview(base.CommandBase, BlogCommon): |
314 | 315 |
315 def __init__(self, host): | 316 def __init__(self, host): |
316 super(Preview, self).__init__(host, 'preview', help=_(u'preview a blog content')) | 317 super(Preview, self).__init__(host, 'preview', use_verbose=True, help=_(u'preview a blog content')) |
317 | 318 |
318 def add_parser_options(self): | 319 def add_parser_options(self): |
320 self.parser.add_argument("--inotify", type=str, choices=('auto', 'true', 'false'), default=u'auto', help=_(u"use inotify to handle preview")) | |
319 self.parser.add_argument("file", type=base.unicode_decoder, nargs='?', default=u'current', help=_(u"path to the content file")) | 321 self.parser.add_argument("file", type=base.unicode_decoder, nargs='?', default=u'current', help=_(u"path to the content file")) |
320 | 322 |
321 def showPreview(self, content_xhtml, file_obj): | 323 def showPreview(self): |
322 import webbrowser | 324 # we implement showPreview here so we don't have to import webbroser and urllib |
323 import urllib | 325 # when preview is not used |
326 url = 'file:{}'.format(self.urllib.quote(self.preview_file_path)) | |
327 self.webbrowser.open_new_tab(url) | |
328 | |
329 def updateContent(self): | |
330 with open(self.current_file_path, 'rb') as f: | |
331 content_xhtml = f.read().decode('utf-8') | |
332 if self.syntax != 'XHTML': | |
333 # we use safe=True because we want to have a preview as close as possible to what the | |
334 # people will see | |
335 content_xhtml = self.host.bridge.syntaxConvert(content_xhtml, self.syntax, 'XHTML', True, self.profile) | |
336 | |
324 xhtml = (u'<html xmlns="http://www.w3.org/1999/xhtml">' + | 337 xhtml = (u'<html xmlns="http://www.w3.org/1999/xhtml">' + |
325 u'<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /></head>'+ | 338 u'<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /></head>'+ |
326 '<body>{}</body>' + | 339 '<body>{}</body>' + |
327 u'</html>').format(content_xhtml) | 340 u'</html>').format(content_xhtml) |
328 file_obj.write(xhtml.encode('utf-8')) | 341 |
329 url = 'file:{}'.format(urllib.quote(file_obj.name)) | 342 with open(self.preview_file_path, 'wb') as f: |
330 webbrowser.open_new_tab(url) | 343 f.write(xhtml.encode('utf-8')) |
331 | 344 |
332 def start(self): | 345 def start(self): |
346 import webbrowser | |
347 import urllib | |
348 self.webbrowser, self.urllib = webbrowser, urllib | |
349 | |
350 if self.args.inotify != 'false': | |
351 try: | |
352 import inotify.adapters | |
353 import inotify.constants | |
354 except ImportError: | |
355 if self.args.inotify == 'auto': | |
356 inotify = None | |
357 self.disp(u'inotify module not found, deactivating feature. You can install it with {install}'.format(install=INOTIFY_INSTALL)) | |
358 else: | |
359 self.disp(u"inotify not found, can't activate the feature! Please install it with {install}".format(install=INOTIFY_INSTALL), error=True) | |
360 self.host.quit(1) | |
361 else: | |
362 # we deactivate logging in inotify, which is quite annoying | |
363 try: | |
364 inotify.adapters._LOGGER.setLevel(40) | |
365 except AttributeError: | |
366 self.disp(u"Logger doesn't exists, inotify may have chanded", error=True) | |
367 else: | |
368 inotify=None | |
369 | |
333 sat_conf = config.parseMainConf() | 370 sat_conf = config.parseMainConf() |
334 SYNTAX_EXT.update(config.getConfig(sat_conf, 'jp', CONF_SYNTAX_EXT, {})) | 371 SYNTAX_EXT.update(config.getConfig(sat_conf, 'jp', CONF_SYNTAX_EXT, {})) |
335 | 372 |
373 open_cb = self.showPreview | |
374 update_cb = self.showPreview | |
375 | |
336 # which file do we need to edit? | 376 # which file do we need to edit? |
337 if self.args.file == 'current': | 377 if self.args.file == 'current': |
338 current_file_path = self.getCurrentFile(sat_conf) | 378 self.current_file_path = self.getCurrentFile(sat_conf) |
339 else: | 379 else: |
340 current_file_path = os.path.abspath(self.args.file) | 380 self.current_file_path = os.path.abspath(self.args.file) |
341 | 381 |
342 syntax = self.guessSyntaxFromPath(sat_conf, current_file_path) | 382 self.syntax = self.guessSyntaxFromPath(sat_conf, self.current_file_path) |
343 | 383 |
344 if syntax != 'XHTML': | |
345 with open(current_file_path, 'rb') as f: | |
346 # we use safe=True because we want to have a preview as close as possible to what the | |
347 # people will see | |
348 content_xhtml = self.host.bridge.syntaxConvert(f.read(), syntax, 'XHTML', True, self.profile) | |
349 | 384 |
350 # at this point the syntax is converted, we can display the preview | 385 # at this point the syntax is converted, we can display the preview |
351 | 386 preview_file = tempfile.NamedTemporaryFile(suffix='.xhtml', delete=False) |
352 with tempfile.NamedTemporaryFile(suffix='.xhtml', delete=False) as f: | 387 self.preview_file_path = preview_file.name |
388 preview_file.close() | |
389 self.updateContent() | |
390 | |
391 if inotify is None: | |
353 # XXX: we don't delete file automatically because browser need it (and webbrowser.open can return before it is read) | 392 # XXX: we don't delete file automatically because browser need it (and webbrowser.open can return before it is read) |
354 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)) | 393 self.disp(u'temporary file created at {}\nthis file will NOT BE DELETED AUTOMATICALLY, please delete it yourself when you have finished'.format(self.preview_file_path)) |
355 self.showPreview(content_xhtml, f) | 394 open_cb() |
395 else: | |
396 open_cb() | |
397 i = inotify.adapters.Inotify(block_duration_s=60) # no need for 1 s duraction, inotify drive actions here | |
398 # XXX: we only check IN_CLOSE_WRITE, but that may need to be changed depending on editor | |
399 # experience will tell the appropriate values | |
400 i.add_watch(self.current_file_path, mask=inotify.constants.IN_CLOSE_WRITE) | |
401 try: | |
402 for event in i.event_gen(): | |
403 if event is not None: | |
404 self.disp(u"Content updated", 1) | |
405 self.updateContent() | |
406 update_cb() | |
407 finally: | |
408 os.unlink(self.preview_file_path) | |
409 i.remove_watch(self.current_file_path) | |
356 | 410 |
357 | 411 |
358 class Import(base.CommandAnswering): | 412 class Import(base.CommandAnswering): |
359 def __init__(self, host): | 413 def __init__(self, host): |
360 super(Import, self).__init__(host, 'import', use_progress=True, help=_(u'import an external blog')) | 414 super(Import, self).__init__(host, 'import', use_progress=True, help=_(u'import an external blog')) |