# HG changeset patch # User Goffi # Date 1457197150 -3600 # Node ID ed444e9aa078b0e0762983958835ae0be4077bd3 # Parent 386d61abfac2506efc511a0e479acd331c3f6a0f 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) diff -r 386d61abfac2 -r ed444e9aa078 frontends/src/jp/cmd_blog.py --- a/frontends/src/jp/cmd_blog.py Sat Mar 05 15:43:14 2016 +0100 +++ b/frontends/src/jp/cmd_blog.py Sat Mar 05 17:59:10 2016 +0100 @@ -60,6 +60,7 @@ KEY_TO_REMOVE_METADATA = ('id','content', 'content_xhtml', 'comments_node', 'comments_service') URL_REDIRECT_PREFIX = 'url_redirect_' +INOTIFY_INSTALL = '"pip install inotify"' class BlogCommon(object): @@ -313,46 +314,99 @@ class Preview(base.CommandBase, BlogCommon): def __init__(self, host): - super(Preview, self).__init__(host, 'preview', help=_(u'preview a blog content')) + super(Preview, self).__init__(host, 'preview', use_verbose=True, help=_(u'preview a blog content')) def add_parser_options(self): + self.parser.add_argument("--inotify", type=str, choices=('auto', 'true', 'false'), default=u'auto', help=_(u"use inotify to handle preview")) self.parser.add_argument("file", type=base.unicode_decoder, nargs='?', default=u'current', help=_(u"path to the content file")) - def showPreview(self, content_xhtml, file_obj): - import webbrowser - import urllib + def showPreview(self): + # we implement showPreview here so we don't have to import webbroser and urllib + # when preview is not used + url = 'file:{}'.format(self.urllib.quote(self.preview_file_path)) + self.webbrowser.open_new_tab(url) + + def updateContent(self): + with open(self.current_file_path, 'rb') as f: + content_xhtml = f.read().decode('utf-8') + if self.syntax != 'XHTML': + # we use safe=True because we want to have a preview as close as possible to what the + # people will see + content_xhtml = self.host.bridge.syntaxConvert(content_xhtml, self.syntax, 'XHTML', True, self.profile) + xhtml = (u'' + u''+ '{}' + u'').format(content_xhtml) - file_obj.write(xhtml.encode('utf-8')) - url = 'file:{}'.format(urllib.quote(file_obj.name)) - webbrowser.open_new_tab(url) + + with open(self.preview_file_path, 'wb') as f: + f.write(xhtml.encode('utf-8')) def start(self): + import webbrowser + import urllib + self.webbrowser, self.urllib = webbrowser, urllib + + if self.args.inotify != 'false': + try: + import inotify.adapters + import inotify.constants + except ImportError: + if self.args.inotify == 'auto': + inotify = None + self.disp(u'inotify module not found, deactivating feature. You can install it with {install}'.format(install=INOTIFY_INSTALL)) + else: + self.disp(u"inotify not found, can't activate the feature! Please install it with {install}".format(install=INOTIFY_INSTALL), error=True) + self.host.quit(1) + else: + # we deactivate logging in inotify, which is quite annoying + try: + inotify.adapters._LOGGER.setLevel(40) + except AttributeError: + self.disp(u"Logger doesn't exists, inotify may have chanded", error=True) + else: + inotify=None + sat_conf = config.parseMainConf() SYNTAX_EXT.update(config.getConfig(sat_conf, 'jp', CONF_SYNTAX_EXT, {})) + open_cb = self.showPreview + update_cb = self.showPreview + # which file do we need to edit? if self.args.file == 'current': - current_file_path = self.getCurrentFile(sat_conf) + self.current_file_path = self.getCurrentFile(sat_conf) else: - current_file_path = os.path.abspath(self.args.file) - - syntax = self.guessSyntaxFromPath(sat_conf, current_file_path) + self.current_file_path = os.path.abspath(self.args.file) - if syntax != 'XHTML': - with open(current_file_path, 'rb') as f: - # we use safe=True because we want to have a preview as close as possible to what the - # people will see - content_xhtml = self.host.bridge.syntaxConvert(f.read(), syntax, 'XHTML', True, self.profile) + self.syntax = self.guessSyntaxFromPath(sat_conf, self.current_file_path) + # at this point the syntax is converted, we can display the preview + preview_file = tempfile.NamedTemporaryFile(suffix='.xhtml', delete=False) + self.preview_file_path = preview_file.name + preview_file.close() + self.updateContent() - with tempfile.NamedTemporaryFile(suffix='.xhtml', delete=False) as f: + if inotify is None: # XXX: we don't delete file automatically because browser need it (and webbrowser.open can return before it is read) - 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)) - self.showPreview(content_xhtml, f) + 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)) + open_cb() + else: + open_cb() + i = inotify.adapters.Inotify(block_duration_s=60) # no need for 1 s duraction, inotify drive actions here + # XXX: we only check IN_CLOSE_WRITE, but that may need to be changed depending on editor + # experience will tell the appropriate values + i.add_watch(self.current_file_path, mask=inotify.constants.IN_CLOSE_WRITE) + try: + for event in i.event_gen(): + if event is not None: + self.disp(u"Content updated", 1) + self.updateContent() + update_cb() + finally: + os.unlink(self.preview_file_path) + i.remove_watch(self.current_file_path) class Import(base.CommandAnswering):