Coverage for pyquickhelper/ipythonhelper/magic_parser.py: 84%
82 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-03 02:21 +0200
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-03 02:21 +0200
1# -*- coding: utf-8 -*-
2"""
3@file
4@brief Magic parser to parse magic commands
5"""
6import argparse
7import shlex
9from ..loghelper.flog import noLOG
12class MagicCommandParser(argparse.ArgumentParser):
14 """
15 Adds method ``parse_cmd`` to :epkg:`*py:argparse:ArgumentParser`.
16 """
18 def __init__(self, prog, *args, **kwargs):
19 """
20 custom constructor, see :epkg:`*py:argparse:ArgumentParser`.
22 @param prog command name
23 @param args positional arguments
24 @param kwargs named arguments
25 """
26 argparse.ArgumentParser.__init__(self, prog=prog, *args, **kwargs)
27 self._keep_args = {}
29 @staticmethod
30 def _private_get_name(*args):
31 """
32 guesses the name of a parameter knowning the argument
33 given to @see me add_argument
34 """
35 if args == ('-h', '--help'):
36 return "help"
37 typstr = str
38 for a in args:
39 if isinstance(a, typstr):
40 if a[0] != "-":
41 return a
42 elif a.startswith("--"):
43 return a[2:].replace("-", "_")
44 raise KeyError( # pragma: no cover
45 "Unable to find parameter name in: " + typstr(args))
47 def add_argument(self, *args, **kwargs):
48 """
49 Overloads the method,
50 see `ArgumentParser <https://docs.python.org/3/library/argparse.html>`_.
51 Among the parameters:
53 * *no_eval*: avoid considering the parameter
54 value as a potential variable stored in the notebook workspace.
55 * *eval_type*: *type* can be used for parsing and *eval_type*
56 is the expected return type.
58 The method adds parameter *no_eval* to avoid considering the parameter
59 value as a potential variable stored in the notebook workspace.
60 """
61 name = MagicCommandParser._private_get_name(*args)
62 if name in ["help", "-h", "--h"]:
63 super(argparse.ArgumentParser, self).add_argument(*args, **kwargs)
64 else:
65 self._keep_args[name] = (args, kwargs.copy())
66 if kwargs.get("no_eval", False):
67 del kwargs["no_eval"]
68 if kwargs.get("eval_type", None):
69 del kwargs["eval_type"]
71 super(argparse.ArgumentParser, self).add_argument(*args, **kwargs)
73 if args != ('-h', '--help'):
74 pass
75 elif kwargs.get("action", "") != "help":
76 raise ValueError( # pragma: no cover
77 "Unable to add parameter -h, --help, already taken for help.")
79 def has_choices(self, name):
80 """
81 tells if a parameter has choises
83 @param name parameter name
84 @return boolean
85 """
86 if name not in self._keep_args:
87 raise KeyError(
88 "Unable to find parameter name: {0} in {1}".format(
89 name, list(self._keep_args.keys())))
90 return "choices" in self._keep_args[name][1]
92 def has_eval(self, name):
93 """
94 Tells if a parameter value should be consider as a variable or some python code
95 to evaluate.
97 @param name parameter name
98 @return boolean
99 """
100 if name not in self._keep_args:
101 raise KeyError(
102 "Unable to find parameter name: {0} in {1}".format(
103 name, list(self._keep_args.keys())))
104 return "no_eval" not in self._keep_args[name][1]
106 def expected_type(self, name):
107 """
108 Returns the expected type for the parameter.
110 @param name parameter name
111 @return type or None of unknown
112 """
113 if name in self._keep_args:
114 return self._keep_args[name][1].get("type", None)
115 return None
117 def expected_eval_type(self, name):
118 """
119 return the expected evaluation type for the parameter
120 (if the value is interpreter as a python expression)
122 @param name parameter name
123 @return type or None of unknown
124 """
125 if name in self._keep_args:
126 return self._keep_args[name][1].get("eval_type", None)
127 return None
129 def parse_cmd(self, line, context=None, fLOG=noLOG):
130 """
131 Splits line using `shlex <https://docs.python.org/3/library/shlex.html>`_
132 and call `parse_args <https://docs.python.org/3/library/
133 argparse.html#argparse.ArgumentParser.parse_args>`_
135 @param line string
136 @param context if not None, tries to evaluate expression the command may contain
137 @param fLOG logging function
138 @return list of strings
140 The function distinguishes between the type used to parse
141 the command line (type) and the expected type after the evaluation
142 *eval_type*.
143 """
144 args = shlex.split(line, posix=False)
145 res = self.parse_args(args)
147 if context is not None:
148 up = {}
149 for k, v in res.__dict__.items():
150 if self.has_choices(k) or not self.has_eval(k):
151 up[k] = v
152 else:
153 ev = self.eval(v, context=context, fLOG=fLOG)
154 v_exp = self.expected_eval_type(k)
155 if (ev is not None and (v_exp is None or v_exp == type(ev)) and # pylint: disable=C0123
156 (type(v) != type(ev) or v != ev)): # pylint: disable=C0123
157 up[k] = ev
158 elif v_exp is not None and type(v) != v_exp: # pylint: disable=C0123
159 up[k] = v_exp(v)
161 if len(up) > 0:
162 for k, v in up.items():
163 res.__dict__[k] = v
165 return res
167 def eval(self, value, context, fLOG=noLOG):
168 """
169 Evaluate a string knowing the context,
170 it returns *value* if it does not belong to the context
171 or if it contains brackets or symbols (+, *),
172 if the value cannot be evaluated (with function `eval <https://docs.python.org/3/library/functions.html#eval>`_),
173 it returns the value value
175 @param value string
176 @param context something like ``self.shell.user_ns``
177 @param fLOG logging function
178 @return *value* or its evaluation
180 The method interprets variable inside list, tuple or dictionaries (for *value*).
181 """
182 typstr = str
183 if isinstance(value, typstr):
184 if value in context:
185 return context[value]
186 elif isinstance(value, list):
187 return [self.eval(v, context, fLOG=fLOG) for v in value]
188 elif isinstance(value, tuple):
189 return tuple(self.eval(v, context, fLOG=fLOG) for v in value)
190 elif isinstance(value, dict):
191 return {k: self.eval(v, context, fLOG=fLOG) for k, v in value.items()}
193 if isinstance(value, typstr) and (
194 "[" in value or "]" in value or "+" in value or "*" in value or
195 value.split(".")[0] in context):
196 try:
197 res = eval(value, {}, context)
198 return res
199 except Exception as e: # pragma: no cover
200 fLOG(
201 f"Unable to interpret {typstr(value)} due to {e}.")
202 return value
203 return value