Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# -*- coding: utf-8 -*- 

2""" 

3@file 

4@brief Magic parser to parse magic commands 

5""" 

6import argparse 

7import shlex 

8 

9from ..loghelper.flog import noLOG 

10 

11 

12class MagicCommandParser(argparse.ArgumentParser): 

13 

14 """ 

15 Adds method ``parse_cmd`` to :epkg:`*py:argparse:ArgumentParser`. 

16 """ 

17 

18 def __init__(self, prog, *args, **kwargs): 

19 """ 

20 custom constructor, see :epkg:`*py:argparse:ArgumentParser`. 

21 

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 = {} 

28 

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)) 

46 

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: 

52 

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. 

57 

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"] 

70 

71 super(argparse.ArgumentParser, self).add_argument(*args, **kwargs) 

72 

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.") 

78 

79 def has_choices(self, name): 

80 """ 

81 tells if a parameter has choises 

82 

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] 

91 

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. 

96 

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] 

105 

106 def expected_type(self, name): 

107 """ 

108 Returns the expected type for the parameter. 

109 

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 

116 

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) 

121 

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 

128 

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>`_ 

134 

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 

139 

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) 

146 

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) 

160 

161 if len(up) > 0: 

162 for k, v in up.items(): 

163 res.__dict__[k] = v 

164 

165 return res 

166 

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 

174 

175 @param value string 

176 @param context something like ``self.shell.user_ns`` 

177 @param fLOG logging function 

178 @return *value* or its evaluation 

179 

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()} 

192 

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 "Unable to interpret {} due to {}.".format(typstr(value), e)) 

202 return value 

203 return value