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("unable to find parameter name in: " + typstr(args)) 

45 

46 def add_argument(self, *args, **kwargs): 

47 """ 

48 Overloads the method, 

49 see `ArgumentParser <https://docs.python.org/3/library/argparse.html>`_. 

50 Among the parameters: 

51 

52 * *no_eval*: avoid considering the parameter 

53 value as a potential variable stored in the notebook workspace. 

54 * *eval_type*: *type* can be used for parsing and *eval_type* 

55 is the expected return type. 

56 

57 The method adds parameter *no_eval* to avoid considering the parameter 

58 value as a potential variable stored in the notebook workspace. 

59 """ 

60 name = MagicCommandParser._private_get_name(*args) 

61 if name in ["help", "-h", "--h"]: 

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

63 else: 

64 self._keep_args[name] = (args, kwargs.copy()) 

65 if kwargs.get("no_eval", False): 

66 del kwargs["no_eval"] 

67 if kwargs.get("eval_type", None): 

68 del kwargs["eval_type"] 

69 

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

71 

72 if args != ('-h', '--help'): 

73 pass 

74 elif kwargs.get("action", "") != "help": 

75 raise ValueError( 

76 "unable to add parameter -h, --help, already taken for help") 

77 

78 def has_choices(self, name): 

79 """ 

80 tells if a parameter has choises 

81 

82 @param name parameter name 

83 @return boolean 

84 """ 

85 if name not in self._keep_args: 

86 raise KeyError("unable to find parameter name: {0} in {1}".format( 

87 name, list(self._keep_args.keys()))) 

88 return "choices" in self._keep_args[name][1] 

89 

90 def has_eval(self, name): 

91 """ 

92 Tells if a parameter value should be consider as a variable or some python code 

93 to evaluate. 

94 

95 @param name parameter name 

96 @return boolean 

97 """ 

98 if name not in self._keep_args: 

99 raise KeyError("unable to find parameter name: {0} in {1}".format( 

100 name, list(self._keep_args.keys()))) 

101 return "no_eval" not in self._keep_args[name][1] 

102 

103 def expected_type(self, name): 

104 """ 

105 Returns the expected type for the parameter. 

106 

107 @param name parameter name 

108 @return type or None of unknown 

109 """ 

110 if name in self._keep_args: 

111 return self._keep_args[name][1].get("type", None) 

112 else: 

113 return None 

114 

115 def expected_eval_type(self, name): 

116 """ 

117 return the expected evaluation type for the parameter 

118 (if the value is interpreter as a python expression) 

119 

120 @param name parameter name 

121 @return type or None of unknown 

122 """ 

123 if name in self._keep_args: 

124 return self._keep_args[name][1].get("eval_type", None) 

125 else: 

126 return None 

127 

128 def parse_cmd(self, line, context=None, fLOG=noLOG): 

129 """ 

130 split line using `shlex <https://docs.python.org/3/library/shlex.html>`_ 

131 and call `parse_args <https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.parse_args>`_ 

132 

133 @param line string 

134 @param context if not None, tries to evaluate expression the command may contain 

135 @param fLOG logging function 

136 @return list of strings 

137 

138 The function distinguishes between the type used to parse 

139 the command line (type) and the expected type after the evaluation 

140 *eval_type*. 

141 """ 

142 args = shlex.split(line, posix=False) 

143 res = self.parse_args(args) 

144 

145 if context is not None: 

146 up = {} 

147 for k, v in res.__dict__.items(): 

148 if self.has_choices(k) or not self.has_eval(k): 

149 up[k] = v 

150 else: 

151 ev = self.eval(v, context=context, fLOG=fLOG) 

152 v_exp = self.expected_eval_type(k) 

153 if ev is not None and (v_exp is None or v_exp == type(ev)) and \ 

154 (type(v) != type(ev) or v != ev): # pylint: disable=C0123 

155 up[k] = ev 

156 elif v_exp is not None and type(v) != v_exp: # pylint: disable=C0123 

157 up[k] = v_exp(v) 

158 

159 if len(up) > 0: 

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

161 res.__dict__[k] = v 

162 

163 return res 

164 

165 def eval(self, value, context, fLOG=noLOG): 

166 """ 

167 Evaluate a string knowing the context, 

168 it returns *value* if it does not belong to the context 

169 or if it contains brackets or symbols (+, *), 

170 if the value cannot be evaluated (with function `eval <https://docs.python.org/3/library/functions.html#eval>`_), 

171 it returns the value value 

172 

173 @param value string 

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

175 @param fLOG logging function 

176 @return *value* or its evaluation 

177 

178 The method interprets variable inside list, tuple or dictionaries (for *value*). 

179 """ 

180 typstr = str 

181 if isinstance(value, typstr): 

182 if value in context: 

183 return context[value] 

184 elif isinstance(value, list): 

185 return [self.eval(v, context, fLOG=fLOG) for v in value] 

186 elif isinstance(value, tuple): 

187 return tuple(self.eval(v, context, fLOG=fLOG) for v in value) 

188 elif isinstance(value, dict): 

189 return {k: self.eval(v, context, fLOG=fLOG) for k, v in value.items()} 

190 

191 if isinstance(value, typstr) and ( 

192 "[" in value or "]" in value or "+" in value or "*" in value or 

193 value.split(".")[0] in context): 

194 try: 

195 res = eval(value, {}, context) 

196 return res 

197 except Exception as e: 

198 fLOG( 

199 "unable to interpret: " + typstr(value), " exception ", typstr(e)) 

200 return value 

201 else: 

202 return value