OILS / frontend / flag_spec.py View on Github | oilshell.org

349 lines, 200 significant
1#!/usr/bin/env python2
2"""
3flag_spec.py -- Flag and arg defs for builtins.
4"""
5from __future__ import print_function
6
7from _devbuild.gen.runtime_asdl import flag_type_e, flag_type_t
8from _devbuild.gen.value_asdl import (value, value_t)
9from frontend import args
10from mycpp import mops
11from mycpp import mylib
12from mycpp.mylib import log
13
14from typing import Union, List, Dict, Any, Optional
15
16_ = log
17
18# Similar to frontend/{option,builtin}_def.py
19FLAG_SPEC = {}
20FLAG_SPEC_AND_MORE = {}
21
22
23def FlagSpec(builtin_name):
24 # type: (str) -> _FlagSpec
25 """Define a flag parser."""
26 arg_spec = _FlagSpec()
27 FLAG_SPEC[builtin_name] = arg_spec
28 return arg_spec
29
30
31def FlagSpecAndMore(name, typed=True):
32 # type: (str, bool) -> _FlagSpecAndMore
33 """Define a flag parser with -o +o options
34
35 For set, bin/oils_for_unix.py main, compgen -A, complete -A, etc.
36 """
37 arg_spec = _FlagSpecAndMore()
38 FLAG_SPEC_AND_MORE[name] = arg_spec
39 return arg_spec
40
41
42def All():
43 # type: () -> Dict[str, Any]
44 return FLAG_SPEC
45
46
47def _FlagType(arg_type):
48 # type: (Union[None, int, List[str]]) -> flag_type_t
49
50 if arg_type is None: # implicit _FlagSpec
51 typ = flag_type_e.Bool
52 elif arg_type == args.Bool:
53 typ = flag_type_e.Bool
54
55 elif arg_type == args.Int:
56 typ = flag_type_e.Int
57 elif arg_type == args.Float:
58 typ = flag_type_e.Float
59 elif arg_type == args.String:
60 typ = flag_type_e.Str
61 elif isinstance(arg_type, list):
62 typ = flag_type_e.Str
63 else:
64 raise AssertionError(arg_type)
65
66 return typ
67
68
69def _MakeAction(arg_type, name, quit_parsing_flags=False):
70 # type: (Union[None, int, List[str]], str, bool) -> args._Action
71
72 if arg_type == args.Bool:
73 assert not quit_parsing_flags
74 action = args.SetAttachedBool(name) # type: args._Action
75
76 elif arg_type == args.Int:
77 assert not quit_parsing_flags
78 action = args.SetToInt(name)
79
80 elif arg_type == args.Float:
81 assert not quit_parsing_flags
82 action = args.SetToFloat(name)
83
84 elif arg_type == args.String:
85 action = args.SetToString(name, quit_parsing_flags)
86
87 elif isinstance(arg_type, list):
88 action = args.SetToString(name, quit_parsing_flags, valid=arg_type)
89
90 else:
91 raise AssertionError(arg_type)
92
93 return action
94
95
96def _Default(arg_type, arg_default=None):
97 # type: (Union[None, int, List[str]], Optional[str]) -> value_t
98
99 if arg_default is not None:
100 if isinstance(arg_default, bool):
101 return value.Bool(arg_default)
102 elif isinstance(arg_default, int):
103 return value.Int(mops.IntWiden(arg_default))
104 elif isinstance(arg_default, str):
105 return value.Str(arg_default)
106 else:
107 raise AssertionError(arg_default)
108
109 if arg_type is None:
110 default = value.Bool(False) # type: value_t
111 elif arg_type == args.Bool:
112 default = value.Bool(False)
113
114 elif arg_type == args.Int:
115 # positive values aren't allowed now
116 default = value.Int(mops.MINUS_ONE)
117 elif arg_type == args.Float:
118 default = value.Float(-1.0) # ditto
119 elif arg_type == args.String:
120 default = value.Undef # e.g. read -d '' is NOT the default
121 elif isinstance(arg_type, list):
122 default = value.Str('') # note: it's not None
123 else:
124 raise AssertionError(arg_type)
125 return default
126
127
128class _FlagSpec(object):
129 """Parser for sh builtins, like 'read' or 'echo' (which has a special
130 case).
131
132 Usage:
133 spec = args.FlagSpec()
134 spec.ShortFlag('-a')
135 opts, i = spec.Parse(argv)
136 """
137
138 def __init__(self):
139 # type: () -> None
140 self.arity0 = [] # type: List[str]
141 self.arity1 = {} # type: Dict[str, args._Action]
142 self.plus_flags = [] # type: List[str]
143
144 # YSH extensions
145 self.actions_long = {} # type: Dict[str, args._Action]
146 self.defaults = {} # type: Dict[str, value_t]
147
148 # For arg_types code generation to use. Not used at runtime.
149 self.fields = {} # type: Dict[str, flag_type_t]
150
151 def PrintHelp(self, f):
152 # type: (mylib.Writer) -> None
153 if self.arity0:
154 print(' arity 0:')
155 for ch in self.arity0:
156 print(' -%s' % ch)
157
158 if self.arity1:
159 print(' arity 1:')
160 for ch in self.arity1:
161 print(' -%s' % ch)
162
163 def ShortFlag(self, short_name, arg_type=None, long_name=None, help=None):
164 # type: (str, Optional[int], Optional[str], Optional[str]) -> None
165 """This is very similar to ShortFlag for FlagSpecAndMore, except we
166 have separate arity0 and arity1 dicts."""
167 assert short_name.startswith('-'), short_name
168 assert len(short_name) == 2, short_name
169
170 typ = _FlagType(arg_type)
171 char = short_name[1]
172
173 # Hack for read -0. Make it a valid variable name
174 if char == '0':
175 char = 'Z'
176
177 if arg_type is None:
178 self.arity0.append(char)
179 else:
180 self.arity1[char] = _MakeAction(arg_type, char)
181
182 if long_name is not None:
183 name = long_name[2:] # key for parsing
184 if arg_type is None:
185 self.actions_long[name] = args.SetToTrue(char)
186 else:
187 self.actions_long[name] = _MakeAction(arg_type, char)
188
189 self.defaults[char] = _Default(arg_type)
190 self.fields[char] = typ
191
192 def LongFlag(
193 self,
194 long_name, # type: str
195 arg_type=None, # type: Union[None, int, List[str]]
196 default=None, # type: Optional[Any]
197 help=None # type: Optional[str]
198 ):
199 # type: (...) -> None
200 """Define a long flag like --verbose or --validate=0."""
201 assert long_name.startswith('--'), long_name
202 typ = _FlagType(arg_type)
203
204 name = long_name[2:] # key for parsing
205 if arg_type is None:
206 self.actions_long[name] = args.SetToTrue(name)
207 else:
208 self.actions_long[name] = _MakeAction(arg_type, name)
209
210 self.defaults[name] = _Default(arg_type, arg_default=default)
211 self.fields[name] = typ
212
213 def PlusFlag(self, char, help=None):
214 # type: (str, Optional[str]) -> None
215 """Define an option that can be turned off with + and on with -.
216
217 It's actually a ternary value: plus, minus, or unset.
218
219 For declare -x, etc.
220 """
221 assert len(char) == 1 # 'r' for -r +r
222 self.plus_flags.append(char)
223
224 self.defaults[char] = value.Undef
225 # '+' or '-'. TODO: Should we make it a bool?
226 self.fields[char] = flag_type_e.Str
227
228
229class _FlagSpecAndMore(object):
230 """Parser for 'set' and 'sh', which both need to process shell options.
231
232 Usage:
233 spec = FlagSpecAndMore()
234 spec.ShortFlag(...)
235 spec.Option('u', 'nounset')
236 spec.Parse(...)
237 """
238
239 def __init__(self, typed=True):
240 # type: (bool) -> None
241
242 # {'-c': _Action}
243 self.actions_short = {} # type: Dict[str, args._Action]
244
245 # {'--rcfile': _Action}
246 self.actions_long = {} # type: Dict[str, args._Action]
247 self.plus_flags = [] # type: List[str]
248 self.defaults = {} # type: Dict[str, value_t]
249
250 # For code generation. Not used at runtime.
251 self.fields = {} # type: Dict[str, flag_type_t]
252
253 def InitActions(self):
254 # type: () -> None
255 self.actions_short['A'] = args.SetNamedAction() # -A
256
257 def InitOptions(self):
258 # type: () -> None
259 self.actions_short['o'] = args.SetNamedOption() # -o and +o
260 self.plus_flags.append('o')
261
262 def InitShopt(self):
263 # type: () -> None
264 self.actions_short['O'] = args.SetNamedOption(shopt=True) # -O and +O
265 self.plus_flags.append('O')
266
267 def ShortFlag(self,
268 short_name,
269 arg_type=None,
270 default=None,
271 quit_parsing_flags=False,
272 help=None):
273 # type: (str, int, Optional[Any], bool, Optional[str]) -> None
274 """ -c """
275 assert short_name.startswith('-'), short_name
276 assert len(short_name) == 2, short_name
277
278 char = short_name[1]
279 typ = _FlagType(arg_type)
280 if arg_type is None:
281 assert quit_parsing_flags == False
282 self.actions_short[char] = args.SetToTrue(char)
283 else:
284 self.actions_short[char] = _MakeAction(
285 arg_type, char, quit_parsing_flags=quit_parsing_flags)
286
287 self.defaults[char] = _Default(arg_type, arg_default=default)
288 self.fields[char] = typ
289
290 def LongFlag(
291 self,
292 long_name, # type: str
293 arg_type=None, # type: Union[List[str], None, int]
294 default=None, # type: Optional[Any]
295 help=None, # type: Optional[str]
296 ):
297 # type: (...) -> None
298 """ --rcfile """
299 assert long_name.startswith('--'), long_name
300
301 name = long_name[2:]
302 typ = _FlagType(arg_type)
303 if arg_type is None:
304 self.actions_long[name] = args.SetToTrue(name)
305 else:
306 self.actions_long[name] = _MakeAction(arg_type, name)
307
308 attr_name = name.replace('-', '_')
309 self.defaults[attr_name] = _Default(arg_type, arg_default=default)
310 self.fields[attr_name] = typ
311
312 def Option(self, short_flag, name, help=None):
313 # type: (Optional[str], str, Optional[str]) -> None
314 """Register an option; used for -e / -o errexit.
315
316 Args:
317 short_flag: 'e'
318 name: errexit
319 """
320 attr_name = name
321 if short_flag:
322 assert not short_flag.startswith('-'), short_flag
323 self.actions_short[short_flag] = args.SetOption(attr_name)
324 self.plus_flags.append(short_flag)
325
326 # Not validating with ArgName() for set -o. It's done later
327
328 def Option2(self, name, help=None):
329 # type: (str, Optional[str]) -> None
330 """Register an option; used for compopt -o plusdirs, etc."""
331 # validate the arg name
332 self.actions_short['o'].ArgName(name) # type: ignore
333
334 def Action(self, short_flag, name):
335 # type: (str, str) -> None
336 """Register an action that can be -f or -A file.
337
338 For the compgen builtin.
339
340 Args:
341 short_flag: 'f'
342 name: 'file'
343 """
344 attr_name = name
345 if short_flag:
346 assert not short_flag.startswith('-'), short_flag
347 self.actions_short[short_flag] = args.SetAction(attr_name)
348
349 self.actions_short['A'].ArgName(attr_name) # type: ignore