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

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