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

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