comparison frontends/src/jp/cmd_blog.py @ 1877:a97db84c048d

jp (blog): moved common method in a BlogCommon class + added "current" as an item keyword for blog/edit
author Goffi <goffi@goffi.org>
date Fri, 04 Mar 2016 11:22:41 +0100
parents 1088bf7b28e7
children 386d61abfac2
comparison
equal deleted inserted replaced
1876:1088bf7b28e7 1877:a97db84c048d
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 63
64 64
65 def getTmpDir(sat_conf): 65 class BlogCommon(object):
66 """Return directory used to store temporary files 66 def getTmpDir(self, sat_conf):
67 67 """Return directory used to store temporary files
68 @param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration 68
69 @return (str): path to the dir 69 @param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration
70 """ 70 @return (str): path to the dir
71 local_dir = config.getConfig(sat_conf, '', 'local_dir', Exception) 71 """
72 return os.path.join(local_dir, BLOG_TMP_DIR) 72 local_dir = config.getConfig(sat_conf, '', 'local_dir', Exception)
73 73 return os.path.join(local_dir, BLOG_TMP_DIR)
74 74
75 class Edit(base.CommandBase): 75 def getCurrentFile(self, sat_conf):
76 # we guess the blog item currently edited by choosing
77 # the most recent file corresponding to temp file pattern
78 # in tmp_dir, excluding metadata files
79 tmp_dir = self.getTmpDir(sat_conf)
80 available = [path for path in glob.glob(os.path.join(tmp_dir, 'blog_*')) if not path.endswith(METADATA_SUFF)]
81 if not available:
82 self.disp(u"Counldn't find any content draft in {path}".format(path=tmp_dir), error=True)
83 self.host.quit(1)
84 return max(available, key=lambda path: os.stat(path).st_mtime)
85
86 def guessSyntaxFromPath(self, sat_conf, path):
87 """Return syntax guessed according to filename extension
88
89 @param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration
90 @param path(str): path to the content file
91 @return(unicode): syntax to use
92 """
93 # we first try to guess syntax with extension
94 ext = os.path.splitext(path)[1][1:] # we get extension without the '.'
95 if ext:
96 for k,v in SYNTAX_EXT.iteritems():
97 if ext == v:
98 return v
99
100 # if not found, we use current syntax
101 return self.host.bridge.getParamA("Syntax", "Composition", "value", self.profile)
102
103
104 class Edit(base.CommandBase, BlogCommon):
76 105
77 def __init__(self, host): 106 def __init__(self, host):
78 super(Edit, self).__init__(host, 'edit', use_verbose=True, help=_(u'edit an existing or new blog post')) 107 super(Edit, self).__init__(host, 'edit', use_verbose=True, help=_(u'edit an existing or new blog post'))
79 108
80 def add_parser_options(self): 109 def add_parser_options(self):
88 117
89 @param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration 118 @param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration
90 @param tmp_suff (str): suffix to use for the filename 119 @param tmp_suff (str): suffix to use for the filename
91 @return (tuple(file, str)): opened (w+b) file object and file path 120 @return (tuple(file, str)): opened (w+b) file object and file path
92 """ 121 """
93 tmp_dir = getTmpDir(sat_conf) 122 tmp_dir = self.getTmpDir(sat_conf)
94 if not os.path.exists(tmp_dir): 123 if not os.path.exists(tmp_dir):
95 try: 124 try:
96 os.makedirs(tmp_dir) 125 os.makedirs(tmp_dir)
97 except OSError as e: 126 except OSError as e:
98 self.disp(u"Can't create {path} directory: {reason}".format( 127 self.disp(u"Can't create {path} directory: {reason}".format(
236 265
237 os.unlink(content_file_path) 266 os.unlink(content_file_path)
238 os.unlink(meta_file_path) 267 os.unlink(meta_file_path)
239 268
240 def start(self): 269 def start(self):
241 # we get current syntax to determine file extension 270 item_lower = self.args.item.lower()
242 current_syntax = self.host.bridge.getParamA("Syntax", "Composition", "value", self.profile)
243 self.disp(u"Current syntax: {}".format(current_syntax), 1)
244 sat_conf = config.parseMainConf() 271 sat_conf = config.parseMainConf()
245 # if there are user defined extension, we use them 272 # if there are user defined extension, we use them
246 SYNTAX_EXT.update(config.getConfig(sat_conf, 'jp', CONF_SYNTAX_EXT, {})) 273 SYNTAX_EXT.update(config.getConfig(sat_conf, 'jp', CONF_SYNTAX_EXT, {}))
247 274 current_syntax = None
248 # we now create a temporary file 275
249 tmp_suff = '.' + SYNTAX_EXT.get(current_syntax, SYNTAX_EXT['']) 276 if item_lower == 'current':
250 content_file_obj, content_file_path = self.getTmpFile(sat_conf, tmp_suff) 277 # use wants to continue current draft
251 278 content_file_path = self.getCurrentFile(sat_conf)
252 item_lower = self.args.item.lower() 279 content_file_obj = open(content_file_path, 'r+b')
280 current_syntax = self.guessSyntaxFromPath(sat_conf, content_file_path)
281 else:
282 # we get current syntax to determine file extension
283 current_syntax = self.host.bridge.getParamA("Syntax", "Composition", "value", self.profile)
284 # we now create a temporary file
285 tmp_suff = '.' + SYNTAX_EXT.get(current_syntax, SYNTAX_EXT[''])
286 content_file_obj, content_file_path = self.getTmpFile(sat_conf, tmp_suff)
287
288 self.disp(u"Syntax used: {}".format(current_syntax), 1)
289
253 if item_lower == 'new': 290 if item_lower == 'new':
254 self.disp(u'Editing a new blog item', 2) 291 self.disp(u'Editing a new blog item', 2)
255 self.edit(sat_conf, content_file_path, content_file_obj) 292 mb_data = None
293 elif item_lower == 'current':
294 self.disp(u'Continuing edition of current draft', 2)
295 mb_data = None
256 elif item_lower == 'last': 296 elif item_lower == 'last':
257 self.disp(u'Editing last published item', 2) 297 self.disp(u'Editing last published item', 2)
258 try: 298 try:
259 mb_data = self.host.bridge.mbGet('', '', 1, [], {}, self.profile)[0][0] 299 mb_data = self.host.bridge.mbGet('', '', 1, [], {}, self.profile)[0][0]
260 except Exception as e: 300 except Exception as e:
264 content = mb_data['content_xhtml'] 304 content = mb_data['content_xhtml']
265 if current_syntax != 'XHTML': 305 if current_syntax != 'XHTML':
266 content = self.host.bridge.syntaxConvert(content, 'XHTML', current_syntax, False, self.profile) 306 content = self.host.bridge.syntaxConvert(content, 'XHTML', current_syntax, False, self.profile)
267 content_file_obj.write(content.encode('utf-8')) 307 content_file_obj.write(content.encode('utf-8'))
268 content_file_obj.seek(0) 308 content_file_obj.seek(0)
269 self.edit(sat_conf, content_file_path, content_file_obj, mb_data=mb_data) 309
270 310 self.edit(sat_conf, content_file_path, content_file_obj, mb_data=mb_data)
271 311
272 class Preview(base.CommandBase): 312
313 class Preview(base.CommandBase, BlogCommon):
273 314
274 def __init__(self, host): 315 def __init__(self, host):
275 super(Preview, self).__init__(host, 'preview', use_verbose=True, help=_(u'preview a blog content')) 316 super(Preview, self).__init__(host, 'preview', help=_(u'preview a blog content'))
276 317
277 def add_parser_options(self): 318 def add_parser_options(self):
278 self.parser.add_argument("file", type=base.unicode_decoder, nargs='?', default=u'current', help=_(u"path to the content file")) 319 self.parser.add_argument("file", type=base.unicode_decoder, nargs='?', default=u'current', help=_(u"path to the content file"))
279 320
321 def showPreview(self, content_xhtml, file_obj):
322 import webbrowser
323 import urllib
324 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>'+
326 '<body>{}</body>' +
327 u'</html>').format(content_xhtml)
328 file_obj.write(xhtml.encode('utf-8'))
329 url = 'file:{}'.format(urllib.quote(file_obj.name))
330 webbrowser.open_new_tab(url)
331
280 def start(self): 332 def start(self):
281 sat_conf = config.parseMainConf() 333 sat_conf = config.parseMainConf()
334 SYNTAX_EXT.update(config.getConfig(sat_conf, 'jp', CONF_SYNTAX_EXT, {}))
282 335
283 # which file do we need to edit? 336 # which file do we need to edit?
284 if self.args.file == 'current': 337 if self.args.file == 'current':
285 # we guess the blog item currently edited by choosing 338 current_file_path = self.getCurrentFile(sat_conf)
286 # the most recent file corresponding to temp file pattern
287 # in tmp_dir, excluding metadata files
288 tmp_dir = getTmpDir(sat_conf)
289 available = [path for path in glob.glob(os.path.join(tmp_dir, 'blog_*')) if not path.endswith(METADATA_SUFF)]
290 if not available:
291 self.disp(u"Counldn't find any content draft in {path}".format(path=tmp_dir), error=True)
292 self.host.quit(1)
293 current_path = max(available, key=lambda path: os.stat(path).st_mtime)
294 else: 339 else:
295 current_path = os.path.abspath(self.args.file) 340 current_file_path = os.path.abspath(self.args.file)
296 341
297 # we first try to guess syntax with extension 342 syntax = self.guessSyntaxFromPath(sat_conf, current_file_path)
298 SYNTAX_EXT.update(config.getConfig(sat_conf, 'jp', CONF_SYNTAX_EXT, {})) 343
299 ext = os.path.splitext(current_path)[1][1:] # we get extension without the '.'
300 syntax = None
301 if ext:
302 for k,v in SYNTAX_EXT.iteritems():
303 if ext == v:
304 syntax = k
305 break
306
307 # if not found, we use current syntax
308 if syntax is None:
309 syntax = self.host.bridge.getParamA("Syntax", "Composition", "value", self.profile)
310 if syntax != 'XHTML': 344 if syntax != 'XHTML':
311 with open(current_path, 'rb') as f: 345 with open(current_file_path, 'rb') as f:
312 content_xhtml = self.host.bridge.syntaxConvert(f.read(), syntax, 'XHTML', False, self.profile) 346 content_xhtml = self.host.bridge.syntaxConvert(f.read(), syntax, 'XHTML', False, self.profile)
313 import webbrowser 347
314 import urllib 348 # at this point the syntax is converted, we can display the preview
349
315 with tempfile.NamedTemporaryFile(suffix='.xhtml', delete=False) as f: 350 with tempfile.NamedTemporaryFile(suffix='.xhtml', delete=False) as f:
316 # XXX: we don't delete file automatically because browser need it (and webbrowser.open can return before it is read) 351 # XXX: we don't delete file automatically because browser need it (and webbrowser.open can return before it is read)
317 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)) 352 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))
318 xhtml = (u'<html xmlns="http://www.w3.org/1999/xhtml">' + 353 self.showPreview(content_xhtml, f)
319 u'<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /></head>'+
320 '<body>{}</body>' +
321 u'</html>').format(content_xhtml)
322 f.write(xhtml.encode('utf-8'))
323 url = 'file:{}'.format(urllib.quote(f.name))
324 webbrowser.open_new_tab(url)
325 354
326 355
327 class Import(base.CommandAnswering): 356 class Import(base.CommandAnswering):
328 def __init__(self, host): 357 def __init__(self, host):
329 super(Import, self).__init__(host, 'import', use_progress=True, help=_(u'import an external blog')) 358 super(Import, self).__init__(host, 'import', use_progress=True, help=_(u'import an external blog'))