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