Mercurial > libervia-backend
comparison sat_frontends/jp/base.py @ 3028:ab2696e34d29
Python 3 port:
/!\ this is a huge commit
/!\ starting from this commit, SàT is needs Python 3.6+
/!\ SàT maybe be instable or some feature may not work anymore, this will improve with time
This patch port backend, bridge and frontends to Python 3.
Roughly this has been done this way:
- 2to3 tools has been applied (with python 3.7)
- all references to python2 have been replaced with python3 (notably shebangs)
- fixed files not handled by 2to3 (notably the shell script)
- several manual fixes
- fixed issues reported by Python 3 that where not handled in Python 2
- replaced "async" with "async_" when needed (it's a reserved word from Python 3.7)
- replaced zope's "implements" with @implementer decorator
- temporary hack to handle data pickled in database, as str or bytes may be returned,
to be checked later
- fixed hash comparison for password
- removed some code which is not needed anymore with Python 3
- deactivated some code which needs to be checked (notably certificate validation)
- tested with jp, fixed reported issues until some basic commands worked
- ported Primitivus (after porting dependencies like urwid satext)
- more manual fixes
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 13 Aug 2019 19:08:41 +0200 |
parents | b2f323237fce |
children | fee60f17ebac |
comparison
equal
deleted
inserted
replaced
3027:ff5bcb12ae60 | 3028:ab2696e34d29 |
---|---|
76 @param *args: args of the callbac | 76 @param *args: args of the callbac |
77 """ | 77 """ |
78 GLib.timeout_add(delay, callback, *args) | 78 GLib.timeout_add(delay, callback, *args) |
79 | 79 |
80 else: | 80 else: |
81 print u"can't start jp: only D-Bus bridge is currently handled" | 81 print("can't start jp: only D-Bus bridge is currently handled") |
82 sys.exit(C.EXIT_ERROR) | 82 sys.exit(C.EXIT_ERROR) |
83 # FIXME: twisted loop can be used when jp can handle fully async bridges | 83 # FIXME: twisted loop can be used when jp can handle fully async bridges |
84 # from twisted.internet import reactor | 84 # from twisted.internet import reactor |
85 | 85 |
86 # class JPLoop(object): | 86 # class JPLoop(object): |
102 | 102 |
103 if bridge_name == "embedded": | 103 if bridge_name == "embedded": |
104 from sat.core import sat_main | 104 from sat.core import sat_main |
105 sat = sat_main.SAT() | 105 sat = sat_main.SAT() |
106 | 106 |
107 if sys.version_info < (2, 7, 3): | |
108 # XXX: shlex.split only handle unicode since python 2.7.3 | |
109 # this is a workaround for older versions | |
110 old_split = shlex.split | |
111 new_split = (lambda s, *a, **kw: [t.decode('utf-8') for t in old_split(s.encode('utf-8'), *a, **kw)] | |
112 if isinstance(s, unicode) else old_split(s, *a, **kw)) | |
113 shlex.split = new_split | |
114 | |
115 try: | 107 try: |
116 import progressbar | 108 import progressbar |
117 except ImportError: | 109 except ImportError: |
118 msg = (_(u'ProgressBar not available, please download it at http://pypi.python.org/pypi/progressbar\n') + | 110 msg = (_('ProgressBar not available, please download it at http://pypi.python.org/pypi/progressbar\n') + |
119 _(u'Progress bar deactivated\n--\n')) | 111 _('Progress bar deactivated\n--\n')) |
120 print >>sys.stderr,msg.encode('utf-8') | 112 print(msg.encode('utf-8'), file=sys.stderr) |
121 progressbar=None | 113 progressbar=None |
122 | 114 |
123 #consts | 115 #consts |
124 PROG_NAME = u"jp" | 116 PROG_NAME = "jp" |
125 DESCRIPTION = """This software is a command line tool for XMPP. | 117 DESCRIPTION = """This software is a command line tool for XMPP. |
126 Get the latest version at """ + C.APP_URL | 118 Get the latest version at """ + C.APP_URL |
127 | 119 |
128 COPYLEFT = u"""Copyright (C) 2009-2019 Jérôme Poisson, Adrien Cossa | 120 COPYLEFT = """Copyright (C) 2009-2019 Jérôme Poisson, Adrien Cossa |
129 This program comes with ABSOLUTELY NO WARRANTY; | 121 This program comes with ABSOLUTELY NO WARRANTY; |
130 This is free software, and you are welcome to redistribute it under certain conditions. | 122 This is free software, and you are welcome to redistribute it under certain conditions. |
131 """ | 123 """ |
132 | 124 |
133 PROGRESS_DELAY = 10 # the progression will be checked every PROGRESS_DELAY ms | 125 PROGRESS_DELAY = 10 # the progression will be checked every PROGRESS_DELAY ms |
134 | |
135 | |
136 def unicode_decoder(arg): | |
137 # Needed to have unicode strings from arguments | |
138 return arg.decode(locale.getpreferredencoding()) | |
139 | 126 |
140 | 127 |
141 def date_decoder(arg): | 128 def date_decoder(arg): |
142 return date_utils.date_parse_ext(arg, default_tz=date_utils.TZ_LOCAL) | 129 return date_utils.date_parse_ext(arg, default_tz=date_utils.TZ_LOCAL) |
143 | 130 |
165 """ | 152 """ |
166 # FIXME: need_loop should be removed, everything must be async in bridge so | 153 # FIXME: need_loop should be removed, everything must be async in bridge so |
167 # loop will always be needed | 154 # loop will always be needed |
168 bridge_module = dynamic_import.bridge(bridge_name, 'sat_frontends.bridge') | 155 bridge_module = dynamic_import.bridge(bridge_name, 'sat_frontends.bridge') |
169 if bridge_module is None: | 156 if bridge_module is None: |
170 log.error(u"Can't import {} bridge".format(bridge_name)) | 157 log.error("Can't import {} bridge".format(bridge_name)) |
171 sys.exit(1) | 158 sys.exit(1) |
172 | 159 |
173 self.bridge = bridge_module.Bridge() | 160 self.bridge = bridge_module.Bridge() |
174 self.bridge.bridgeConnect(callback=self._bridgeCb, errback=self._bridgeEb) | 161 self.bridge.bridgeConnect(callback=self._bridgeCb, errback=self._bridgeEb) |
175 | 162 |
176 def _bridgeCb(self): | 163 def _bridgeCb(self): |
177 self.parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, | 164 self.parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, |
178 description=DESCRIPTION) | 165 description=DESCRIPTION) |
179 self._make_parents() | 166 self._make_parents() |
180 self.add_parser_options() | 167 self.add_parser_options() |
181 self.subparsers = self.parser.add_subparsers(title=_(u'Available commands'), dest='subparser_name') | 168 self.subparsers = self.parser.add_subparsers(title=_('Available commands'), dest='subparser_name') |
182 self._auto_loop = False # when loop is used for internal reasons | 169 self._auto_loop = False # when loop is used for internal reasons |
183 self._need_loop = False | 170 self._need_loop = False |
184 | 171 |
185 # progress attributes | 172 # progress attributes |
186 self._progress_id = None # TODO: manage several progress ids | 173 self._progress_id = None # TODO: manage several progress ids |
194 | 181 |
195 self.own_jid = None # must be filled at runtime if needed | 182 self.own_jid = None # must be filled at runtime if needed |
196 | 183 |
197 def _bridgeEb(self, failure): | 184 def _bridgeEb(self, failure): |
198 if isinstance(failure, exceptions.BridgeExceptionNoService): | 185 if isinstance(failure, exceptions.BridgeExceptionNoService): |
199 print(_(u"Can't connect to SàT backend, are you sure it's launched ?")) | 186 print((_("Can't connect to SàT backend, are you sure it's launched ?"))) |
200 elif isinstance(failure, exceptions.BridgeInitError): | 187 elif isinstance(failure, exceptions.BridgeInitError): |
201 print(_(u"Can't init bridge")) | 188 print((_("Can't init bridge"))) |
202 else: | 189 else: |
203 print(_(u"Error while initialising bridge: {}".format(failure))) | 190 print((_("Error while initialising bridge: {}".format(failure)))) |
204 sys.exit(C.EXIT_BRIDGE_ERROR) | 191 sys.exit(C.EXIT_BRIDGE_ERROR) |
205 | 192 |
206 @property | 193 @property |
207 def version(self): | 194 def version(self): |
208 return self.bridge.getVersion() | 195 return self.bridge.getVersion() |
262 @param no_lf(bool): if True, do not emit line feed at the end of line | 249 @param no_lf(bool): if True, do not emit line feed at the end of line |
263 """ | 250 """ |
264 if self.verbosity >= verbosity: | 251 if self.verbosity >= verbosity: |
265 if error: | 252 if error: |
266 if no_lf: | 253 if no_lf: |
267 print >>sys.stderr,msg.encode('utf-8'), | 254 print(msg, end=' ', file=sys.stderr) |
268 else: | 255 else: |
269 print >>sys.stderr,msg.encode('utf-8') | 256 print(msg, file=sys.stderr) |
270 else: | 257 else: |
271 if no_lf: | 258 if no_lf: |
272 print msg.encode('utf-8'), | 259 print(msg, end=' ') |
273 else: | 260 else: |
274 print msg.encode('utf-8') | 261 print(msg) |
275 | 262 |
276 def output(self, type_, name, extra_outputs, data): | 263 def output(self, type_, name, extra_outputs, data): |
277 if name in extra_outputs: | 264 if name in extra_outputs: |
278 extra_outputs[name](data) | 265 extra_outputs[name](data) |
279 else: | 266 else: |
295 """Return valid output filters for output_type | 282 """Return valid output filters for output_type |
296 | 283 |
297 @param output_type: True for default, | 284 @param output_type: True for default, |
298 else can be any registered type | 285 else can be any registered type |
299 """ | 286 """ |
300 return self._outputs[output_type].keys() | 287 return list(self._outputs[output_type].keys()) |
301 | 288 |
302 def _make_parents(self): | 289 def _make_parents(self): |
303 self.parents = {} | 290 self.parents = {} |
304 | 291 |
305 # we have a special case here as the start-session option is present only if connection is not needed, | 292 # we have a special case here as the start-session option is present only if connection is not needed, |
306 # so we create two similar parents, one with the option, the other one without it | 293 # so we create two similar parents, one with the option, the other one without it |
307 for parent_name in ('profile', 'profile_session'): | 294 for parent_name in ('profile', 'profile_session'): |
308 parent = self.parents[parent_name] = argparse.ArgumentParser(add_help=False) | 295 parent = self.parents[parent_name] = argparse.ArgumentParser(add_help=False) |
309 parent.add_argument("-p", "--profile", action="store", type=str, default='@DEFAULT@', help=_("Use PROFILE profile key (default: %(default)s)")) | 296 parent.add_argument("-p", "--profile", action="store", type=str, default='@DEFAULT@', help=_("Use PROFILE profile key (default: %(default)s)")) |
310 parent.add_argument("--pwd", action="store", type=unicode_decoder, default='', metavar='PASSWORD', help=_("Password used to connect profile, if necessary")) | 297 parent.add_argument("--pwd", action="store", default='', metavar='PASSWORD', help=_("Password used to connect profile, if necessary")) |
311 | 298 |
312 profile_parent, profile_session_parent = self.parents['profile'], self.parents['profile_session'] | 299 profile_parent, profile_session_parent = self.parents['profile'], self.parents['profile_session'] |
313 | 300 |
314 connect_short, connect_long, connect_action, connect_help = "-c", "--connect", "store_true", _(u"Connect the profile before doing anything else") | 301 connect_short, connect_long, connect_action, connect_help = "-c", "--connect", "store_true", _("Connect the profile before doing anything else") |
315 profile_parent.add_argument(connect_short, connect_long, action=connect_action, help=connect_help) | 302 profile_parent.add_argument(connect_short, connect_long, action=connect_action, help=connect_help) |
316 | 303 |
317 profile_session_connect_group = profile_session_parent.add_mutually_exclusive_group() | 304 profile_session_connect_group = profile_session_parent.add_mutually_exclusive_group() |
318 profile_session_connect_group.add_argument(connect_short, connect_long, action=connect_action, help=connect_help) | 305 profile_session_connect_group.add_argument(connect_short, connect_long, action=connect_action, help=connect_help) |
319 profile_session_connect_group.add_argument("--start-session", action="store_true", help=_("Start a profile session without connecting")) | 306 profile_session_connect_group.add_argument("--start-session", action="store_true", help=_("Start a profile session without connecting")) |
321 progress_parent = self.parents['progress'] = argparse.ArgumentParser(add_help=False) | 308 progress_parent = self.parents['progress'] = argparse.ArgumentParser(add_help=False) |
322 if progressbar: | 309 if progressbar: |
323 progress_parent.add_argument("-P", "--progress", action="store_true", help=_("Show progress bar")) | 310 progress_parent.add_argument("-P", "--progress", action="store_true", help=_("Show progress bar")) |
324 | 311 |
325 verbose_parent = self.parents['verbose'] = argparse.ArgumentParser(add_help=False) | 312 verbose_parent = self.parents['verbose'] = argparse.ArgumentParser(add_help=False) |
326 verbose_parent.add_argument('--verbose', '-v', action='count', default=0, help=_(u"Add a verbosity level (can be used multiple times)")) | 313 verbose_parent.add_argument('--verbose', '-v', action='count', default=0, help=_("Add a verbosity level (can be used multiple times)")) |
327 | 314 |
328 draft_parent = self.parents['draft'] = argparse.ArgumentParser(add_help=False) | 315 draft_parent = self.parents['draft'] = argparse.ArgumentParser(add_help=False) |
329 draft_group = draft_parent.add_argument_group(_('draft handling')) | 316 draft_group = draft_parent.add_argument_group(_('draft handling')) |
330 draft_group.add_argument("-D", "--current", action="store_true", help=_(u"load current draft")) | 317 draft_group.add_argument("-D", "--current", action="store_true", help=_("load current draft")) |
331 draft_group.add_argument("-F", "--draft-path", type=unicode_decoder, help=_(u"path to a draft file to retrieve")) | 318 draft_group.add_argument("-F", "--draft-path", help=_("path to a draft file to retrieve")) |
332 | 319 |
333 | 320 |
334 def make_pubsub_group(self, flags, defaults): | 321 def make_pubsub_group(self, flags, defaults): |
335 """generate pubsub options according to flags | 322 """generate pubsub options according to flags |
336 | 323 |
341 @return (ArgumentParser): parser to add | 328 @return (ArgumentParser): parser to add |
342 """ | 329 """ |
343 flags = misc.FlagsHandler(flags) | 330 flags = misc.FlagsHandler(flags) |
344 parent = argparse.ArgumentParser(add_help=False) | 331 parent = argparse.ArgumentParser(add_help=False) |
345 pubsub_group = parent.add_argument_group('pubsub') | 332 pubsub_group = parent.add_argument_group('pubsub') |
346 pubsub_group.add_argument("-u", "--pubsub-url", type=unicode_decoder, | 333 pubsub_group.add_argument("-u", "--pubsub-url", |
347 help=_(u"Pubsub URL (xmpp or http)")) | 334 help=_("Pubsub URL (xmpp or http)")) |
348 | 335 |
349 service_help = _(u"JID of the PubSub service") | 336 service_help = _("JID of the PubSub service") |
350 if not flags.service: | 337 if not flags.service: |
351 default = defaults.pop(u'service', _(u'PEP service')) | 338 default = defaults.pop('service', _('PEP service')) |
352 if default is not None: | 339 if default is not None: |
353 service_help += _(u" (DEFAULT: {default})".format(default=default)) | 340 service_help += _(" (DEFAULT: {default})".format(default=default)) |
354 pubsub_group.add_argument("-s", "--service", type=unicode_decoder, default=u'', | 341 pubsub_group.add_argument("-s", "--service", default='', |
355 help=service_help) | 342 help=service_help) |
356 | 343 |
357 node_help = _(u"node to request") | 344 node_help = _("node to request") |
358 if not flags.node: | 345 if not flags.node: |
359 default = defaults.pop(u'node', _(u'standard node')) | 346 default = defaults.pop('node', _('standard node')) |
360 if default is not None: | 347 if default is not None: |
361 node_help += _(u" (DEFAULT: {default})".format(default=default)) | 348 node_help += _(" (DEFAULT: {default})".format(default=default)) |
362 pubsub_group.add_argument("-n", "--node", type=unicode_decoder, default=u'', help=node_help) | 349 pubsub_group.add_argument("-n", "--node", default='', help=node_help) |
363 | 350 |
364 if flags.single_item: | 351 if flags.single_item: |
365 item_help = (u"item to retrieve") | 352 item_help = ("item to retrieve") |
366 if not flags.item: | 353 if not flags.item: |
367 default = defaults.pop(u'item', _(u'last item')) | 354 default = defaults.pop('item', _('last item')) |
368 if default is not None: | 355 if default is not None: |
369 item_help += _(u" (DEFAULT: {default})".format(default=default)) | 356 item_help += _(" (DEFAULT: {default})".format(default=default)) |
370 pubsub_group.add_argument("-i", "--item", type=unicode_decoder, default=u'', | 357 pubsub_group.add_argument("-i", "--item", default='', |
371 help=item_help) | 358 help=item_help) |
372 pubsub_group.add_argument("-L", "--last-item", action='store_true', help=_(u'retrieve last item')) | 359 pubsub_group.add_argument("-L", "--last-item", action='store_true', help=_('retrieve last item')) |
373 elif flags.multi_items: | 360 elif flags.multi_items: |
374 # mutiple items, this activate several features: max-items, RSM, MAM | 361 # mutiple items, this activate several features: max-items, RSM, MAM |
375 # and Orbder-by | 362 # and Orbder-by |
376 pubsub_group.add_argument("-i", "--item", type=unicode_decoder, action='append', dest='items', default=[], help=_(u"items to retrieve (DEFAULT: all)")) | 363 pubsub_group.add_argument("-i", "--item", action='append', dest='items', default=[], help=_("items to retrieve (DEFAULT: all)")) |
377 if not flags.no_max: | 364 if not flags.no_max: |
378 max_group = pubsub_group.add_mutually_exclusive_group() | 365 max_group = pubsub_group.add_mutually_exclusive_group() |
379 # XXX: defaut value for --max-items or --max is set in parse_pubsub_args | 366 # XXX: defaut value for --max-items or --max is set in parse_pubsub_args |
380 max_group.add_argument( | 367 max_group.add_argument( |
381 "-M", "--max-items", dest="max", type=int, | 368 "-M", "--max-items", dest="max", type=int, |
382 help=_(u"maximum number of items to get ({no_limit} to get all items)" | 369 help=_("maximum number of items to get ({no_limit} to get all items)" |
383 .format(no_limit=C.NO_LIMIT))) | 370 .format(no_limit=C.NO_LIMIT))) |
384 # FIXME: it could be possible to no duplicate max (between pubsub | 371 # FIXME: it could be possible to no duplicate max (between pubsub |
385 # max-items and RSM max)should not be duplicated, RSM could be | 372 # max-items and RSM max)should not be duplicated, RSM could be |
386 # used when available and pubsub max otherwise | 373 # used when available and pubsub max otherwise |
387 max_group.add_argument( | 374 max_group.add_argument( |
388 "-m", "--max", dest="rsm_max", type=int, | 375 "-m", "--max", dest="rsm_max", type=int, |
389 help=_(u"maximum number of items to get per page (DEFAULT: 10)")) | 376 help=_("maximum number of items to get per page (DEFAULT: 10)")) |
390 | 377 |
391 # RSM | 378 # RSM |
392 | 379 |
393 rsm_page_group = pubsub_group.add_mutually_exclusive_group() | 380 rsm_page_group = pubsub_group.add_mutually_exclusive_group() |
394 rsm_page_group.add_argument( | 381 rsm_page_group.add_argument( |
395 "-a", "--after", dest="rsm_after", type=unicode_decoder, | 382 "-a", "--after", dest="rsm_after", |
396 help=_(u"find page after this item"), metavar='ITEM_ID') | 383 help=_("find page after this item"), metavar='ITEM_ID') |
397 rsm_page_group.add_argument( | 384 rsm_page_group.add_argument( |
398 "-b", "--before", dest="rsm_before", type=unicode_decoder, | 385 "-b", "--before", dest="rsm_before", |
399 help=_(u"find page before this item"), metavar='ITEM_ID') | 386 help=_("find page before this item"), metavar='ITEM_ID') |
400 rsm_page_group.add_argument( | 387 rsm_page_group.add_argument( |
401 "--index", dest="rsm_index", type=int, | 388 "--index", dest="rsm_index", type=int, |
402 help=_(u"index of the page to retrieve")) | 389 help=_("index of the page to retrieve")) |
403 | 390 |
404 | 391 |
405 # MAM | 392 # MAM |
406 | 393 |
407 pubsub_group.add_argument( | 394 pubsub_group.add_argument( |
408 "-f", "--filter", dest='mam_filters', type=unicode_decoder, nargs=2, | 395 "-f", "--filter", dest='mam_filters', nargs=2, |
409 action='append', default=[], help=_(u"MAM filters to use"), | 396 action='append', default=[], help=_("MAM filters to use"), |
410 metavar=(u"FILTER_NAME", u"VALUE") | 397 metavar=("FILTER_NAME", "VALUE") |
411 ) | 398 ) |
412 | 399 |
413 # Order-By | 400 # Order-By |
414 | 401 |
415 # TODO: order-by should be a list to handle several levels of ordering | 402 # TODO: order-by should be a list to handle several levels of ordering |
417 # current specifications, as only "creation" and "modification" are | 404 # current specifications, as only "creation" and "modification" are |
418 # available) | 405 # available) |
419 pubsub_group.add_argument( | 406 pubsub_group.add_argument( |
420 "-o", "--order-by", choices=[C.ORDER_BY_CREATION, | 407 "-o", "--order-by", choices=[C.ORDER_BY_CREATION, |
421 C.ORDER_BY_MODIFICATION], | 408 C.ORDER_BY_MODIFICATION], |
422 help=_(u"how items should be ordered")) | 409 help=_("how items should be ordered")) |
423 | 410 |
424 if not flags.all_used: | 411 if not flags.all_used: |
425 raise exceptions.InternalError('unknown flags: {flags}'.format(flags=u', '.join(flags.unused))) | 412 raise exceptions.InternalError('unknown flags: {flags}'.format(flags=', '.join(flags.unused))) |
426 if defaults: | 413 if defaults: |
427 raise exceptions.InternalError('unused defaults: {defaults}'.format(defaults=defaults)) | 414 raise exceptions.InternalError('unused defaults: {defaults}'.format(defaults=defaults)) |
428 | 415 |
429 return parent | 416 return parent |
430 | 417 |
431 def add_parser_options(self): | 418 def add_parser_options(self): |
432 self.parser.add_argument('--version', action='version', version=("%(name)s %(version)s %(copyleft)s" % {'name': PROG_NAME, 'version': self.version, 'copyleft': COPYLEFT})) | 419 self.parser.add_argument('--version', action='version', version=("%(name)s %(version)s %(copyleft)s" % {'name': PROG_NAME, 'version': self.version, 'copyleft': COPYLEFT})) |
433 | 420 |
434 def register_output(self, type_, name, callback, description="", default=False): | 421 def register_output(self, type_, name, callback, description="", default=False): |
435 if type_ not in C.OUTPUT_TYPES: | 422 if type_ not in C.OUTPUT_TYPES: |
436 log.error(u"Invalid output type {}".format(type_)) | 423 log.error("Invalid output type {}".format(type_)) |
437 return | 424 return |
438 self._outputs[type_][name] = {'callback': callback, | 425 self._outputs[type_][name] = {'callback': callback, |
439 'description': description | 426 'description': description |
440 } | 427 } |
441 if default: | 428 if default: |
442 if type_ in self.default_output: | 429 if type_ in self.default_output: |
443 self.disp(_(u'there is already a default output for {}, ignoring new one').format(type_)) | 430 self.disp(_('there is already a default output for {}, ignoring new one').format(type_)) |
444 else: | 431 else: |
445 self.default_output[type_] = name | 432 self.default_output[type_] = name |
446 | 433 |
447 | 434 |
448 def parse_output_options(self): | 435 def parse_output_options(self): |
449 options = self.command.args.output_opts | 436 options = self.command.args.output_opts |
450 options_dict = {} | 437 options_dict = {} |
451 for option in options: | 438 for option in options: |
452 try: | 439 try: |
453 key, value = option.split(u'=', 1) | 440 key, value = option.split('=', 1) |
454 except ValueError: | 441 except ValueError: |
455 key, value = option, None | 442 key, value = option, None |
456 options_dict[key.strip()] = value.strip() if value is not None else None | 443 options_dict[key.strip()] = value.strip() if value is not None else None |
457 return options_dict | 444 return options_dict |
458 | 445 |
459 def check_output_options(self, accepted_set, options): | 446 def check_output_options(self, accepted_set, options): |
460 if not accepted_set.issuperset(options): | 447 if not accepted_set.issuperset(options): |
461 self.disp(u"The following output options are invalid: {invalid_options}".format( | 448 self.disp("The following output options are invalid: {invalid_options}".format( |
462 invalid_options = u', '.join(set(options).difference(accepted_set))), | 449 invalid_options = ', '.join(set(options).difference(accepted_set))), |
463 error=True) | 450 error=True) |
464 self.quit(C.EXIT_BAD_ARG) | 451 self.quit(C.EXIT_BAD_ARG) |
465 | 452 |
466 def import_plugins(self): | 453 def import_plugins(self): |
467 """Automaticaly import commands and outputs in jp | 454 """Automaticaly import commands and outputs in jp |
476 module_path = "sat_frontends.jp." + module_name | 463 module_path = "sat_frontends.jp." + module_name |
477 try: | 464 try: |
478 module = import_module(module_path) | 465 module = import_module(module_path) |
479 self.import_plugin_module(module, type_) | 466 self.import_plugin_module(module, type_) |
480 except ImportError as e: | 467 except ImportError as e: |
481 self.disp(_(u"Can't import {module_path} plugin, ignoring it: {msg}".format( | 468 self.disp(_("Can't import {module_path} plugin, ignoring it: {msg}".format( |
482 module_path = module_path, | 469 module_path = module_path, |
483 msg = e)), error=True) | 470 msg = e)), error=True) |
484 except exceptions.CancelError: | 471 except exceptions.CancelError: |
485 continue | 472 continue |
486 except exceptions.MissingModule as e: | 473 except exceptions.MissingModule as e: |
487 self.disp(_(u"Missing module for plugin {name}: {missing}".format( | 474 self.disp(_("Missing module for plugin {name}: {missing}".format( |
488 name = module_path, | 475 name = module_path, |
489 missing = e)), error=True) | 476 missing = e)), error=True) |
490 | 477 |
491 | 478 |
492 def import_plugin_module(self, module, type_): | 479 def import_plugin_module(self, module, type_): |
496 @param type_(str): one of C_PLUGIN_* | 483 @param type_(str): one of C_PLUGIN_* |
497 """ | 484 """ |
498 try: | 485 try: |
499 class_names = getattr(module, '__{}__'.format(type_)) | 486 class_names = getattr(module, '__{}__'.format(type_)) |
500 except AttributeError: | 487 except AttributeError: |
501 log.disp(_(u"Invalid plugin module [{type}] {module}").format(type=type_, module=module), error=True) | 488 log.disp(_("Invalid plugin module [{type}] {module}").format(type=type_, module=module), error=True) |
502 raise ImportError | 489 raise ImportError |
503 else: | 490 else: |
504 for class_name in class_names: | 491 for class_name in class_names: |
505 cls = getattr(module, class_name) | 492 cls = getattr(module, class_name) |
506 cls(self) | 493 cls(self) |
507 | 494 |
508 def get_xmpp_uri_from_http(self, http_url): | 495 def get_xmpp_uri_from_http(self, http_url): |
509 """parse HTML page at http(s) URL, and looks for xmpp: uri""" | 496 """parse HTML page at http(s) URL, and looks for xmpp: uri""" |
510 if http_url.startswith('https'): | 497 if http_url.startswith('https'): |
511 scheme = u'https' | 498 scheme = 'https' |
512 elif http_url.startswith('http'): | 499 elif http_url.startswith('http'): |
513 scheme = u'http' | 500 scheme = 'http' |
514 else: | 501 else: |
515 raise exceptions.InternalError(u'An HTTP scheme is expected in this method') | 502 raise exceptions.InternalError('An HTTP scheme is expected in this method') |
516 self.disp(u"{scheme} URL found, trying to find associated xmpp: URI".format(scheme=scheme.upper()),1) | 503 self.disp("{scheme} URL found, trying to find associated xmpp: URI".format(scheme=scheme.upper()),1) |
517 # HTTP URL, we try to find xmpp: links | 504 # HTTP URL, we try to find xmpp: links |
518 try: | 505 try: |
519 from lxml import etree | 506 from lxml import etree |
520 except ImportError: | 507 except ImportError: |
521 self.disp(u"lxml module must be installed to use http(s) scheme, please install it with \"pip install lxml\"", error=True) | 508 self.disp("lxml module must be installed to use http(s) scheme, please install it with \"pip install lxml\"", error=True) |
522 self.quit(1) | 509 self.quit(1) |
523 import urllib2 | 510 import urllib.request, urllib.error, urllib.parse |
524 parser = etree.HTMLParser() | 511 parser = etree.HTMLParser() |
525 try: | 512 try: |
526 root = etree.parse(urllib2.urlopen(http_url), parser) | 513 root = etree.parse(urllib.request.urlopen(http_url), parser) |
527 except etree.XMLSyntaxError as e: | 514 except etree.XMLSyntaxError as e: |
528 self.disp(_(u"Can't parse HTML page : {msg}").format(msg=e)) | 515 self.disp(_("Can't parse HTML page : {msg}").format(msg=e)) |
529 links = [] | 516 links = [] |
530 else: | 517 else: |
531 links = root.xpath("//link[@rel='alternate' and starts-with(@href, 'xmpp:')]") | 518 links = root.xpath("//link[@rel='alternate' and starts-with(@href, 'xmpp:')]") |
532 if not links: | 519 if not links: |
533 self.disp(u'Could not find alternate "xmpp:" URI, can\'t find associated XMPP PubSub node/item', error=True) | 520 self.disp('Could not find alternate "xmpp:" URI, can\'t find associated XMPP PubSub node/item', error=True) |
534 self.quit(1) | 521 self.quit(1) |
535 xmpp_uri = links[0].get('href') | 522 xmpp_uri = links[0].get('href') |
536 return xmpp_uri | 523 return xmpp_uri |
537 | 524 |
538 def parse_pubsub_args(self): | 525 def parse_pubsub_args(self): |
544 url = self.get_xmpp_uri_from_http(url) | 531 url = self.get_xmpp_uri_from_http(url) |
545 | 532 |
546 try: | 533 try: |
547 uri_data = uri.parseXMPPUri(url) | 534 uri_data = uri.parseXMPPUri(url) |
548 except ValueError: | 535 except ValueError: |
549 self.parser.error(_(u'invalid XMPP URL: {url}').format(url=url)) | 536 self.parser.error(_('invalid XMPP URL: {url}').format(url=url)) |
550 else: | 537 else: |
551 if uri_data[u'type'] == 'pubsub': | 538 if uri_data['type'] == 'pubsub': |
552 # URL is alright, we only set data not already set by other options | 539 # URL is alright, we only set data not already set by other options |
553 if not self.args.service: | 540 if not self.args.service: |
554 self.args.service = uri_data[u'path'] | 541 self.args.service = uri_data['path'] |
555 if not self.args.node: | 542 if not self.args.node: |
556 self.args.node = uri_data[u'node'] | 543 self.args.node = uri_data['node'] |
557 uri_item = uri_data.get(u'item') | 544 uri_item = uri_data.get('item') |
558 if uri_item: | 545 if uri_item: |
559 # there is an item in URI | 546 # there is an item in URI |
560 # we use it only if item is not already set | 547 # we use it only if item is not already set |
561 # and item_last is not used either | 548 # and item_last is not used either |
562 try: | 549 try: |
563 item = self.args.item | 550 item = self.args.item |
564 except AttributeError: | 551 except AttributeError: |
565 try: | 552 try: |
566 items = self.args.items | 553 items = self.args.items |
567 except AttributeError: | 554 except AttributeError: |
568 self.disp(_(u"item specified in URL but not needed in command, ignoring it"), error=True) | 555 self.disp(_("item specified in URL but not needed in command, ignoring it"), error=True) |
569 else: | 556 else: |
570 if not items: | 557 if not items: |
571 self.args.items = [uri_item] | 558 self.args.items = [uri_item] |
572 else: | 559 else: |
573 if not item: | 560 if not item: |
576 except AttributeError: | 563 except AttributeError: |
577 item_last = False | 564 item_last = False |
578 if not item_last: | 565 if not item_last: |
579 self.args.item = uri_item | 566 self.args.item = uri_item |
580 else: | 567 else: |
581 self.parser.error(_(u'XMPP URL is not a pubsub one: {url}').format(url=url)) | 568 self.parser.error(_('XMPP URL is not a pubsub one: {url}').format(url=url)) |
582 flags = self.args._cmd._pubsub_flags | 569 flags = self.args._cmd._pubsub_flags |
583 # we check required arguments here instead of using add_arguments' required option | 570 # we check required arguments here instead of using add_arguments' required option |
584 # because the required argument can be set in URL | 571 # because the required argument can be set in URL |
585 if C.SERVICE in flags and not self.args.service: | 572 if C.SERVICE in flags and not self.args.service: |
586 self.parser.error(_(u"argument -s/--service is required")) | 573 self.parser.error(_("argument -s/--service is required")) |
587 if C.NODE in flags and not self.args.node: | 574 if C.NODE in flags and not self.args.node: |
588 self.parser.error(_(u"argument -n/--node is required")) | 575 self.parser.error(_("argument -n/--node is required")) |
589 if C.ITEM in flags and not self.args.item: | 576 if C.ITEM in flags and not self.args.item: |
590 self.parser.error(_(u"argument -i/--item is required")) | 577 self.parser.error(_("argument -i/--item is required")) |
591 | 578 |
592 # FIXME: mutually groups can't be nested in a group and don't support title | 579 # FIXME: mutually groups can't be nested in a group and don't support title |
593 # so we check conflict here. This may be fixed in Python 3, to be checked | 580 # so we check conflict here. This may be fixed in Python 3, to be checked |
594 try: | 581 try: |
595 if self.args.item and self.args.item_last: | 582 if self.args.item and self.args.item_last: |
596 self.parser.error(_(u"--item and --item-last can't be used at the same time")) | 583 self.parser.error(_("--item and --item-last can't be used at the same time")) |
597 except AttributeError: | 584 except AttributeError: |
598 pass | 585 pass |
599 | 586 |
600 try: | 587 try: |
601 max_items = self.args.max | 588 max_items = self.args.max |
637 try: | 624 try: |
638 self.loop.quit() | 625 self.loop.quit() |
639 except AttributeError: | 626 except AttributeError: |
640 pass | 627 pass |
641 | 628 |
642 def confirmOrQuit(self, message, cancel_message=_(u"action cancelled by user")): | 629 def confirmOrQuit(self, message, cancel_message=_("action cancelled by user")): |
643 """Request user to confirm action, and quit if he doesn't""" | 630 """Request user to confirm action, and quit if he doesn't""" |
644 | 631 |
645 res = raw_input("{} (y/N)? ".format(message)) | 632 res = input("{} (y/N)? ".format(message)) |
646 if res not in ("y", "Y"): | 633 if res not in ("y", "Y"): |
647 self.disp(cancel_message) | 634 self.disp(cancel_message) |
648 self.quit(C.EXIT_USER_CANCELLED) | 635 self.quit(C.EXIT_USER_CANCELLED) |
649 | 636 |
650 def quitFromSignal(self, errcode=0): | 637 def quitFromSignal(self, errcode=0): |
726 - 1 when there is a connection error | 713 - 1 when there is a connection error |
727 """ | 714 """ |
728 # FIXME: need better exit codes | 715 # FIXME: need better exit codes |
729 | 716 |
730 def cant_connect(failure): | 717 def cant_connect(failure): |
731 log.error(_(u"Can't connect profile: {reason}").format(reason=failure)) | 718 log.error(_("Can't connect profile: {reason}").format(reason=failure)) |
732 self.quit(1) | 719 self.quit(1) |
733 | 720 |
734 def cant_start_session(failure): | 721 def cant_start_session(failure): |
735 log.error(_(u"Can't start {profile}'s session: {reason}").format(profile=self.profile, reason=failure)) | 722 log.error(_("Can't start {profile}'s session: {reason}").format(profile=self.profile, reason=failure)) |
736 self.quit(1) | 723 self.quit(1) |
737 | 724 |
738 self.profile = self.bridge.profileNameGet(self.args.profile) | 725 self.profile = self.bridge.profileNameGet(self.args.profile) |
739 | 726 |
740 if not self.profile: | 727 if not self.profile: |
750 self.bridge.profileStartSession(self.args.pwd, self.profile, lambda __: callback(), cant_start_session) | 737 self.bridge.profileStartSession(self.args.pwd, self.profile, lambda __: callback(), cant_start_session) |
751 self._auto_loop = True | 738 self._auto_loop = True |
752 return | 739 return |
753 elif not self.bridge.profileIsSessionStarted(self.profile): | 740 elif not self.bridge.profileIsSessionStarted(self.profile): |
754 if not self.args.connect: | 741 if not self.args.connect: |
755 log.error(_(u"Session for [{profile}] is not started, please start it before using jp, or use either --start-session or --connect option").format(profile=self.profile)) | 742 log.error(_("Session for [{profile}] is not started, please start it before using jp, or use either --start-session or --connect option").format(profile=self.profile)) |
756 self.quit(1) | 743 self.quit(1) |
757 elif not getattr(self.args, "connect", False): | 744 elif not getattr(self.args, "connect", False): |
758 callback() | 745 callback() |
759 return | 746 return |
760 | 747 |
766 self.bridge.connect(self.profile, self.args.pwd, {}, lambda __: callback(), cant_connect) | 753 self.bridge.connect(self.profile, self.args.pwd, {}, lambda __: callback(), cant_connect) |
767 self._auto_loop = True | 754 self._auto_loop = True |
768 return | 755 return |
769 else: | 756 else: |
770 if not self.bridge.isConnected(self.profile): | 757 if not self.bridge.isConnected(self.profile): |
771 log.error(_(u"Profile [{profile}] is not connected, please connect it before using jp, or use --connect option").format(profile=self.profile)) | 758 log.error(_("Profile [{profile}] is not connected, please connect it before using jp, or use --connect option").format(profile=self.profile)) |
772 self.quit(1) | 759 self.quit(1) |
773 | 760 |
774 callback() | 761 callback() |
775 | 762 |
776 def get_full_jid(self, param_jid): | 763 def get_full_jid(self, param_jid): |
853 if not choices: | 840 if not choices: |
854 raise exceptions.InternalError("No choice found for {} output type".format(use_output)) | 841 raise exceptions.InternalError("No choice found for {} output type".format(use_output)) |
855 try: | 842 try: |
856 default = self.host.default_output[use_output] | 843 default = self.host.default_output[use_output] |
857 except KeyError: | 844 except KeyError: |
858 if u'default' in choices: | 845 if 'default' in choices: |
859 default = u'default' | 846 default = 'default' |
860 elif u'simple' in choices: | 847 elif 'simple' in choices: |
861 default = u'simple' | 848 default = 'simple' |
862 else: | 849 else: |
863 default = list(choices)[0] | 850 default = list(choices)[0] |
864 output_parent.add_argument('--output', '-O', choices=sorted(choices), default=default, help=_(u"select output format (default: {})".format(default))) | 851 output_parent.add_argument('--output', '-O', choices=sorted(choices), default=default, help=_("select output format (default: {})".format(default))) |
865 output_parent.add_argument('--output-option', '--oo', type=unicode_decoder, action="append", dest='output_opts', default=[], help=_(u"output specific option")) | 852 output_parent.add_argument('--output-option', '--oo', action="append", dest='output_opts', default=[], help=_("output specific option")) |
866 parents.add(output_parent) | 853 parents.add(output_parent) |
867 else: | 854 else: |
868 assert extra_outputs is None | 855 assert extra_outputs is None |
869 | 856 |
870 self._use_pubsub = kwargs.pop('use_pubsub', False) | 857 self._use_pubsub = kwargs.pop('use_pubsub', False) |
873 defaults = kwargs.pop('pubsub_defaults', {}) | 860 defaults = kwargs.pop('pubsub_defaults', {}) |
874 parents.add(self.host.make_pubsub_group(flags, defaults)) | 861 parents.add(self.host.make_pubsub_group(flags, defaults)) |
875 self._pubsub_flags = flags | 862 self._pubsub_flags = flags |
876 | 863 |
877 # other common options | 864 # other common options |
878 use_opts = {k:v for k,v in kwargs.iteritems() if k.startswith('use_')} | 865 use_opts = {k:v for k,v in kwargs.items() if k.startswith('use_')} |
879 for param, do_use in use_opts.iteritems(): | 866 for param, do_use in use_opts.items(): |
880 opt=param[4:] # if param is use_verbose, opt is verbose | 867 opt=param[4:] # if param is use_verbose, opt is verbose |
881 if opt not in self.host.parents: | 868 if opt not in self.host.parents: |
882 raise exceptions.InternalError(u"Unknown parent option {}".format(opt)) | 869 raise exceptions.InternalError("Unknown parent option {}".format(opt)) |
883 del kwargs[param] | 870 del kwargs[param] |
884 if do_use: | 871 if do_use: |
885 parents.add(self.host.parents[opt]) | 872 parents.add(self.host.parents[opt]) |
886 | 873 |
887 self.parser = host.subparsers.add_parser(name, help=help, **kwargs) | 874 self.parser = host.subparsers.add_parser(name, help=help, **kwargs) |
955 data = self.host.bridge.progressGet(self.progress_id, self.profile) | 942 data = self.host.bridge.progressGet(self.progress_id, self.profile) |
956 if data: | 943 if data: |
957 try: | 944 try: |
958 size = data['size'] | 945 size = data['size'] |
959 except KeyError: | 946 except KeyError: |
960 self.disp(_(u"file size is not known, we can't show a progress bar"), 1, error=True) | 947 self.disp(_("file size is not known, we can't show a progress bar"), 1, error=True) |
961 return False | 948 return False |
962 if self.host.pbar is None: | 949 if self.host.pbar is None: |
963 #first answer, we must construct the bar | 950 #first answer, we must construct the bar |
964 self.host.pbar = progressbar.ProgressBar(max_value=int(size), | 951 self.host.pbar = progressbar.ProgressBar(max_value=int(size), |
965 widgets=[_(u"Progress: "),progressbar.Percentage(), | 952 widgets=[_("Progress: "),progressbar.Percentage(), |
966 " ", | 953 " ", |
967 progressbar.Bar(), | 954 progressbar.Bar(), |
968 " ", | 955 " ", |
969 progressbar.FileTransferSpeed(), | 956 progressbar.FileTransferSpeed(), |
970 " ", | 957 " ", |
984 """Called when progress has just started | 971 """Called when progress has just started |
985 | 972 |
986 can be overidden by a command | 973 can be overidden by a command |
987 @param metadata(dict): metadata as sent by bridge.progressStarted | 974 @param metadata(dict): metadata as sent by bridge.progressStarted |
988 """ | 975 """ |
989 self.disp(_(u"Operation started"), 2) | 976 self.disp(_("Operation started"), 2) |
990 | 977 |
991 def onProgressUpdate(self, metadata): | 978 def onProgressUpdate(self, metadata): |
992 """Method called on each progress updata | 979 """Method called on each progress updata |
993 | 980 |
994 can be overidden by a command to handle progress metadata | 981 can be overidden by a command to handle progress metadata |
1000 """Called when progress has just finished | 987 """Called when progress has just finished |
1001 | 988 |
1002 can be overidden by a command | 989 can be overidden by a command |
1003 @param metadata(dict): metadata as sent by bridge.progressFinished | 990 @param metadata(dict): metadata as sent by bridge.progressFinished |
1004 """ | 991 """ |
1005 self.disp(_(u"Operation successfully finished"), 2) | 992 self.disp(_("Operation successfully finished"), 2) |
1006 | 993 |
1007 def onProgressError(self, error_msg): | 994 def onProgressError(self, error_msg): |
1008 """Called when a progress failed | 995 """Called when a progress failed |
1009 | 996 |
1010 @param error_msg(unicode): error message as sent by bridge.progressError | 997 @param error_msg(unicode): error message as sent by bridge.progressError |
1011 """ | 998 """ |
1012 self.disp(_(u"Error while doing operation: {}").format(error_msg), error=True) | 999 self.disp(_("Error while doing operation: {}").format(error_msg), error=True) |
1013 | 1000 |
1014 def disp(self, msg, verbosity=0, error=False, no_lf=False): | 1001 def disp(self, msg, verbosity=0, error=False, no_lf=False): |
1015 return self.host.disp(msg, verbosity, error, no_lf) | 1002 return self.host.disp(msg, verbosity, error, no_lf) |
1016 | 1003 |
1017 def output(self, data): | 1004 def output(self, data): |
1018 try: | 1005 try: |
1019 output_type = self._output_type | 1006 output_type = self._output_type |
1020 except AttributeError: | 1007 except AttributeError: |
1021 raise exceptions.InternalError(_(u'trying to use output when use_output has not been set')) | 1008 raise exceptions.InternalError(_('trying to use output when use_output has not been set')) |
1022 return self.host.output(output_type, self.args.output, self.extra_outputs, data) | 1009 return self.host.output(output_type, self.args.output, self.extra_outputs, data) |
1023 | 1010 |
1024 def exitCb(self, msg=None): | 1011 def exitCb(self, msg=None): |
1025 """generic callback for success | 1012 """generic callback for success |
1026 | 1013 |
1039 @param msg(unicode, None): message template | 1026 @param msg(unicode, None): message template |
1040 use {} if you want to display failure message | 1027 use {} if you want to display failure message |
1041 @param exit_code(int): shell exit code | 1028 @param exit_code(int): shell exit code |
1042 """ | 1029 """ |
1043 if msg is None: | 1030 if msg is None: |
1044 msg = _(u"error: {}") | 1031 msg = _("error: {}") |
1045 self.disp(msg.format(failure_), error=True) | 1032 self.disp(msg.format(failure_), error=True) |
1046 self.host.quit(exit_code) | 1033 self.host.quit(exit_code) |
1047 | 1034 |
1048 def getPubsubExtra(self, extra=None): | 1035 def getPubsubExtra(self, extra=None): |
1049 """Helper method to compute extra data from pubsub arguments | 1036 """Helper method to compute extra data from pubsub arguments |
1052 @return (dict): dict which can be used directly in the bridge for pubsub | 1039 @return (dict): dict which can be used directly in the bridge for pubsub |
1053 """ | 1040 """ |
1054 if extra is None: | 1041 if extra is None: |
1055 extra = {} | 1042 extra = {} |
1056 else: | 1043 else: |
1057 intersection = {C.KEY_ORDER_BY}.intersection(extra.keys()) | 1044 intersection = {C.KEY_ORDER_BY}.intersection(list(extra.keys())) |
1058 if intersection: | 1045 if intersection: |
1059 raise exceptions.ConflictError( | 1046 raise exceptions.ConflictError( |
1060 u"given extra dict has conflicting keys with pubsub keys " | 1047 "given extra dict has conflicting keys with pubsub keys " |
1061 u"{intersection}".format(intersection=intersection)) | 1048 "{intersection}".format(intersection=intersection)) |
1062 | 1049 |
1063 # RSM | 1050 # RSM |
1064 | 1051 |
1065 for attribute in (u'max', u'after', u'before', 'index'): | 1052 for attribute in ('max', 'after', 'before', 'index'): |
1066 key = u'rsm_' + attribute | 1053 key = 'rsm_' + attribute |
1067 if key in extra: | 1054 if key in extra: |
1068 raise exceptions.ConflictError( | 1055 raise exceptions.ConflictError( |
1069 u"This key already exists in extra: u{key}".format(key=key)) | 1056 "This key already exists in extra: u{key}".format(key=key)) |
1070 value = getattr(self.args, key, None) | 1057 value = getattr(self.args, key, None) |
1071 if value is not None: | 1058 if value is not None: |
1072 extra[key] = unicode(value) | 1059 extra[key] = str(value) |
1073 | 1060 |
1074 # MAM | 1061 # MAM |
1075 | 1062 |
1076 if hasattr(self.args, u'mam_filters'): | 1063 if hasattr(self.args, 'mam_filters'): |
1077 for key, value in self.args.mam_filters: | 1064 for key, value in self.args.mam_filters: |
1078 key = u'filter_' + key | 1065 key = 'filter_' + key |
1079 if key in extra: | 1066 if key in extra: |
1080 raise exceptions.ConflictError( | 1067 raise exceptions.ConflictError( |
1081 u"This key already exists in extra: u{key}".format(key=key)) | 1068 "This key already exists in extra: u{key}".format(key=key)) |
1082 extra[key] = value | 1069 extra[key] = value |
1083 | 1070 |
1084 # Order-By | 1071 # Order-By |
1085 | 1072 |
1086 try: | 1073 try: |