OILS / frontend / flag_spec.py View on Github | oils.pub

356 lines, 203 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 EvalFlags(self):
270 # type: () -> None
271 self.actions_long['eval'] = args.AppendEvalFlag('eval')
272 self.actions_long['eval-pure'] = args.AppendEvalFlag('eval-pure')
273
274 def ShortFlag(self,
275 short_name,
276 arg_type=None,
277 default=None,
278 quit_parsing_flags=False,
279 help=None):
280 # type: (str, int, Optional[Any], bool, Optional[str]) -> None
281 """ -c """
282 assert short_name.startswith('-'), short_name
283 assert len(short_name) == 2, short_name
284
285 char = short_name[1]
286 typ = _FlagType(arg_type)
287 if arg_type is None:
288 assert quit_parsing_flags == False
289 self.actions_short[char] = args.SetToTrue(char)
290 else:
291 self.actions_short[char] = _ActionForOneArg(
292 arg_type, char, quit_parsing_flags=quit_parsing_flags)
293
294 self.defaults[char] = _Default(arg_type, arg_default=default)
295 self.fields[char] = typ
296
297 def LongFlag(
298 self,
299 long_name, # type: str
300 arg_type=None, # type: Union[List[str], None, int]
301 default=None, # type: Optional[Any]
302 help=None, # type: Optional[str]
303 ):
304 # type: (...) -> None
305 """ --rcfile """
306 assert long_name.startswith('--'), long_name
307
308 name = long_name[2:]
309 typ = _FlagType(arg_type)
310 if arg_type is None:
311 self.actions_long[name] = args.SetToTrue(name)
312 else:
313 self.actions_long[name] = _ActionForOneArg(arg_type, name)
314
315 attr_name = name.replace('-', '_')
316 self.defaults[attr_name] = _Default(arg_type, arg_default=default)
317 self.fields[attr_name] = typ
318
319 def Option(self, short_flag, name, help=None):
320 # type: (Optional[str], str, Optional[str]) -> None
321 """Register an option; used for -e / -o errexit.
322
323 Args:
324 short_flag: 'e'
325 name: errexit
326 """
327 attr_name = name
328 if short_flag:
329 assert not short_flag.startswith('-'), short_flag
330 self.actions_short[short_flag] = args.SetOption(attr_name)
331 self.plus_flags.append(short_flag)
332
333 # Not validating with ArgName() for set -o. It's done later
334
335 def Option2(self, name, help=None):
336 # type: (str, Optional[str]) -> None
337 """Register an option; used for compopt -o plusdirs, etc."""
338 # validate the arg name
339 self.actions_short['o'].ArgName(name) # type: ignore
340
341 def Action(self, short_flag, name):
342 # type: (str, str) -> None
343 """Register an action that can be -f or -A file.
344
345 For the compgen builtin.
346
347 Args:
348 short_flag: 'f'
349 name: 'file'
350 """
351 attr_name = name
352 if short_flag:
353 assert not short_flag.startswith('-'), short_flag
354 self.actions_short[short_flag] = args.SetAction(attr_name)
355
356 self.actions_short['A'].ArgName(attr_name) # type: ignore