Source code for pyquickhelper.ipythonhelper.magic_parser

# -*- coding: utf-8 -*-
"""
Magic parser to parse magic commands


:githublink:`%|py|6`
"""
import argparse
import shlex

from ..loghelper.flog import noLOG


[docs]class MagicCommandParser(argparse.ArgumentParser): """ Adds method ``parse_cmd`` to :epkg:`*py:argparse:ArgumentParser`. :githublink:`%|py|16` """
[docs] def __init__(self, prog, *args, **kwargs): """ custom constructor, see :epkg:`*py:argparse:ArgumentParser`. :param prog: command name :param args: positional arguments :param kwargs: named arguments :githublink:`%|py|25` """ argparse.ArgumentParser.__init__(self, prog=prog, *args, **kwargs) self._keep_args = {}
[docs] @staticmethod def _private_get_name(*args): """ guesses the name of a parameter knowning the argument given to :meth:`add_argument <pyquickhelper.ipythonhelper.magic_parser.MagicCommandParser.add_argument>` :githublink:`%|py|34` """ if args == ('-h', '--help'): return "help" typstr = str for a in args: if isinstance(a, typstr): if a[0] != "-": return a elif a.startswith("--"): return a[2:].replace("-", "_") raise KeyError( # pragma: no cover "Unable to find parameter name in: " + typstr(args))
[docs] def add_argument(self, *args, **kwargs): """ Overloads the method, see `ArgumentParser <https://docs.python.org/3/library/argparse.html>`_. Among the parameters: * *no_eval*: avoid considering the parameter value as a potential variable stored in the notebook workspace. * *eval_type*: *type* can be used for parsing and *eval_type* is the expected return type. The method adds parameter *no_eval* to avoid considering the parameter value as a potential variable stored in the notebook workspace. :githublink:`%|py|60` """ name = MagicCommandParser._private_get_name(*args) if name in ["help", "-h", "--h"]: super(argparse.ArgumentParser, self).add_argument(*args, **kwargs) else: self._keep_args[name] = (args, kwargs.copy()) if kwargs.get("no_eval", False): del kwargs["no_eval"] if kwargs.get("eval_type", None): del kwargs["eval_type"] super(argparse.ArgumentParser, self).add_argument(*args, **kwargs) if args != ('-h', '--help'): pass elif kwargs.get("action", "") != "help": raise ValueError( # pragma: no cover "Unable to add parameter -h, --help, already taken for help.")
[docs] def has_choices(self, name): """ tells if a parameter has choises :param name: parameter name :return: boolean :githublink:`%|py|85` """ if name not in self._keep_args: raise KeyError( "Unable to find parameter name: {0} in {1}".format( name, list(self._keep_args.keys()))) return "choices" in self._keep_args[name][1]
[docs] def has_eval(self, name): """ Tells if a parameter value should be consider as a variable or some python code to evaluate. :param name: parameter name :return: boolean :githublink:`%|py|99` """ if name not in self._keep_args: raise KeyError( "Unable to find parameter name: {0} in {1}".format( name, list(self._keep_args.keys()))) return "no_eval" not in self._keep_args[name][1]
[docs] def expected_type(self, name): """ Returns the expected type for the parameter. :param name: parameter name :return: type or None of unknown :githublink:`%|py|112` """ if name in self._keep_args: return self._keep_args[name][1].get("type", None) return None
[docs] def expected_eval_type(self, name): """ return the expected evaluation type for the parameter (if the value is interpreter as a python expression) :param name: parameter name :return: type or None of unknown :githublink:`%|py|124` """ if name in self._keep_args: return self._keep_args[name][1].get("eval_type", None) return None
[docs] def parse_cmd(self, line, context=None, fLOG=noLOG): """ Splits line using `shlex <https://docs.python.org/3/library/shlex.html>`_ and call `parse_args <https://docs.python.org/3/library/ argparse.html#argparse.ArgumentParser.parse_args>`_ :param line: string :param context: if not None, tries to evaluate expression the command may contain :param fLOG: logging function :return: list of strings The function distinguishes between the type used to parse the command line (type) and the expected type after the evaluation *eval_type*. :githublink:`%|py|143` """ args = shlex.split(line, posix=False) res = self.parse_args(args) if context is not None: up = {} for k, v in res.__dict__.items(): if self.has_choices(k) or not self.has_eval(k): up[k] = v else: ev = self.eval(v, context=context, fLOG=fLOG) v_exp = self.expected_eval_type(k) if (ev is not None and (v_exp is None or v_exp == type(ev)) and # pylint: disable=C0123 (type(v) != type(ev) or v != ev)): # pylint: disable=C0123 up[k] = ev elif v_exp is not None and type(v) != v_exp: # pylint: disable=C0123 up[k] = v_exp(v) if len(up) > 0: for k, v in up.items(): res.__dict__[k] = v return res
[docs] def eval(self, value, context, fLOG=noLOG): """ Evaluate a string knowing the context, it returns *value* if it does not belong to the context or if it contains brackets or symbols (+, *), if the value cannot be evaluated (with function `eval <https://docs.python.org/3/library/functions.html#eval>`_), it returns the value value :param value: string :param context: something like ``self.shell.user_ns`` :param fLOG: logging function :return: *value* or its evaluation The method interprets variable inside list, tuple or dictionaries (for *value*). :githublink:`%|py|181` """ typstr = str if isinstance(value, typstr): if value in context: return context[value] elif isinstance(value, list): return [self.eval(v, context, fLOG=fLOG) for v in value] elif isinstance(value, tuple): return tuple(self.eval(v, context, fLOG=fLOG) for v in value) elif isinstance(value, dict): return {k: self.eval(v, context, fLOG=fLOG) for k, v in value.items()} if isinstance(value, typstr) and ( "[" in value or "]" in value or "+" in value or "*" in value or value.split(".")[0] in context): try: res = eval(value, {}, context) return res except Exception as e: # pragma: no cover fLOG( "Unable to interpret {} due to {}.".format(typstr(value), e)) return value return value