Mercurial > libervia-backend
comparison sat_frontends/jp/cmd_blog.py @ 2624:56f94936df1e
code style reformatting using black
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 27 Jun 2018 20:14:46 +0200 |
parents | 0b6adc2672d9 |
children | 266f871fcb4b |
comparison
equal
deleted
inserted
replaced
2623:49533de4540b | 2624:56f94936df1e |
---|---|
38 import codecs | 38 import codecs |
39 from sat.tools.common import data_format | 39 from sat.tools.common import data_format |
40 | 40 |
41 __commands__ = ["Blog"] | 41 __commands__ = ["Blog"] |
42 | 42 |
43 SYNTAX_XHTML = u'xhtml' | 43 SYNTAX_XHTML = u"xhtml" |
44 # extensions to use with known syntaxes | 44 # extensions to use with known syntaxes |
45 SYNTAX_EXT = { | 45 SYNTAX_EXT = { |
46 '': 'txt', # used when the syntax is not found | 46 "": "txt", # used when the syntax is not found |
47 SYNTAX_XHTML: "xhtml", | 47 SYNTAX_XHTML: "xhtml", |
48 "markdown": "md" | 48 "markdown": "md", |
49 } | 49 } |
50 | 50 |
51 | 51 |
52 CONF_SYNTAX_EXT = u'syntax_ext_dict' | 52 CONF_SYNTAX_EXT = u"syntax_ext_dict" |
53 BLOG_TMP_DIR = u"blog" | 53 BLOG_TMP_DIR = u"blog" |
54 # key to remove from metadata tmp file if they exist | 54 # key to remove from metadata tmp file if they exist |
55 KEY_TO_REMOVE_METADATA = ('id','content', 'content_xhtml', 'comments_node', 'comments_service', 'updated') | 55 KEY_TO_REMOVE_METADATA = ( |
56 | 56 "id", |
57 URL_REDIRECT_PREFIX = 'url_redirect_' | 57 "content", |
58 "content_xhtml", | |
59 "comments_node", | |
60 "comments_service", | |
61 "updated", | |
62 ) | |
63 | |
64 URL_REDIRECT_PREFIX = "url_redirect_" | |
58 INOTIFY_INSTALL = '"pip install inotify"' | 65 INOTIFY_INSTALL = '"pip install inotify"' |
59 MB_KEYS = (u"id", | 66 MB_KEYS = ( |
60 u"url", | 67 u"id", |
61 u"atom_id", | 68 u"url", |
62 u"updated", | 69 u"atom_id", |
63 u"published", | 70 u"updated", |
64 u"language", | 71 u"published", |
65 u"comments", # this key is used for all comments* keys | 72 u"language", |
66 u"tags", # this key is used for all tag* keys | 73 u"comments", # this key is used for all comments* keys |
67 u"author", | 74 u"tags", # this key is used for all tag* keys |
68 u"author_jid", | 75 u"author", |
69 u"author_email", | 76 u"author_jid", |
70 u"author_jid_verified", | 77 u"author_email", |
71 u"content", | 78 u"author_jid_verified", |
72 u"content_xhtml", | 79 u"content", |
73 u"title", | 80 u"content_xhtml", |
74 u"title_xhtml", | 81 u"title", |
75 ) | 82 u"title_xhtml", |
76 OUTPUT_OPT_NO_HEADER = u'no-header' | 83 ) |
84 OUTPUT_OPT_NO_HEADER = u"no-header" | |
77 | 85 |
78 | 86 |
79 def guessSyntaxFromPath(host, sat_conf, path): | 87 def guessSyntaxFromPath(host, sat_conf, path): |
80 """Return syntax guessed according to filename extension | 88 """Return syntax guessed according to filename extension |
81 | 89 |
82 @param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration | 90 @param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration |
83 @param path(str): path to the content file | 91 @param path(str): path to the content file |
84 @return(unicode): syntax to use | 92 @return(unicode): syntax to use |
85 """ | 93 """ |
86 # we first try to guess syntax with extension | 94 # we first try to guess syntax with extension |
87 ext = os.path.splitext(path)[1][1:] # we get extension without the '.' | 95 ext = os.path.splitext(path)[1][1:] # we get extension without the '.' |
88 if ext: | 96 if ext: |
89 for k,v in SYNTAX_EXT.iteritems(): | 97 for k, v in SYNTAX_EXT.iteritems(): |
90 if k and ext == v: | 98 if k and ext == v: |
91 return k | 99 return k |
92 | 100 |
93 # if not found, we use current syntax | 101 # if not found, we use current syntax |
94 return host.bridge.getParamA("Syntax", "Composition", "value", host.profile) | 102 return host.bridge.getParamA("Syntax", "Composition", "value", host.profile) |
96 | 104 |
97 class BlogPublishCommon(object): | 105 class BlogPublishCommon(object): |
98 """handle common option for publising commands (Set and Edit)""" | 106 """handle common option for publising commands (Set and Edit)""" |
99 | 107 |
100 def add_parser_options(self): | 108 def add_parser_options(self): |
101 self.parser.add_argument("-T", '--title', type=base.unicode_decoder, help=_(u"title of the item")) | 109 self.parser.add_argument( |
102 self.parser.add_argument("-t", '--tag', type=base.unicode_decoder, action='append', help=_(u"tag (category) of your item")) | 110 "-T", "--title", type=base.unicode_decoder, help=_(u"title of the item") |
103 self.parser.add_argument("-C", "--comments", action='store_true', help=_(u"enable comments")) | 111 ) |
104 self.parser.add_argument("-S", '--syntax', type=base.unicode_decoder, help=_(u"syntax to use (default: get profile's default syntax)")) | 112 self.parser.add_argument( |
113 "-t", | |
114 "--tag", | |
115 type=base.unicode_decoder, | |
116 action="append", | |
117 help=_(u"tag (category) of your item"), | |
118 ) | |
119 self.parser.add_argument( | |
120 "-C", "--comments", action="store_true", help=_(u"enable comments") | |
121 ) | |
122 self.parser.add_argument( | |
123 "-S", | |
124 "--syntax", | |
125 type=base.unicode_decoder, | |
126 help=_(u"syntax to use (default: get profile's default syntax)"), | |
127 ) | |
105 | 128 |
106 def setMbDataContent(self, content, mb_data): | 129 def setMbDataContent(self, content, mb_data): |
107 if self.args.syntax is None: | 130 if self.args.syntax is None: |
108 # default syntax has been used | 131 # default syntax has been used |
109 mb_data['content_rich'] = content | 132 mb_data["content_rich"] = content |
110 elif self.current_syntax == SYNTAX_XHTML: | 133 elif self.current_syntax == SYNTAX_XHTML: |
111 mb_data['content_xhtml'] = content | 134 mb_data["content_xhtml"] = content |
112 else: | 135 else: |
113 mb_data['content_xhtml'] = self.host.bridge.syntaxConvert(content, self.current_syntax, SYNTAX_XHTML, False, self.profile) | 136 mb_data["content_xhtml"] = self.host.bridge.syntaxConvert( |
137 content, self.current_syntax, SYNTAX_XHTML, False, self.profile | |
138 ) | |
114 | 139 |
115 def setMbDataFromArgs(self, mb_data): | 140 def setMbDataFromArgs(self, mb_data): |
116 """set microblog metadata according to command line options | 141 """set microblog metadata according to command line options |
117 | 142 |
118 if metadata already exist, it will be overwritten | 143 if metadata already exist, it will be overwritten |
119 """ | 144 """ |
120 mb_data['allow_comments'] = C.boolConst(self.args.comments) | 145 mb_data["allow_comments"] = C.boolConst(self.args.comments) |
121 if self.args.tag: | 146 if self.args.tag: |
122 data_format.iter2dict('tag', self.args.tag, mb_data, check_conflict=False) | 147 data_format.iter2dict("tag", self.args.tag, mb_data, check_conflict=False) |
123 if self.args.title is not None: | 148 if self.args.title is not None: |
124 mb_data['title'] = self.args.title | 149 mb_data["title"] = self.args.title |
125 | 150 |
126 | 151 |
127 class Set(base.CommandBase, BlogPublishCommon): | 152 class Set(base.CommandBase, BlogPublishCommon): |
128 | |
129 def __init__(self, host): | 153 def __init__(self, host): |
130 base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.SINGLE_ITEM}, | 154 base.CommandBase.__init__( |
131 help=_(u'publish a new blog item or update an existing one')) | 155 self, |
156 host, | |
157 "set", | |
158 use_pubsub=True, | |
159 pubsub_flags={C.SINGLE_ITEM}, | |
160 help=_(u"publish a new blog item or update an existing one"), | |
161 ) | |
132 BlogPublishCommon.__init__(self) | 162 BlogPublishCommon.__init__(self) |
133 self.need_loop=True | 163 self.need_loop = True |
134 | 164 |
135 def add_parser_options(self): | 165 def add_parser_options(self): |
136 BlogPublishCommon.add_parser_options(self) | 166 BlogPublishCommon.add_parser_options(self) |
137 | 167 |
138 def mbSendCb(self): | 168 def mbSendCb(self): |
141 | 171 |
142 def start(self): | 172 def start(self): |
143 self.pubsub_item = self.args.item | 173 self.pubsub_item = self.args.item |
144 mb_data = {} | 174 mb_data = {} |
145 self.setMbDataFromArgs(mb_data) | 175 self.setMbDataFromArgs(mb_data) |
146 content = codecs.getreader('utf-8')(sys.stdin).read() | 176 content = codecs.getreader("utf-8")(sys.stdin).read() |
147 self.setMbDataContent(content, mb_data) | 177 self.setMbDataContent(content, mb_data) |
148 | 178 |
149 self.host.bridge.mbSend( | 179 self.host.bridge.mbSend( |
150 self.args.service, | 180 self.args.service, |
151 self.args.node, | 181 self.args.node, |
152 mb_data, | 182 mb_data, |
153 self.profile, | 183 self.profile, |
154 callback=self.exitCb, | 184 callback=self.exitCb, |
155 errback=partial(self.errback, | 185 errback=partial( |
156 msg=_(u"can't send item: {}"), | 186 self.errback, |
157 exit_code=C.EXIT_BRIDGE_ERRBACK)) | 187 msg=_(u"can't send item: {}"), |
188 exit_code=C.EXIT_BRIDGE_ERRBACK, | |
189 ), | |
190 ) | |
158 | 191 |
159 | 192 |
160 class Get(base.CommandBase): | 193 class Get(base.CommandBase): |
161 TEMPLATE = u"blog/articles.html" | 194 TEMPLATE = u"blog/articles.html" |
162 | 195 |
163 def __init__(self, host): | 196 def __init__(self, host): |
164 extra_outputs = {'default': self.default_output, | 197 extra_outputs = {"default": self.default_output, "fancy": self.fancy_output} |
165 'fancy': self.fancy_output} | 198 base.CommandBase.__init__( |
166 base.CommandBase.__init__(self, host, 'get', use_verbose=True, use_pubsub=True, pubsub_flags={C.MULTI_ITEMS}, | 199 self, |
167 use_output=C.OUTPUT_COMPLEX, extra_outputs=extra_outputs, help=_(u'get blog item(s)')) | 200 host, |
168 self.need_loop=True | 201 "get", |
202 use_verbose=True, | |
203 use_pubsub=True, | |
204 pubsub_flags={C.MULTI_ITEMS}, | |
205 use_output=C.OUTPUT_COMPLEX, | |
206 extra_outputs=extra_outputs, | |
207 help=_(u"get blog item(s)"), | |
208 ) | |
209 self.need_loop = True | |
169 | 210 |
170 def add_parser_options(self): | 211 def add_parser_options(self): |
171 # TODO: a key(s) argument to select keys to display | 212 # TODO: a key(s) argument to select keys to display |
172 self.parser.add_argument("-k", "--key", type=base.unicode_decoder, action='append', dest='keys', | 213 self.parser.add_argument( |
173 help=_(u"microblog data key(s) to display (default: depend of verbosity)")) | 214 "-k", |
215 "--key", | |
216 type=base.unicode_decoder, | |
217 action="append", | |
218 dest="keys", | |
219 help=_(u"microblog data key(s) to display (default: depend of verbosity)"), | |
220 ) | |
174 # TODO: add MAM filters | 221 # TODO: add MAM filters |
175 | 222 |
176 def template_data_mapping(self, data): | 223 def template_data_mapping(self, data): |
177 return {u'items': data_objects.BlogItems(data)} | 224 return {u"items": data_objects.BlogItems(data)} |
178 | 225 |
179 def format_comments(self, item, keys): | 226 def format_comments(self, item, keys): |
180 comments_data = data_format.dict2iterdict(u'comments', item, (u'node', u'service'), pop=True) | 227 comments_data = data_format.dict2iterdict( |
228 u"comments", item, (u"node", u"service"), pop=True | |
229 ) | |
181 lines = [] | 230 lines = [] |
182 for data in comments_data: | 231 for data in comments_data: |
183 lines.append(data[u'comments']) | 232 lines.append(data[u"comments"]) |
184 for k in (u'node', u'service'): | 233 for k in (u"node", u"service"): |
185 if OUTPUT_OPT_NO_HEADER in self.args.output_opts: | 234 if OUTPUT_OPT_NO_HEADER in self.args.output_opts: |
186 header = u'' | 235 header = u"" |
187 else: | 236 else: |
188 header = C.A_HEADER + k + u': ' + A.RESET | 237 header = C.A_HEADER + k + u": " + A.RESET |
189 lines.append(header + data[k]) | 238 lines.append(header + data[k]) |
190 return u'\n'.join(lines) | 239 return u"\n".join(lines) |
191 | 240 |
192 def format_tags(self, item, keys): | 241 def format_tags(self, item, keys): |
193 tags = data_format.dict2iter('tag', item, pop=True) | 242 tags = data_format.dict2iter("tag", item, pop=True) |
194 return u', '.join(tags) | 243 return u", ".join(tags) |
195 | 244 |
196 def format_updated(self, item, keys): | 245 def format_updated(self, item, keys): |
197 return self.format_time(item['updated']) | 246 return self.format_time(item["updated"]) |
198 | 247 |
199 def format_published(self, item, keys): | 248 def format_published(self, item, keys): |
200 return self.format_time(item['published']) | 249 return self.format_time(item["published"]) |
201 | 250 |
202 def format_url(self, item, keys): | 251 def format_url(self, item, keys): |
203 return uri.buildXMPPUri(u'pubsub', | 252 return uri.buildXMPPUri( |
204 subtype=u'microblog', | 253 u"pubsub", |
205 path=self.metadata[u'service'], | 254 subtype=u"microblog", |
206 node=self.metadata[u'node'], | 255 path=self.metadata[u"service"], |
207 item=item[u'id']) | 256 node=self.metadata[u"node"], |
257 item=item[u"id"], | |
258 ) | |
208 | 259 |
209 def get_keys(self): | 260 def get_keys(self): |
210 """return keys to display according to verbosity or explicit key request""" | 261 """return keys to display according to verbosity or explicit key request""" |
211 verbosity = self.args.verbose | 262 verbosity = self.args.verbose |
212 if self.args.keys: | 263 if self.args.keys: |
213 if not set(MB_KEYS).issuperset(self.args.keys): | 264 if not set(MB_KEYS).issuperset(self.args.keys): |
214 self.disp(u"following keys are invalid: {invalid}.\n" | 265 self.disp( |
215 u"Valid keys are: {valid}.".format( | 266 u"following keys are invalid: {invalid}.\n" |
216 invalid = u', '.join(set(self.args.keys).difference(MB_KEYS)), | 267 u"Valid keys are: {valid}.".format( |
217 valid = u', '.join(sorted(MB_KEYS))), | 268 invalid=u", ".join(set(self.args.keys).difference(MB_KEYS)), |
218 error=True) | 269 valid=u", ".join(sorted(MB_KEYS)), |
270 ), | |
271 error=True, | |
272 ) | |
219 self.host.quit(C.EXIT_BAD_ARG) | 273 self.host.quit(C.EXIT_BAD_ARG) |
220 return self.args.keys | 274 return self.args.keys |
221 else: | 275 else: |
222 if verbosity == 0: | 276 if verbosity == 0: |
223 return (u'title', u'content') | 277 return (u"title", u"content") |
224 elif verbosity == 1: | 278 elif verbosity == 1: |
225 return (u"title", u"tags", u"author", u"author_jid", u"author_email", u"author_jid_verified", u"published", u"updated", u"content") | 279 return ( |
280 u"title", | |
281 u"tags", | |
282 u"author", | |
283 u"author_jid", | |
284 u"author_email", | |
285 u"author_jid_verified", | |
286 u"published", | |
287 u"updated", | |
288 u"content", | |
289 ) | |
226 else: | 290 else: |
227 return MB_KEYS | 291 return MB_KEYS |
228 | 292 |
229 def default_output(self, data): | 293 def default_output(self, data): |
230 """simple key/value output""" | 294 """simple key/value output""" |
231 items, self.metadata = data | 295 items, self.metadata = data |
232 keys = self.get_keys() | 296 keys = self.get_keys() |
233 | 297 |
234 # k_cb use format_[key] methods for complex formattings | 298 # k_cb use format_[key] methods for complex formattings |
235 k_cb = {} | 299 k_cb = {} |
236 for k in keys: | 300 for k in keys: |
237 try: | 301 try: |
238 callback = getattr(self, "format_" + k) | 302 callback = getattr(self, "format_" + k) |
239 except AttributeError: | 303 except AttributeError: |
243 for idx, item in enumerate(items): | 307 for idx, item in enumerate(items): |
244 for k in keys: | 308 for k in keys: |
245 if k not in item and k not in k_cb: | 309 if k not in item and k not in k_cb: |
246 continue | 310 continue |
247 if OUTPUT_OPT_NO_HEADER in self.args.output_opts: | 311 if OUTPUT_OPT_NO_HEADER in self.args.output_opts: |
248 header = '' | 312 header = "" |
249 else: | 313 else: |
250 header = u"{k_fmt}{key}:{k_fmt_e} {sep}".format( | 314 header = u"{k_fmt}{key}:{k_fmt_e} {sep}".format( |
251 k_fmt = C.A_HEADER, | 315 k_fmt=C.A_HEADER, |
252 key = k, | 316 key=k, |
253 k_fmt_e = A.RESET, | 317 k_fmt_e=A.RESET, |
254 sep = u'\n' if 'content' in k else u'') | 318 sep=u"\n" if "content" in k else u"", |
319 ) | |
255 value = k_cb[k](item, keys) if k in k_cb else item[k] | 320 value = k_cb[k](item, keys) if k in k_cb else item[k] |
256 self.disp(header + value) | 321 self.disp(header + value) |
257 # we want a separation line after each item but the last one | 322 # we want a separation line after each item but the last one |
258 if idx < len(items)-1: | 323 if idx < len(items) - 1: |
259 print(u'') | 324 print(u"") |
260 | 325 |
261 def format_time(self, timestamp): | 326 def format_time(self, timestamp): |
262 """return formatted date for timestamp | 327 """return formatted date for timestamp |
263 | 328 |
264 @param timestamp(str,int,float): unix timestamp | 329 @param timestamp(str,int,float): unix timestamp |
271 """display blog is a nice to read way | 336 """display blog is a nice to read way |
272 | 337 |
273 this output doesn't use keys filter | 338 this output doesn't use keys filter |
274 """ | 339 """ |
275 # thanks to http://stackoverflow.com/a/943921 | 340 # thanks to http://stackoverflow.com/a/943921 |
276 rows, columns = map(int, os.popen('stty size', 'r').read().split()) | 341 rows, columns = map(int, os.popen("stty size", "r").read().split()) |
277 items, metadata = data | 342 items, metadata = data |
278 verbosity = self.args.verbose | 343 verbosity = self.args.verbose |
279 sep = A.color(A.FG_BLUE, columns * u'▬') | 344 sep = A.color(A.FG_BLUE, columns * u"▬") |
280 if items: | 345 if items: |
281 print(u'\n' + sep + '\n') | 346 print(u"\n" + sep + "\n") |
282 | 347 |
283 for idx, item in enumerate(items): | 348 for idx, item in enumerate(items): |
284 title = item.get(u'title') | 349 title = item.get(u"title") |
285 if verbosity > 0: | 350 if verbosity > 0: |
286 author = item[u'author'] | 351 author = item[u"author"] |
287 published, updated = item[u'published'], item.get('updated') | 352 published, updated = item[u"published"], item.get("updated") |
288 else: | 353 else: |
289 author = published = updated = None | 354 author = published = updated = None |
290 if verbosity > 1: | 355 if verbosity > 1: |
291 tags = list(data_format.dict2iter('tag', item, pop=True)) | 356 tags = list(data_format.dict2iter("tag", item, pop=True)) |
292 else: | 357 else: |
293 tags = None | 358 tags = None |
294 content = item.get(u'content') | 359 content = item.get(u"content") |
295 | 360 |
296 if title: | 361 if title: |
297 print(A.color(A.BOLD, A.FG_CYAN, item[u'title'])) | 362 print(A.color(A.BOLD, A.FG_CYAN, item[u"title"])) |
298 meta = [] | 363 meta = [] |
299 if author: | 364 if author: |
300 meta.append(A.color(A.FG_YELLOW, author)) | 365 meta.append(A.color(A.FG_YELLOW, author)) |
301 if published: | 366 if published: |
302 meta.append(A.color(A.FG_YELLOW, u'on ', self.format_time(published))) | 367 meta.append(A.color(A.FG_YELLOW, u"on ", self.format_time(published))) |
303 if updated != published: | 368 if updated != published: |
304 meta.append(A.color(A.FG_YELLOW, u'(updated on ', self.format_time(updated), u')')) | 369 meta.append( |
305 print(u' '.join(meta)) | 370 A.color(A.FG_YELLOW, u"(updated on ", self.format_time(updated), u")") |
371 ) | |
372 print(u" ".join(meta)) | |
306 if tags: | 373 if tags: |
307 print(A.color(A.FG_MAGENTA, u', '.join(tags))) | 374 print(A.color(A.FG_MAGENTA, u", ".join(tags))) |
308 if (title or tags) and content: | 375 if (title or tags) and content: |
309 print("") | 376 print("") |
310 if content: | 377 if content: |
311 self.disp(content) | 378 self.disp(content) |
312 | 379 |
313 print(u'\n' + sep + '\n') | 380 print(u"\n" + sep + "\n") |
314 | |
315 | 381 |
316 def mbGetCb(self, mb_result): | 382 def mbGetCb(self, mb_result): |
317 self.output(mb_result) | 383 self.output(mb_result) |
318 self.host.quit(C.EXIT_OK) | 384 self.host.quit(C.EXIT_OK) |
319 | 385 |
320 def mbGetEb(self, failure_): | 386 def mbGetEb(self, failure_): |
321 self.disp(u"can't get blog items: {reason}".format( | 387 self.disp(u"can't get blog items: {reason}".format(reason=failure_), error=True) |
322 reason=failure_), error=True) | |
323 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | 388 self.host.quit(C.EXIT_BRIDGE_ERRBACK) |
324 | 389 |
325 def start(self): | 390 def start(self): |
326 self.host.bridge.mbGet( | 391 self.host.bridge.mbGet( |
327 self.args.service, | 392 self.args.service, |
329 self.args.max, | 394 self.args.max, |
330 self.args.items, | 395 self.args.items, |
331 {}, | 396 {}, |
332 self.profile, | 397 self.profile, |
333 callback=self.mbGetCb, | 398 callback=self.mbGetCb, |
334 errback=self.mbGetEb) | 399 errback=self.mbGetEb, |
400 ) | |
335 | 401 |
336 | 402 |
337 class Edit(base.CommandBase, BlogPublishCommon, common.BaseEdit): | 403 class Edit(base.CommandBase, BlogPublishCommon, common.BaseEdit): |
338 | |
339 def __init__(self, host): | 404 def __init__(self, host): |
340 base.CommandBase.__init__(self, host, 'edit', use_pubsub=True, pubsub_flags={C.SINGLE_ITEM}, | 405 base.CommandBase.__init__( |
341 use_draft=True, use_verbose=True, help=_(u'edit an existing or new blog post')) | 406 self, |
407 host, | |
408 "edit", | |
409 use_pubsub=True, | |
410 pubsub_flags={C.SINGLE_ITEM}, | |
411 use_draft=True, | |
412 use_verbose=True, | |
413 help=_(u"edit an existing or new blog post"), | |
414 ) | |
342 BlogPublishCommon.__init__(self) | 415 BlogPublishCommon.__init__(self) |
343 common.BaseEdit.__init__(self, self.host, BLOG_TMP_DIR, use_metadata=True) | 416 common.BaseEdit.__init__(self, self.host, BLOG_TMP_DIR, use_metadata=True) |
344 | 417 |
345 @property | 418 @property |
346 def current_syntax(self): | 419 def current_syntax(self): |
347 if self._current_syntax is None: | 420 if self._current_syntax is None: |
348 self._current_syntax = self.host.bridge.getParamA("Syntax", "Composition", "value", self.profile) | 421 self._current_syntax = self.host.bridge.getParamA( |
422 "Syntax", "Composition", "value", self.profile | |
423 ) | |
349 return self._current_syntax | 424 return self._current_syntax |
350 | 425 |
351 def add_parser_options(self): | 426 def add_parser_options(self): |
352 BlogPublishCommon.add_parser_options(self) | 427 BlogPublishCommon.add_parser_options(self) |
353 self.parser.add_argument("-P", "--preview", action="store_true", help=_(u"launch a blog preview in parallel")) | 428 self.parser.add_argument( |
429 "-P", | |
430 "--preview", | |
431 action="store_true", | |
432 help=_(u"launch a blog preview in parallel"), | |
433 ) | |
354 | 434 |
355 def buildMetadataFile(self, content_file_path, mb_data=None): | 435 def buildMetadataFile(self, content_file_path, mb_data=None): |
356 """Build a metadata file using json | 436 """Build a metadata file using json |
357 | 437 |
358 The file is named after content_file_path, with extension replaced by _metadata.json | 438 The file is named after content_file_path, with extension replaced by _metadata.json |
365 # or re-use the existing one if it exists | 445 # or re-use the existing one if it exists |
366 meta_file_path = os.path.splitext(content_file_path)[0] + common.METADATA_SUFF | 446 meta_file_path = os.path.splitext(content_file_path)[0] + common.METADATA_SUFF |
367 if os.path.exists(meta_file_path): | 447 if os.path.exists(meta_file_path): |
368 self.disp(u"Metadata file already exists, we re-use it") | 448 self.disp(u"Metadata file already exists, we re-use it") |
369 try: | 449 try: |
370 with open(meta_file_path, 'rb') as f: | 450 with open(meta_file_path, "rb") as f: |
371 mb_data = json.load(f) | 451 mb_data = json.load(f) |
372 except (OSError, IOError, ValueError) as e: | 452 except (OSError, IOError, ValueError) as e: |
373 self.disp(u"Can't read existing metadata file at {path}, aborting: {reason}".format( | 453 self.disp( |
374 path=meta_file_path, reason=e), error=True) | 454 u"Can't read existing metadata file at {path}, aborting: {reason}".format( |
455 path=meta_file_path, reason=e | |
456 ), | |
457 error=True, | |
458 ) | |
375 self.host.quit(1) | 459 self.host.quit(1) |
376 else: | 460 else: |
377 mb_data = {} if mb_data is None else mb_data.copy() | 461 mb_data = {} if mb_data is None else mb_data.copy() |
378 | 462 |
379 # in all cases, we want to remove unwanted keys | 463 # in all cases, we want to remove unwanted keys |
385 # and override metadata with command-line arguments | 469 # and override metadata with command-line arguments |
386 self.setMbDataFromArgs(mb_data) | 470 self.setMbDataFromArgs(mb_data) |
387 | 471 |
388 # then we create the file and write metadata there, as JSON dict | 472 # then we create the file and write metadata there, as JSON dict |
389 # XXX: if we port jp one day on Windows, O_BINARY may need to be added here | 473 # XXX: if we port jp one day on Windows, O_BINARY may need to be added here |
390 with os.fdopen(os.open(meta_file_path, os.O_RDWR | os.O_CREAT | os.O_TRUNC,0o600), 'w+b') as f: | 474 with os.fdopen( |
475 os.open(meta_file_path, os.O_RDWR | os.O_CREAT | os.O_TRUNC, 0o600), "w+b" | |
476 ) as f: | |
391 # we need to use an intermediate unicode buffer to write to the file unicode without escaping characters | 477 # we need to use an intermediate unicode buffer to write to the file unicode without escaping characters |
392 unicode_dump = json.dumps(mb_data, ensure_ascii=False, indent=4, separators=(',', ': '), sort_keys=True) | 478 unicode_dump = json.dumps( |
393 f.write(unicode_dump.encode('utf-8')) | 479 mb_data, |
480 ensure_ascii=False, | |
481 indent=4, | |
482 separators=(",", ": "), | |
483 sort_keys=True, | |
484 ) | |
485 f.write(unicode_dump.encode("utf-8")) | |
394 | 486 |
395 return mb_data, meta_file_path | 487 return mb_data, meta_file_path |
396 | 488 |
397 def edit(self, content_file_path, content_file_obj, | 489 def edit(self, content_file_path, content_file_obj, mb_data=None): |
398 mb_data=None): | |
399 """Edit the file contening the content using editor, and publish it""" | 490 """Edit the file contening the content using editor, and publish it""" |
400 # we first create metadata file | 491 # we first create metadata file |
401 meta_ori, meta_file_path = self.buildMetadataFile(content_file_path, mb_data) | 492 meta_ori, meta_file_path = self.buildMetadataFile(content_file_path, mb_data) |
402 | 493 |
403 # do we need a preview ? | 494 # do we need a preview ? |
404 if self.args.preview: | 495 if self.args.preview: |
405 self.disp(u"Preview requested, launching it", 1) | 496 self.disp(u"Preview requested, launching it", 1) |
406 # we redirect outputs to /dev/null to avoid console pollution in editor | 497 # we redirect outputs to /dev/null to avoid console pollution in editor |
407 # if user wants to see messages, (s)he can call "blog preview" directly | 498 # if user wants to see messages, (s)he can call "blog preview" directly |
408 DEVNULL = open(os.devnull, 'wb') | 499 DEVNULL = open(os.devnull, "wb") |
409 subprocess.Popen([sys.argv[0], "blog", "preview", "--inotify", "true", "-p", self.profile, content_file_path], stdout=DEVNULL, stderr=subprocess.STDOUT) | 500 subprocess.Popen( |
501 [ | |
502 sys.argv[0], | |
503 "blog", | |
504 "preview", | |
505 "--inotify", | |
506 "true", | |
507 "-p", | |
508 self.profile, | |
509 content_file_path, | |
510 ], | |
511 stdout=DEVNULL, | |
512 stderr=subprocess.STDOUT, | |
513 ) | |
410 | 514 |
411 # we launch editor | 515 # we launch editor |
412 self.runEditor("blog_editor_args", content_file_path, content_file_obj, meta_file_path=meta_file_path, meta_ori=meta_ori) | 516 self.runEditor( |
517 "blog_editor_args", | |
518 content_file_path, | |
519 content_file_obj, | |
520 meta_file_path=meta_file_path, | |
521 meta_ori=meta_ori, | |
522 ) | |
413 | 523 |
414 def publish(self, content, mb_data): | 524 def publish(self, content, mb_data): |
415 self.setMbDataContent(content, mb_data) | 525 self.setMbDataContent(content, mb_data) |
416 | 526 |
417 if self.pubsub_item is not None: | 527 if self.pubsub_item is not None: |
418 mb_data['id'] = self.pubsub_item | 528 mb_data["id"] = self.pubsub_item |
419 | 529 |
420 self.host.bridge.mbSend(self.pubsub_service, self.pubsub_node, mb_data, self.profile) | 530 self.host.bridge.mbSend( |
531 self.pubsub_service, self.pubsub_node, mb_data, self.profile | |
532 ) | |
421 self.disp(u"Blog item published") | 533 self.disp(u"Blog item published") |
422 | |
423 | 534 |
424 def getTmpSuff(self): | 535 def getTmpSuff(self): |
425 # we get current syntax to determine file extension | 536 # we get current syntax to determine file extension |
426 return SYNTAX_EXT.get(self.current_syntax, SYNTAX_EXT['']) | 537 return SYNTAX_EXT.get(self.current_syntax, SYNTAX_EXT[""]) |
427 | 538 |
428 def getItemData(self, service, node, item): | 539 def getItemData(self, service, node, item): |
429 items = [item] if item is not None else [] | 540 items = [item] if item is not None else [] |
430 mb_data = self.host.bridge.mbGet(service, node, 1, items, {}, self.profile)[0][0] | 541 mb_data = self.host.bridge.mbGet(service, node, 1, items, {}, self.profile)[0][0] |
431 try: | 542 try: |
432 content = mb_data['content_xhtml'] | 543 content = mb_data["content_xhtml"] |
433 except KeyError: | 544 except KeyError: |
434 content = mb_data['content'] | 545 content = mb_data["content"] |
435 if content: | 546 if content: |
436 content = self.host.bridge.syntaxConvert(content, 'text', SYNTAX_XHTML, False, self.profile) | 547 content = self.host.bridge.syntaxConvert( |
548 content, "text", SYNTAX_XHTML, False, self.profile | |
549 ) | |
437 if content and self.current_syntax != SYNTAX_XHTML: | 550 if content and self.current_syntax != SYNTAX_XHTML: |
438 content = self.host.bridge.syntaxConvert(content, SYNTAX_XHTML, self.current_syntax, False, self.profile) | 551 content = self.host.bridge.syntaxConvert( |
552 content, SYNTAX_XHTML, self.current_syntax, False, self.profile | |
553 ) | |
439 if content and self.current_syntax == SYNTAX_XHTML: | 554 if content and self.current_syntax == SYNTAX_XHTML: |
440 try: | 555 try: |
441 from lxml import etree | 556 from lxml import etree |
442 except ImportError: | 557 except ImportError: |
443 self.disp(_(u"You need lxml to edit pretty XHTML")) | 558 self.disp(_(u"You need lxml to edit pretty XHTML")) |
444 else: | 559 else: |
445 parser = etree.XMLParser(remove_blank_text=True) | 560 parser = etree.XMLParser(remove_blank_text=True) |
446 root = etree.fromstring(content, parser) | 561 root = etree.fromstring(content, parser) |
447 content = etree.tostring(root, encoding=unicode, pretty_print=True) | 562 content = etree.tostring(root, encoding=unicode, pretty_print=True) |
448 | 563 |
449 return content, mb_data, mb_data['id'] | 564 return content, mb_data, mb_data["id"] |
450 | 565 |
451 def start(self): | 566 def start(self): |
452 # if there are user defined extension, we use them | 567 # if there are user defined extension, we use them |
453 SYNTAX_EXT.update(config.getConfig(self.sat_conf, 'jp', CONF_SYNTAX_EXT, {})) | 568 SYNTAX_EXT.update(config.getConfig(self.sat_conf, "jp", CONF_SYNTAX_EXT, {})) |
454 self._current_syntax = self.args.syntax | 569 self._current_syntax = self.args.syntax |
455 if self._current_syntax is not None: | 570 if self._current_syntax is not None: |
456 try: | 571 try: |
457 self._current_syntax = self.args.syntax = self.host.bridge.syntaxGet(self.current_syntax) | 572 self._current_syntax = self.args.syntax = self.host.bridge.syntaxGet( |
573 self.current_syntax | |
574 ) | |
458 except Exception as e: | 575 except Exception as e: |
459 if "NotFound" in unicode(e): # FIXME: there is not good way to check bridge errors | 576 if "NotFound" in unicode( |
460 self.parser.error(_(u"unknown syntax requested ({syntax})").format(syntax=self.args.syntax)) | 577 e |
578 ): # FIXME: there is not good way to check bridge errors | |
579 self.parser.error( | |
580 _(u"unknown syntax requested ({syntax})").format( | |
581 syntax=self.args.syntax | |
582 ) | |
583 ) | |
461 else: | 584 else: |
462 raise e | 585 raise e |
463 | 586 |
464 self.pubsub_service, self.pubsub_node, self.pubsub_item, content_file_path, content_file_obj, mb_data = self.getItemPath() | 587 ( |
588 self.pubsub_service, | |
589 self.pubsub_node, | |
590 self.pubsub_item, | |
591 content_file_path, | |
592 content_file_obj, | |
593 mb_data, | |
594 ) = self.getItemPath() | |
465 | 595 |
466 self.edit(content_file_path, content_file_obj, mb_data=mb_data) | 596 self.edit(content_file_path, content_file_obj, mb_data=mb_data) |
467 | 597 |
468 | 598 |
469 class Preview(base.CommandBase): | 599 class Preview(base.CommandBase): |
470 # TODO: need to be rewritten with template output | 600 # TODO: need to be rewritten with template output |
471 | 601 |
472 def __init__(self, host): | 602 def __init__(self, host): |
473 base.CommandBase.__init__(self, host, 'preview', use_verbose=True, help=_(u'preview a blog content')) | 603 base.CommandBase.__init__( |
604 self, host, "preview", use_verbose=True, help=_(u"preview a blog content") | |
605 ) | |
474 | 606 |
475 def add_parser_options(self): | 607 def add_parser_options(self): |
476 self.parser.add_argument("--inotify", type=str, choices=('auto', 'true', 'false'), default=u'auto', help=_(u"use inotify to handle preview")) | 608 self.parser.add_argument( |
477 self.parser.add_argument("file", type=base.unicode_decoder, nargs='?', default=u'current', help=_(u"path to the content file")) | 609 "--inotify", |
610 type=str, | |
611 choices=("auto", "true", "false"), | |
612 default=u"auto", | |
613 help=_(u"use inotify to handle preview"), | |
614 ) | |
615 self.parser.add_argument( | |
616 "file", | |
617 type=base.unicode_decoder, | |
618 nargs="?", | |
619 default=u"current", | |
620 help=_(u"path to the content file"), | |
621 ) | |
478 | 622 |
479 def showPreview(self): | 623 def showPreview(self): |
480 # we implement showPreview here so we don't have to import webbrowser and urllib | 624 # we implement showPreview here so we don't have to import webbrowser and urllib |
481 # when preview is not used | 625 # when preview is not used |
482 url = 'file:{}'.format(self.urllib.quote(self.preview_file_path)) | 626 url = "file:{}".format(self.urllib.quote(self.preview_file_path)) |
483 self.webbrowser.open_new_tab(url) | 627 self.webbrowser.open_new_tab(url) |
484 | 628 |
485 def _launchPreviewExt(self, cmd_line, opt_name): | 629 def _launchPreviewExt(self, cmd_line, opt_name): |
486 url = 'file:{}'.format(self.urllib.quote(self.preview_file_path)) | 630 url = "file:{}".format(self.urllib.quote(self.preview_file_path)) |
487 args = common.parse_args(self.host, cmd_line, url=url, preview_file=self.preview_file_path) | 631 args = common.parse_args( |
632 self.host, cmd_line, url=url, preview_file=self.preview_file_path | |
633 ) | |
488 if not args: | 634 if not args: |
489 self.disp(u"Couln't find command in \"{name}\", abording".format(name=opt_name), error=True) | 635 self.disp( |
636 u'Couln\'t find command in "{name}", abording'.format(name=opt_name), | |
637 error=True, | |
638 ) | |
490 self.host.quit(1) | 639 self.host.quit(1) |
491 subprocess.Popen(args) | 640 subprocess.Popen(args) |
492 | 641 |
493 def openPreviewExt(self): | 642 def openPreviewExt(self): |
494 self._launchPreviewExt(self.open_cb_cmd, "blog_preview_open_cmd") | 643 self._launchPreviewExt(self.open_cb_cmd, "blog_preview_open_cmd") |
495 | 644 |
496 def updatePreviewExt(self): | 645 def updatePreviewExt(self): |
497 self._launchPreviewExt(self.update_cb_cmd, "blog_preview_update_cmd") | 646 self._launchPreviewExt(self.update_cb_cmd, "blog_preview_update_cmd") |
498 | 647 |
499 def updateContent(self): | 648 def updateContent(self): |
500 with open(self.content_file_path, 'rb') as f: | 649 with open(self.content_file_path, "rb") as f: |
501 content = f.read().decode('utf-8-sig') | 650 content = f.read().decode("utf-8-sig") |
502 if content and self.syntax != SYNTAX_XHTML: | 651 if content and self.syntax != SYNTAX_XHTML: |
503 # we use safe=True because we want to have a preview as close as possible to what the | 652 # we use safe=True because we want to have a preview as close as possible |
504 # people will see | 653 # to what the people will see |
505 content = self.host.bridge.syntaxConvert(content, self.syntax, SYNTAX_XHTML, True, self.profile) | 654 content = self.host.bridge.syntaxConvert( |
506 | 655 content, self.syntax, SYNTAX_XHTML, True, self.profile |
507 xhtml = (u'<html xmlns="http://www.w3.org/1999/xhtml">' + | 656 ) |
508 u'<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /></head>'+ | 657 |
509 '<body>{}</body>' + | 658 xhtml = ( |
510 u'</html>').format(content) | 659 u'<html xmlns="http://www.w3.org/1999/xhtml">' |
511 | 660 u'<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" />' |
512 with open(self.preview_file_path, 'wb') as f: | 661 u"</head>" |
513 f.write(xhtml.encode('utf-8')) | 662 u"<body>{}</body>" |
663 u"</html>" | |
664 ).format(content) | |
665 | |
666 with open(self.preview_file_path, "wb") as f: | |
667 f.write(xhtml.encode("utf-8")) | |
514 | 668 |
515 def start(self): | 669 def start(self): |
516 import webbrowser | 670 import webbrowser |
517 import urllib | 671 import urllib |
672 | |
518 self.webbrowser, self.urllib = webbrowser, urllib | 673 self.webbrowser, self.urllib = webbrowser, urllib |
519 | 674 |
520 if self.args.inotify != 'false': | 675 if self.args.inotify != "false": |
521 try: | 676 try: |
522 import inotify.adapters | 677 import inotify.adapters |
523 import inotify.constants | 678 import inotify.constants |
524 from inotify.calls import InotifyError | 679 from inotify.calls import InotifyError |
525 except ImportError: | 680 except ImportError: |
526 if self.args.inotify == 'auto': | 681 if self.args.inotify == "auto": |
527 inotify = None | 682 inotify = None |
528 self.disp(u'inotify module not found, deactivating feature. You can install it with {install}'.format(install=INOTIFY_INSTALL)) | 683 self.disp( |
684 u"inotify module not found, deactivating feature. You can install" | |
685 u" it with {install}".format(install=INOTIFY_INSTALL) | |
686 ) | |
529 else: | 687 else: |
530 self.disp(u"inotify not found, can't activate the feature! Please install it with {install}".format(install=INOTIFY_INSTALL), error=True) | 688 self.disp( |
689 u"inotify not found, can't activate the feature! Please install " | |
690 u"it with {install}".format(install=INOTIFY_INSTALL), | |
691 error=True, | |
692 ) | |
531 self.host.quit(1) | 693 self.host.quit(1) |
532 else: | 694 else: |
533 # we deactivate logging in inotify, which is quite annoying | 695 # we deactivate logging in inotify, which is quite annoying |
534 try: | 696 try: |
535 inotify.adapters._LOGGER.setLevel(40) | 697 inotify.adapters._LOGGER.setLevel(40) |
536 except AttributeError: | 698 except AttributeError: |
537 self.disp(u"Logger doesn't exists, inotify may have chanded", error=True) | 699 self.disp( |
700 u"Logger doesn't exists, inotify may have chanded", error=True | |
701 ) | |
538 else: | 702 else: |
539 inotify=None | 703 inotify = None |
540 | 704 |
541 sat_conf = config.parseMainConf() | 705 sat_conf = config.parseMainConf() |
542 SYNTAX_EXT.update(config.getConfig(sat_conf, 'jp', CONF_SYNTAX_EXT, {})) | 706 SYNTAX_EXT.update(config.getConfig(sat_conf, "jp", CONF_SYNTAX_EXT, {})) |
543 | 707 |
544 try: | 708 try: |
545 self.open_cb_cmd = config.getConfig(sat_conf, 'jp', "blog_preview_open_cmd", Exception) | 709 self.open_cb_cmd = config.getConfig( |
710 sat_conf, "jp", "blog_preview_open_cmd", Exception | |
711 ) | |
546 except (NoOptionError, NoSectionError): | 712 except (NoOptionError, NoSectionError): |
547 self.open_cb_cmd = None | 713 self.open_cb_cmd = None |
548 open_cb = self.showPreview | 714 open_cb = self.showPreview |
549 else: | 715 else: |
550 open_cb = self.openPreviewExt | 716 open_cb = self.openPreviewExt |
551 | 717 |
552 self.update_cb_cmd = config.getConfig(sat_conf, 'jp', "blog_preview_update_cmd", self.open_cb_cmd) | 718 self.update_cb_cmd = config.getConfig( |
719 sat_conf, "jp", "blog_preview_update_cmd", self.open_cb_cmd | |
720 ) | |
553 if self.update_cb_cmd is None: | 721 if self.update_cb_cmd is None: |
554 update_cb = self.showPreview | 722 update_cb = self.showPreview |
555 else: | 723 else: |
556 update_cb = self.updatePreviewExt | 724 update_cb = self.updatePreviewExt |
557 | 725 |
558 # which file do we need to edit? | 726 # which file do we need to edit? |
559 if self.args.file == 'current': | 727 if self.args.file == "current": |
560 self.content_file_path = self.getCurrentFile(sat_conf, self.profile) | 728 self.content_file_path = self.getCurrentFile(sat_conf, self.profile) |
561 else: | 729 else: |
562 self.content_file_path = os.path.abspath(self.args.file) | 730 self.content_file_path = os.path.abspath(self.args.file) |
563 | 731 |
564 self.syntax = guessSyntaxFromPath(self.host, sat_conf, self.content_file_path) | 732 self.syntax = guessSyntaxFromPath(self.host, sat_conf, self.content_file_path) |
565 | 733 |
566 | |
567 # at this point the syntax is converted, we can display the preview | 734 # at this point the syntax is converted, we can display the preview |
568 preview_file = tempfile.NamedTemporaryFile(suffix='.xhtml', delete=False) | 735 preview_file = tempfile.NamedTemporaryFile(suffix=".xhtml", delete=False) |
569 self.preview_file_path = preview_file.name | 736 self.preview_file_path = preview_file.name |
570 preview_file.close() | 737 preview_file.close() |
571 self.updateContent() | 738 self.updateContent() |
572 | 739 |
573 if inotify is None: | 740 if inotify is None: |
574 # XXX: we don't delete file automatically because browser need it (and webbrowser.open can return before it is read) | 741 # XXX: we don't delete file automatically because browser need it |
575 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)) | 742 # (and webbrowser.open can return before it is read) |
743 self.disp( | |
744 u"temporary file created at {}\nthis file will NOT BE DELETED " | |
745 u"AUTOMATICALLY, please delete it yourself when you have finished".format( | |
746 self.preview_file_path | |
747 ) | |
748 ) | |
576 open_cb() | 749 open_cb() |
577 else: | 750 else: |
578 open_cb() | 751 open_cb() |
579 i = inotify.adapters.Inotify(block_duration_s=60) # no need for 1 s duraction, inotify drive actions here | 752 i = inotify.adapters.Inotify( |
753 block_duration_s=60 | |
754 ) # no need for 1 s duraction, inotify drive actions here | |
580 | 755 |
581 def add_watch(): | 756 def add_watch(): |
582 i.add_watch(self.content_file_path, mask=inotify.constants.IN_CLOSE_WRITE | | 757 i.add_watch( |
583 inotify.constants.IN_DELETE_SELF | | 758 self.content_file_path, |
584 inotify.constants.IN_MOVE_SELF) | 759 mask=inotify.constants.IN_CLOSE_WRITE |
760 | inotify.constants.IN_DELETE_SELF | |
761 | inotify.constants.IN_MOVE_SELF, | |
762 ) | |
763 | |
585 add_watch() | 764 add_watch() |
586 | 765 |
587 try: | 766 try: |
588 for event in i.event_gen(): | 767 for event in i.event_gen(): |
589 if event is not None: | 768 if event is not None: |
590 self.disp(u"Content updated", 1) | 769 self.disp(u"Content updated", 1) |
591 if {"IN_DELETE_SELF", "IN_MOVE_SELF"}.intersection(event[1]): | 770 if {"IN_DELETE_SELF", "IN_MOVE_SELF"}.intersection(event[1]): |
592 self.disp(u"{} event catched, changing the watch".format(", ".join(event[1])), 2) | 771 self.disp( |
772 u"{} event catched, changing the watch".format( | |
773 ", ".join(event[1]) | |
774 ), | |
775 2, | |
776 ) | |
593 try: | 777 try: |
594 add_watch() | 778 add_watch() |
595 except InotifyError: | 779 except InotifyError: |
596 # if the new file is not here yet we can have an error | 780 # if the new file is not here yet we can have an error |
597 # as a workaround, we do a little rest | 781 # as a workaround, we do a little rest |
598 time.sleep(1) | 782 time.sleep(1) |
599 add_watch() | 783 add_watch() |
600 self.updateContent() | 784 self.updateContent() |
601 update_cb() | 785 update_cb() |
602 except InotifyError: | 786 except InotifyError: |
603 self.disp(u"Can't catch inotify events, as the file been deleted?", error=True) | 787 self.disp( |
788 u"Can't catch inotify events, as the file been deleted?", error=True | |
789 ) | |
604 finally: | 790 finally: |
605 os.unlink(self.preview_file_path) | 791 os.unlink(self.preview_file_path) |
606 try: | 792 try: |
607 i.remove_watch(self.content_file_path) | 793 i.remove_watch(self.content_file_path) |
608 except InotifyError: | 794 except InotifyError: |
609 pass | 795 pass |
610 | 796 |
611 | 797 |
612 class Import(base.CommandAnswering): | 798 class Import(base.CommandAnswering): |
613 def __init__(self, host): | 799 def __init__(self, host): |
614 super(Import, self).__init__(host, 'import', use_pubsub=True, use_progress=True, help=_(u'import an external blog')) | 800 super(Import, self).__init__( |
615 self.need_loop=True | 801 host, |
802 "import", | |
803 use_pubsub=True, | |
804 use_progress=True, | |
805 help=_(u"import an external blog"), | |
806 ) | |
807 self.need_loop = True | |
616 | 808 |
617 def add_parser_options(self): | 809 def add_parser_options(self): |
618 self.parser.add_argument("importer", type=base.unicode_decoder, nargs='?', help=_(u"importer name, nothing to display importers list")) | 810 self.parser.add_argument( |
619 self.parser.add_argument('--host', type=base.unicode_decoder, help=_(u"original blog host")) | 811 "importer", |
620 self.parser.add_argument('--no-images-upload', action='store_true', help=_(u"do *NOT* upload images (default: do upload images)")) | 812 type=base.unicode_decoder, |
621 self.parser.add_argument('--upload-ignore-host', help=_(u"do not upload images from this host (default: upload all images)")) | 813 nargs="?", |
622 self.parser.add_argument("--ignore-tls-errors", action="store_true", help=_("ignore invalide TLS certificate for uploads")) | 814 help=_(u"importer name, nothing to display importers list"), |
623 self.parser.add_argument('-o', '--option', action='append', nargs=2, default=[], metavar=(u'NAME', u'VALUE'), | 815 ) |
624 help=_(u"importer specific options (see importer description)")) | 816 self.parser.add_argument( |
625 self.parser.add_argument("location", type=base.unicode_decoder, nargs='?', | 817 "--host", type=base.unicode_decoder, help=_(u"original blog host") |
626 help=_(u"importer data location (see importer description), nothing to show importer description")) | 818 ) |
819 self.parser.add_argument( | |
820 "--no-images-upload", | |
821 action="store_true", | |
822 help=_(u"do *NOT* upload images (default: do upload images)"), | |
823 ) | |
824 self.parser.add_argument( | |
825 "--upload-ignore-host", | |
826 help=_(u"do not upload images from this host (default: upload all images)"), | |
827 ) | |
828 self.parser.add_argument( | |
829 "--ignore-tls-errors", | |
830 action="store_true", | |
831 help=_("ignore invalide TLS certificate for uploads"), | |
832 ) | |
833 self.parser.add_argument( | |
834 "-o", | |
835 "--option", | |
836 action="append", | |
837 nargs=2, | |
838 default=[], | |
839 metavar=(u"NAME", u"VALUE"), | |
840 help=_(u"importer specific options (see importer description)"), | |
841 ) | |
842 self.parser.add_argument( | |
843 "location", | |
844 type=base.unicode_decoder, | |
845 nargs="?", | |
846 help=_( | |
847 u"importer data location (see importer description), nothing to show " | |
848 u"importer description" | |
849 ), | |
850 ) | |
627 | 851 |
628 def onProgressStarted(self, metadata): | 852 def onProgressStarted(self, metadata): |
629 self.disp(_(u'Blog upload started'),2) | 853 self.disp(_(u"Blog upload started"), 2) |
630 | 854 |
631 def onProgressFinished(self, metadata): | 855 def onProgressFinished(self, metadata): |
632 self.disp(_(u'Blog uploaded successfully'),2) | 856 self.disp(_(u"Blog uploaded successfully"), 2) |
633 redirections = {k[len(URL_REDIRECT_PREFIX):]:v for k,v in metadata.iteritems() | 857 redirections = { |
634 if k.startswith(URL_REDIRECT_PREFIX)} | 858 k[len(URL_REDIRECT_PREFIX) :]: v |
859 for k, v in metadata.iteritems() | |
860 if k.startswith(URL_REDIRECT_PREFIX) | |
861 } | |
635 if redirections: | 862 if redirections: |
636 conf = u'\n'.join([ | 863 conf = u"\n".join( |
637 u'url_redirections_profile = {}'.format(self.profile), | 864 [ |
638 u"url_redirections_dict = {}".format( | 865 u"url_redirections_profile = {}".format(self.profile), |
639 # we need to add ' ' before each new line and to double each '%' for ConfigParser | 866 u"url_redirections_dict = {}".format( |
640 u'\n '.join(json.dumps(redirections, indent=1, separators=(',',': ')).replace(u'%', u'%%').split(u'\n'))), | 867 # we need to add ' ' before each new line |
641 ]) | 868 # and to double each '%' for ConfigParser |
642 self.disp(_(u'\nTo redirect old URLs to new ones, put the following lines in your sat.conf file, in [libervia] section:\n\n{conf}'.format(conf=conf))) | 869 u"\n ".join( |
870 json.dumps(redirections, indent=1, separators=(",", ": ")) | |
871 .replace(u"%", u"%%") | |
872 .split(u"\n") | |
873 ) | |
874 ), | |
875 ] | |
876 ) | |
877 self.disp( | |
878 _( | |
879 u"\nTo redirect old URLs to new ones, put the following lines in your" | |
880 u" sat.conf file, in [libervia] section:\n\n{conf}".format(conf=conf) | |
881 ) | |
882 ) | |
643 | 883 |
644 def onProgressError(self, error_msg): | 884 def onProgressError(self, error_msg): |
645 self.disp(_(u'Error while uploading blog: {}').format(error_msg),error=True) | 885 self.disp(_(u"Error while uploading blog: {}").format(error_msg), error=True) |
646 | 886 |
647 def error(self, failure): | 887 def error(self, failure): |
648 self.disp(_("Error while trying to upload a blog: {reason}").format(reason=failure), error=True) | 888 self.disp( |
889 _("Error while trying to upload a blog: {reason}").format(reason=failure), | |
890 error=True, | |
891 ) | |
649 self.host.quit(1) | 892 self.host.quit(1) |
650 | 893 |
651 def start(self): | 894 def start(self): |
652 if self.args.location is None: | 895 if self.args.location is None: |
653 for name in ('option', 'service', 'no_images_upload'): | 896 for name in ("option", "service", "no_images_upload"): |
654 if getattr(self.args, name): | 897 if getattr(self.args, name): |
655 self.parser.error(_(u"{name} argument can't be used without location argument").format(name=name)) | 898 self.parser.error( |
899 _( | |
900 u"{name} argument can't be used without location argument" | |
901 ).format(name=name) | |
902 ) | |
656 if self.args.importer is None: | 903 if self.args.importer is None: |
657 self.disp(u'\n'.join([u'{}: {}'.format(name, desc) for name, desc in self.host.bridge.blogImportList()])) | 904 self.disp( |
905 u"\n".join( | |
906 [ | |
907 u"{}: {}".format(name, desc) | |
908 for name, desc in self.host.bridge.blogImportList() | |
909 ] | |
910 ) | |
911 ) | |
658 else: | 912 else: |
659 try: | 913 try: |
660 short_desc, long_desc = self.host.bridge.blogImportDesc(self.args.importer) | 914 short_desc, long_desc = self.host.bridge.blogImportDesc( |
915 self.args.importer | |
916 ) | |
661 except Exception as e: | 917 except Exception as e: |
662 msg = [l for l in unicode(e).split('\n') if l][-1] # we only keep the last line | 918 msg = [l for l in unicode(e).split("\n") if l][ |
919 -1 | |
920 ] # we only keep the last line | |
663 self.disp(msg) | 921 self.disp(msg) |
664 self.host.quit(1) | 922 self.host.quit(1) |
665 else: | 923 else: |
666 self.disp(u"{name}: {short_desc}\n\n{long_desc}".format(name=self.args.importer, short_desc=short_desc, long_desc=long_desc)) | 924 self.disp( |
925 u"{name}: {short_desc}\n\n{long_desc}".format( | |
926 name=self.args.importer, | |
927 short_desc=short_desc, | |
928 long_desc=long_desc, | |
929 ) | |
930 ) | |
667 self.host.quit() | 931 self.host.quit() |
668 else: | 932 else: |
669 # we have a location, an import is requested | 933 # we have a location, an import is requested |
670 options = {key: value for key, value in self.args.option} | 934 options = {key: value for key, value in self.args.option} |
671 if self.args.host: | 935 if self.args.host: |
672 options['host'] = self.args.host | 936 options["host"] = self.args.host |
673 if self.args.ignore_tls_errors: | 937 if self.args.ignore_tls_errors: |
674 options['ignore_tls_errors'] = C.BOOL_TRUE | 938 options["ignore_tls_errors"] = C.BOOL_TRUE |
675 if self.args.no_images_upload: | 939 if self.args.no_images_upload: |
676 options['upload_images'] = C.BOOL_FALSE | 940 options["upload_images"] = C.BOOL_FALSE |
677 if self.args.upload_ignore_host: | 941 if self.args.upload_ignore_host: |
678 self.parser.error(u"upload-ignore-host option can't be used when no-images-upload is set") | 942 self.parser.error( |
943 u"upload-ignore-host option can't be used when no-images-upload " | |
944 u"is set" | |
945 ) | |
679 elif self.args.upload_ignore_host: | 946 elif self.args.upload_ignore_host: |
680 options['upload_ignore_host'] = self.args.upload_ignore_host | 947 options["upload_ignore_host"] = self.args.upload_ignore_host |
948 | |
681 def gotId(id_): | 949 def gotId(id_): |
682 self.progress_id = id_ | 950 self.progress_id = id_ |
683 self.host.bridge.blogImport(self.args.importer, self.args.location, options, self.args.service, self.args.node, self.profile, | 951 |
684 callback=gotId, errback=self.error) | 952 self.host.bridge.blogImport( |
953 self.args.importer, | |
954 self.args.location, | |
955 options, | |
956 self.args.service, | |
957 self.args.node, | |
958 self.profile, | |
959 callback=gotId, | |
960 errback=self.error, | |
961 ) | |
685 | 962 |
686 | 963 |
687 class Blog(base.CommandBase): | 964 class Blog(base.CommandBase): |
688 subcommands = (Set, Get, Edit, Preview, Import) | 965 subcommands = (Set, Get, Edit, Preview, Import) |
689 | 966 |
690 def __init__(self, host): | 967 def __init__(self, host): |
691 super(Blog, self).__init__(host, 'blog', use_profile=False, help=_('blog/microblog management')) | 968 super(Blog, self).__init__( |
969 host, "blog", use_profile=False, help=_("blog/microblog management") | |
970 ) |