OILS / builtin / meta_oils.py View on Github | oils.pub

907 lines, 573 significant
1#!/usr/bin/env python2
2"""
3meta_oils.py - Builtins that call back into the interpreter, or reflect on it.
4
5OSH builtins:
6 builtin command type
7 source eval
8
9YSH builtins:
10 invoke extern
11 use
12"""
13from __future__ import print_function
14
15from _devbuild.gen import arg_types
16from _devbuild.gen.runtime_asdl import cmd_value, CommandStatus
17from _devbuild.gen.syntax_asdl import source, loc, loc_t, CompoundWord
18from _devbuild.gen.value_asdl import Obj, value, value_t
19from core import alloc
20from core import dev
21from core import error
22from core.error import e_usage
23from core import executor
24from core import main_loop
25from core import process
26from core import pyutil # strerror
27from core import state
28from core import vm
29from data_lang import j8_lite
30from frontend import consts
31from frontend import flag_util
32from frontend import reader
33from mycpp.mylib import log, print_stderr, NewDict
34from pylib import os_path
35from osh import cmd_eval
36
37import posix_ as posix
38from posix_ import X_OK # translated directly to C macro
39
40import libc
41
42_ = log
43
44from typing import Dict, List, Tuple, Optional, TYPE_CHECKING
45if TYPE_CHECKING:
46 from frontend import args
47 from frontend.parse_lib import ParseContext
48 from core import optview
49 from display import ui
50 from mycpp import mylib
51 from osh.cmd_eval import CommandEvaluator
52 from osh import cmd_parse
53
54
55class Eval(vm._Builtin):
56
57 def __init__(
58 self,
59 parse_ctx, # type: ParseContext
60 exec_opts, # type: optview.Exec
61 cmd_ev, # type: CommandEvaluator
62 tracer, # type: dev.Tracer
63 errfmt, # type: ui.ErrorFormatter
64 mem, # type: state.Mem
65 ):
66 # type: (...) -> None
67 self.parse_ctx = parse_ctx
68 self.arena = parse_ctx.arena
69 self.exec_opts = exec_opts
70 self.cmd_ev = cmd_ev
71 self.tracer = tracer
72 self.errfmt = errfmt
73 self.mem = mem
74
75 def Run(self, cmd_val):
76 # type: (cmd_value.Argv) -> int
77
78 # There are no flags, but we need it to respect --
79 _, arg_r = flag_util.ParseCmdVal('eval', cmd_val)
80
81 if self.exec_opts.simple_eval_builtin():
82 code_str, eval_loc = arg_r.ReadRequired2('requires code string')
83 if not arg_r.AtEnd():
84 e_usage('requires exactly 1 argument', loc.Missing)
85 else:
86 code_str = ' '.join(arg_r.Rest())
87 # code_str could be EMPTY, so just use the first one
88 eval_loc = cmd_val.arg_locs[0]
89
90 line_reader = reader.StringLineReader(code_str, self.arena)
91 c_parser = self.parse_ctx.MakeOshParser(line_reader)
92
93 src = source.Dynamic('eval arg', eval_loc)
94 with dev.ctx_Tracer(self.tracer, 'eval', None):
95 with state.ctx_CompoundWordDebugFrame(self.mem, eval_loc):
96 with alloc.ctx_SourceCode(self.arena, src):
97 return main_loop.Batch(self.cmd_ev,
98 c_parser,
99 self.errfmt,
100 cmd_flags=cmd_eval.RaiseControlFlow)
101
102
103def _VarName(module_path):
104 # type: (str) -> str
105 """Convert ///path/foo-bar.ysh -> foo_bar
106
107 Design issue: proc vs. func naming conventinos imply treating hyphens
108 differently.
109
110 foo-bar myproc
111 var x = `foo-bar`.myproc
112
113 I guess use this for now:
114
115 foo_bar myproc
116 var x = foo_bar.myproc
117
118 The user can also choose this:
119
120 fooBar myproc
121 var x = fooBar.myproc
122 """
123 basename = os_path.basename(module_path)
124 i = basename.rfind('.')
125 if i != -1:
126 basename = basename[:i]
127 #return basename.replace('-', '_')
128 return basename
129
130
131class ShellFile(vm._Builtin):
132 """
133 These share code:
134 - 'source' builtin for OSH
135 - 'use' builtin for YSH
136 """
137
138 def __init__(
139 self,
140 parse_ctx, # type: ParseContext
141 search_path, # type: executor.SearchPath
142 cmd_ev, # type: CommandEvaluator
143 fd_state, # type: process.FdState
144 tracer, # type: dev.Tracer
145 errfmt, # type: ui.ErrorFormatter
146 loader, # type: pyutil._ResourceLoader
147 module_invoke=None, # type: vm._Builtin
148 ):
149 # type: (...) -> None
150 """
151 If module_invoke is passed, this class behaves like 'use'. Otherwise
152 it behaves like 'source'.
153 """
154 self.parse_ctx = parse_ctx
155 self.arena = parse_ctx.arena
156 self.search_path = search_path
157 self.cmd_ev = cmd_ev
158 self.fd_state = fd_state
159 self.tracer = tracer
160 self.errfmt = errfmt
161 self.loader = loader
162 self.module_invoke = module_invoke
163
164 self.builtin_name = 'use' if module_invoke else 'source'
165 self.mem = cmd_ev.mem
166
167 # Don't load modules more than once
168 # keyed by libc.realpath(arg)
169 self._disk_cache = {} # type: Dict[str, Obj]
170
171 # keyed by ///
172 self._embed_cache = {} # type: Dict[str, Obj]
173
174 def Run(self, cmd_val):
175 # type: (cmd_value.Argv) -> int
176 if self.module_invoke:
177 return self._Use(cmd_val)
178 else:
179 return self._Source(cmd_val)
180
181 def LoadEmbeddedFile(self, embed_path, blame_loc):
182 # type: (str, loc_t) -> Tuple[str, cmd_parse.CommandParser]
183 try:
184 load_path = os_path.join("stdlib", embed_path)
185 contents = self.loader.Get(load_path)
186 except (IOError, OSError):
187 self.errfmt.Print_('%r failed: No builtin file %r' %
188 (self.builtin_name, load_path),
189 blame_loc=blame_loc)
190 return None, None # error
191
192 line_reader = reader.StringLineReader(contents, self.arena)
193 c_parser = self.parse_ctx.MakeOshParser(line_reader)
194 return load_path, c_parser
195
196 def _LoadDiskFile(self, fs_path, blame_loc):
197 # type: (str, loc_t) -> Tuple[mylib.LineReader, cmd_parse.CommandParser]
198 try:
199 # Shell can't use descriptors 3-9
200 f = self.fd_state.Open(fs_path)
201 except (IOError, OSError) as e:
202 self.errfmt.Print_(
203 '%s %r failed: %s' %
204 (self.builtin_name, fs_path, pyutil.strerror(e)),
205 blame_loc=blame_loc)
206 return None, None
207
208 line_reader = reader.FileLineReader(f, self.arena)
209 c_parser = self.parse_ctx.MakeOshParser(line_reader)
210 return f, c_parser
211
212 def _SourceExec(self, cmd_val, arg_r, path, c_parser):
213 # type: (cmd_value.Argv, args.Reader, str, cmd_parse.CommandParser) -> int
214 call_loc = cmd_val.arg_locs[0]
215
216 # A sourced module CAN have a new arguments array, but it always shares
217 # the same variable scope as the caller. The caller could be at either a
218 # global or a local scope.
219
220 # TODO: I wonder if we compose the enter/exit methods more easily.
221
222 with dev.ctx_Tracer(self.tracer, 'source', cmd_val.argv):
223 source_argv = arg_r.Rest()
224 with state.ctx_Source(self.mem, path, source_argv, call_loc):
225 with state.ctx_ThisDir(self.mem, path):
226 src = source.OtherFile(path, call_loc)
227 with alloc.ctx_SourceCode(self.arena, src):
228 try:
229 status = main_loop.Batch(
230 self.cmd_ev,
231 c_parser,
232 self.errfmt,
233 cmd_flags=cmd_eval.RaiseControlFlow)
234 except vm.IntControlFlow as e:
235 if e.IsReturn():
236 status = e.StatusCode()
237 else:
238 raise
239
240 return status
241
242 def _NewModule(self):
243 # type: () -> Obj
244 # Builtin proc that serves as __invoke__ - it looks up procs in 'self'
245 methods = Obj(None,
246 {'__invoke__': value.BuiltinProc(self.module_invoke)})
247 props = NewDict() # type: Dict[str, value_t]
248 module_obj = Obj(methods, props)
249 return module_obj
250
251 def _UseExec(
252 self,
253 cmd_val, # type: cmd_value.Argv
254 path, # type: str
255 path_loc, # type: loc_t
256 c_parser, # type: cmd_parse.CommandParser
257 props, # type: Dict[str, value_t]
258 ):
259 # type: (...) -> int
260 """
261 Args:
262 props: is mutated, and will contain module properties
263 """
264 error_strs = [] # type: List[str]
265
266 with dev.ctx_Tracer(self.tracer, 'use', cmd_val.argv):
267 with state.ctx_ModuleEval(self.mem, cmd_val.arg_locs[0], props,
268 error_strs):
269 with state.ctx_ThisDir(self.mem, path):
270 src = source.OtherFile(path, path_loc)
271 with alloc.ctx_SourceCode(self.arena, src):
272 try:
273 status = main_loop.Batch(
274 self.cmd_ev,
275 c_parser,
276 self.errfmt,
277 cmd_flags=cmd_eval.RaiseControlFlow)
278 except vm.IntControlFlow as e:
279 if e.IsReturn():
280 status = e.StatusCode()
281 else:
282 raise
283 if status != 0:
284 return status
285 #e_die("'use' failed 2", path_loc)
286
287 if len(error_strs):
288 for s in error_strs:
289 self.errfmt.PrintMessage('Error: %s' % s, path_loc)
290 return 1
291
292 return 0
293
294 def _Source(self, cmd_val):
295 # type: (cmd_value.Argv) -> int
296 attrs, arg_r = flag_util.ParseCmdVal('source', cmd_val)
297 arg = arg_types.source(attrs.attrs)
298
299 path_arg, path_loc = arg_r.ReadRequired2('requires a file path')
300
301 # Old:
302 # source --builtin two.sh # looks up stdlib/two.sh
303 # New:
304 # source $LIB_OSH/two.sh # looks up stdlib/osh/two.sh
305 # source ///osh/two.sh # looks up stdlib/osh/two.sh
306 embed_path = None # type: Optional[str]
307 if arg.builtin:
308 embed_path = path_arg
309 elif path_arg.startswith('///'):
310 embed_path = path_arg[3:]
311
312 if embed_path is not None:
313 load_path, c_parser = self.LoadEmbeddedFile(embed_path, path_loc)
314 if c_parser is None:
315 return 1 # error was already shown
316
317 return self._SourceExec(cmd_val, arg_r, load_path, c_parser)
318
319 else:
320 # 'source' respects $PATH
321 resolved = self.search_path.LookupOne(path_arg,
322 exec_required=False)
323 if resolved is None:
324 resolved = path_arg
325
326 f, c_parser = self._LoadDiskFile(resolved, path_loc)
327 if c_parser is None:
328 return 1 # error was already shown
329
330 with process.ctx_FileCloser(f):
331 return self._SourceExec(cmd_val, arg_r, path_arg, c_parser)
332
333 raise AssertionError()
334
335 def _BindNames(self, module_obj, module_name, pick_names, pick_locs):
336 # type: (Obj, str, Optional[List[str]], Optional[List[CompoundWord]]) -> int
337 state.SetGlobalValue(self.mem, module_name, module_obj)
338
339 if pick_names is None:
340 return 0
341
342 for i, name in enumerate(pick_names):
343 val = module_obj.d.get(name)
344 # ctx_ModuleEval ensures this
345 if val is None:
346 # note: could be more precise
347 self.errfmt.Print_("use: module doesn't provide name %r" %
348 name,
349 blame_loc=pick_locs[i])
350 return 1
351 state.SetGlobalValue(self.mem, name, val)
352 return 0
353
354 def _Use(self, cmd_val):
355 # type: (cmd_value.Argv) -> int
356 """
357 Module system with all the power of Python, but still a proc
358
359 use util.ysh # util is a value.Obj
360
361 # Importing a bunch of words
362 use dialect-ninja.ysh --all-provided
363 use dialect-github.ysh --all-provided
364
365 # This declares some names
366 use --extern grep sed
367
368 # Renaming
369 use util.ysh (&myutil)
370
371 # Ignore
372 use util.ysh (&_)
373
374 # Picking specifics
375 use util.ysh --names log die
376
377 # Rename
378 var mylog = log
379 """
380 attrs, arg_r = flag_util.ParseCmdVal('use', cmd_val)
381 arg = arg_types.use(attrs.attrs)
382
383 # Accepts any args
384 if arg.extern_: # use --extern grep # no-op for static analysis
385 return 0
386
387 path_arg, path_loc = arg_r.ReadRequired2('requires a module path')
388
389 pick_names = None # type: Optional[List[str]]
390 pick_locs = None # type: Optional[List[CompoundWord]]
391
392 # There is only one flag
393 flag, flag_loc = arg_r.Peek2()
394 if flag is not None:
395 if flag == '--pick':
396 arg_r.Next()
397 p = arg_r.Peek()
398 if p is None:
399 raise error.Usage('with --pick expects one or more names',
400 flag_loc)
401 pick_names, pick_locs = arg_r.Rest2()
402
403 elif flag == '--all-provided':
404 arg_r.Next()
405 arg_r.Done()
406 print('TODO: --all-provided not implemented')
407
408 elif flag == '--all-for-testing':
409 arg_r.Next()
410 arg_r.Done()
411 print('TODO: --all-for testing not implemented')
412
413 else:
414 raise error.Usage(
415 'expected flag like --pick after module path', flag_loc)
416
417 # Similar logic as 'source'
418 if path_arg.startswith('///'):
419 embed_path = path_arg[3:]
420 else:
421 embed_path = None
422
423 if self.mem.InsideFunction():
424 raise error.Usage("may only be used at the top level", path_loc)
425
426 # Important, consider:
427 # use symlink.ysh # where symlink.ysh -> realfile.ysh
428 #
429 # Then the cache key would be '/some/path/realfile.ysh'
430 # But the variable name bound is 'symlink'
431 var_name = _VarName(path_arg)
432 #log('var %s', var_name)
433
434 if embed_path is not None:
435 # Embedded modules are cached using /// path as cache key
436 cached_obj = self._embed_cache.get(embed_path)
437 if cached_obj:
438 return self._BindNames(cached_obj, var_name, pick_names,
439 pick_locs)
440
441 load_path, c_parser = self.LoadEmbeddedFile(embed_path, path_loc)
442 if c_parser is None:
443 return 1 # error was already shown
444
445 module_obj = self._NewModule()
446
447 # Cache BEFORE executing, to prevent circular import
448 self._embed_cache[embed_path] = module_obj
449
450 status = self._UseExec(cmd_val, load_path, path_loc, c_parser,
451 module_obj.d)
452 if status != 0:
453 return status
454
455 return self._BindNames(module_obj, var_name, pick_names, pick_locs)
456
457 else:
458 normalized = libc.realpath(path_arg)
459 if normalized is None:
460 self.errfmt.Print_("use: couldn't find %r" % path_arg,
461 blame_loc=path_loc)
462 return 1
463
464 # Disk modules are cached using normalized path as cache key
465 cached_obj = self._disk_cache.get(normalized)
466 if cached_obj:
467 return self._BindNames(cached_obj, var_name, pick_names,
468 pick_locs)
469
470 f, c_parser = self._LoadDiskFile(normalized, path_loc)
471 if c_parser is None:
472 return 1 # error was already shown
473
474 module_obj = self._NewModule()
475
476 # Cache BEFORE executing, to prevent circular import
477 self._disk_cache[normalized] = module_obj
478
479 with process.ctx_FileCloser(f):
480 status = self._UseExec(cmd_val, path_arg, path_loc, c_parser,
481 module_obj.d)
482 if status != 0:
483 return status
484
485 return self._BindNames(module_obj, var_name, pick_names, pick_locs)
486
487 return 0
488
489
490def _PrintFreeForm(row):
491 # type: (Tuple[str, str, Optional[str]]) -> None
492 name, kind, detail = row
493
494 if kind == 'file':
495 what = detail # file path
496 elif kind == 'alias':
497 what = ('an alias for %s' %
498 j8_lite.EncodeString(detail, unquoted_ok=True))
499 elif kind in ('proc', 'invokable'):
500 # Note: haynode should be an invokable
501 what = 'a YSH %s' % kind
502 elif kind == 'builtin':
503 if detail is None:
504 prefix = ''
505 elif detail == 's':
506 prefix = 'special '
507 elif detail == 'p':
508 # printed in invoke --show, but not type, because it's not the
509 # first word
510 prefix = 'private '
511 else:
512 raise AssertionError()
513 what = 'a %sshell %s' % (prefix, kind)
514 else: # function, keyword
515 what = 'a shell %s' % kind
516
517 print('%s is %s' % (name, what))
518
519 # if kind == 'function':
520 # bash is the only shell that prints the function
521
522
523def _PrintEntry(arg, row):
524 # type: (arg_types.type, Tuple[str, str, Optional[str]]) -> None
525 """For type builtin"""
526
527 _, kind, detail = row
528 assert kind is not None
529
530 if arg.t: # short string
531 print(kind)
532
533 elif arg.p:
534 #log('%s %s %s', name, kind, resolved)
535 if kind == 'file':
536 print(detail) # print the file path
537
538 else: # free-form text
539 _PrintFreeForm(row)
540
541
542class Command(vm._Builtin):
543 """'command ls' suppresses function lookup."""
544
545 def __init__(
546 self,
547 shell_ex, # type: vm._Executor
548 procs, # type: state.Procs
549 aliases, # type: Dict[str, str]
550 search_path, # type: executor.SearchPath
551 ):
552 # type: (...) -> None
553 self.shell_ex = shell_ex
554 self.procs = procs
555 self.aliases = aliases
556 self.search_path = search_path
557
558 def Run(self, cmd_val):
559 # type: (cmd_value.Argv) -> int
560
561 # accept_typed_args=True because we invoke other builtins
562 attrs, arg_r = flag_util.ParseCmdVal('command',
563 cmd_val,
564 accept_typed_args=True)
565 arg = arg_types.command(attrs.attrs)
566
567 argv, locs = arg_r.Rest2()
568
569 if arg.v or arg.V:
570 status = 0
571 for argument in argv:
572 r = _ResolveName(argument, self.procs, self.aliases,
573 self.search_path, False)
574 if len(r):
575 # Print only the first occurrence
576 row = r[0]
577 if arg.v:
578 name, kind, detail = row
579 if kind == 'file':
580 print(detail) # /usr/bin/awk
581 else:
582 print(name) # myfunc
583 else:
584 _PrintFreeForm(row)
585 else:
586 # match bash behavior by printing to stderr
587 print_stderr('%s: not found' % argument)
588 status = 1 # nothing printed, but we fail
589
590 return status
591
592 cmd_val2 = cmd_value.Argv(argv, locs, cmd_val.is_last_cmd,
593 cmd_val.self_obj, cmd_val.proc_args)
594
595 cmd_st = CommandStatus.CreateNull(alloc_lists=True)
596
597 # If we respected do_fork here instead of passing DO_FORK
598 # unconditionally, the case 'command date | wc -l' would take 2
599 # processes instead of 3. See test/syscall
600 run_flags = executor.NO_CALL_PROCS
601 if cmd_val.is_last_cmd:
602 run_flags |= executor.IS_LAST_CMD
603 if arg.p:
604 run_flags |= executor.USE_DEFAULT_PATH
605
606 return self.shell_ex.RunSimpleCommand(cmd_val2, cmd_st, run_flags)
607
608
609def _ShiftArgv(cmd_val):
610 # type: (cmd_value.Argv) -> cmd_value.Argv
611 return cmd_value.Argv(cmd_val.argv[1:], cmd_val.arg_locs[1:],
612 cmd_val.is_last_cmd, cmd_val.self_obj,
613 cmd_val.proc_args)
614
615
616class Builtin(vm._Builtin):
617
618 def __init__(self, shell_ex, errfmt):
619 # type: (vm._Executor, ui.ErrorFormatter) -> None
620 self.shell_ex = shell_ex
621 self.errfmt = errfmt
622
623 def Run(self, cmd_val):
624 # type: (cmd_value.Argv) -> int
625 attrs, arg_r = flag_util.ParseCmdVal('builtin',
626 cmd_val,
627 accept_typed_args=True)
628 argv, locs = arg_r.Rest2()
629
630 if len(argv) == 0:
631 return 0 # this could be an error in strict mode?
632
633 name = argv[0]
634
635 to_run = consts.LookupNormalBuiltin(name) # builtin true
636
637 if to_run == consts.NO_INDEX: # builtin eval
638 to_run = consts.LookupSpecialBuiltin(name)
639
640 if to_run == consts.NO_INDEX: # builtin sleep
641 # Note that plain 'sleep' doesn't work
642 to_run = consts.LookupPrivateBuiltin(name)
643
644 if to_run == consts.NO_INDEX: # error
645 location = locs[0]
646 if consts.LookupAssignBuiltin(name) != consts.NO_INDEX:
647 # NOTE: core/executor.py has a similar restriction for 'command'
648 self.errfmt.Print_("'builtin' can't run assignment builtin",
649 blame_loc=location)
650 else:
651 self.errfmt.Print_("%r isn't a shell builtin" % name,
652 blame_loc=location)
653 return 1
654
655 cmd_val2 = _ShiftArgv(cmd_val)
656 return self.shell_ex.RunBuiltin(to_run, cmd_val2)
657
658
659class RunProc(vm._Builtin):
660
661 def __init__(self, shell_ex, procs, errfmt):
662 # type: (vm._Executor, state.Procs, ui.ErrorFormatter) -> None
663 self.shell_ex = shell_ex
664 self.procs = procs
665 self.errfmt = errfmt
666
667 def Run(self, cmd_val):
668 # type: (cmd_value.Argv) -> int
669 _, arg_r = flag_util.ParseCmdVal('runproc',
670 cmd_val,
671 accept_typed_args=True)
672 argv, locs = arg_r.Rest2()
673
674 if len(argv) == 0:
675 raise error.Usage('requires arguments', loc.Missing)
676
677 name = argv[0]
678 proc, _ = self.procs.GetInvokable(name)
679 if not proc:
680 # note: should runproc be invoke?
681 self.errfmt.PrintMessage('runproc: no invokable named %r' % name)
682 return 1
683
684 cmd_val2 = cmd_value.Argv(argv, locs, cmd_val.is_last_cmd,
685 cmd_val.self_obj, cmd_val.proc_args)
686
687 cmd_st = CommandStatus.CreateNull(alloc_lists=True)
688 run_flags = executor.IS_LAST_CMD if cmd_val.is_last_cmd else 0
689 return self.shell_ex.RunSimpleCommand(cmd_val2, cmd_st, run_flags)
690
691
692class Invoke(vm._Builtin):
693 """Invoke a command, controlling the resolution of the first word."""
694 def __init__(
695 self,
696 shell_ex, # type: vm._Executor
697 procs, # type: state.Procs
698 aliases, # type: Dict[str, str]
699 search_path, # type: executor.SearchPath
700 errfmt, # type: ui.ErrorFormatter
701 ):
702 # type: (...) -> None
703 self.shell_ex = shell_ex
704 self.procs = procs
705 self.aliases = aliases
706 self.search_path = search_path
707 self.errfmt = errfmt
708
709 def Run(self, cmd_val):
710 # type: (cmd_value.Argv) -> int
711 attrs, arg_r = flag_util.ParseCmdVal('invoke',
712 cmd_val,
713 accept_typed_args=True)
714 arg = arg_types.invoke(attrs.attrs)
715 argv, locs = arg_r.Rest2()
716
717 if len(argv) == 0:
718 raise error.Usage('expected arguments', cmd_val.arg_locs[0])
719
720 if arg.show:
721 for name in argv:
722 r = _ResolveName(name,
723 self.procs,
724 self.aliases,
725 self.search_path,
726 True,
727 do_private=True)
728 for row in r:
729 # TODO: _PrintRow instead
730 # Should it print shell keywords and aliases? Even if they
731 # can't be invoked?
732 # Yes because 'time' is a keyword, and external
733 _PrintFreeForm(row)
734 return 0
735
736 cmd_val2 = cmd_value.Argv(argv, locs, cmd_val.is_last_cmd,
737 cmd_val.self_obj, cmd_val.proc_args)
738
739 name = argv[0]
740 location = locs[0]
741 to_run = consts.LookupPrivateBuiltin(name)
742 if to_run == consts.NO_INDEX:
743 self.errfmt.Print_("%r isn't a private builtin" % name,
744 blame_loc=location)
745 return 1
746
747 if arg.private_:
748 return self.shell_ex.RunBuiltin(to_run, cmd_val2)
749
750 # TODO:
751 if arg.builtin:
752 pass
753 if arg.proc_like:
754 pass
755
756 # Special considerations:
757 # - set PATH for lookup
758 # - set ENV for external process with a DICT
759 # - that's similar to ENV, but it's a YSH thing
760 # - ambiguity:
761 # - invoke --extern ls (ENV) # i
762 if arg.extern_:
763 pass
764
765 return 0
766
767
768class Extern(vm._Builtin):
769
770 def __init__(self, shell_ex, procs, errfmt):
771 # type: (vm._Executor, state.Procs, ui.ErrorFormatter) -> None
772 self.shell_ex = shell_ex
773 self.procs = procs
774 self.errfmt = errfmt
775
776 def Run(self, cmd_val):
777 # type: (cmd_value.Argv) -> int
778 _, arg_r = flag_util.ParseCmdVal('extern',
779 cmd_val,
780 accept_typed_args=True)
781 #argv, locs = arg_r.Rest2()
782
783 print('TODO: extern')
784
785 return 0
786
787
788def _ResolveName(
789 name, # type: str
790 procs, # type: state.Procs
791 aliases, # type: Dict[str, str]
792 search_path, # type: executor.SearchPath
793 do_all, # type: bool
794 do_private=False, # type: bool
795):
796 # type: (...) -> List[Tuple[str, str, Optional[str]]]
797 """
798 Returns:
799 A list of (name, type, optional arg arg)
800
801 When type == 'file', arg is the path
802 When type == 'builtin', arg is 's' for special, or None otherwise
803
804 TODO: All of these could be in YSH:
805
806 type, type -t, type -a
807 pp proc
808
809 We could builtin functions like isShellFunc() and isInvokableObj()
810 """
811 # MyPy tuple type
812 no_str = None # type: Optional[str]
813
814 results = [] # type: List[Tuple[str, str, Optional[str]]]
815
816 if procs:
817 if procs.IsShellFunc(name):
818 results.append((name, 'function', no_str))
819
820 if procs.IsProc(name):
821 results.append((name, 'proc', no_str))
822 elif procs.IsInvokableObj(name): # can't be both proc and obj
823 results.append((name, 'invokable', no_str))
824
825 if name in aliases:
826 results.append((name, 'alias', aliases[name]))
827
828 # See if it's a builtin
829 if consts.LookupNormalBuiltin(name) != 0:
830 results.append((name, 'builtin', no_str))
831 elif consts.LookupSpecialBuiltin(name) != 0:
832 results.append((name, 'builtin', 's'))
833 elif consts.LookupAssignBuiltin(name) != 0:
834 results.append((name, 'builtin', 's'))
835 elif do_private and consts.LookupPrivateBuiltin(name) != 0:
836 results.append((name, 'builtin', 'p'))
837
838 # See if it's a keyword
839 if consts.IsControlFlow(name): # continue, etc.
840 results.append((name, 'keyword', no_str))
841 elif consts.IsKeyword(name):
842 results.append((name, 'keyword', no_str))
843
844 # See if it's external
845 for path in search_path.LookupReflect(name, do_all):
846 if posix.access(path, X_OK):
847 results.append((name, 'file', path))
848
849 return results
850
851
852class Type(vm._Builtin):
853
854 def __init__(
855 self,
856 procs, # type: state.Procs
857 aliases, # type: Dict[str, str]
858 search_path, # type: executor.SearchPath
859 errfmt, # type: ui.ErrorFormatter
860 ):
861 # type: (...) -> None
862 self.procs = procs
863 self.aliases = aliases
864 self.search_path = search_path
865 self.errfmt = errfmt
866
867 def Run(self, cmd_val):
868 # type: (cmd_value.Argv) -> int
869 attrs, arg_r = flag_util.ParseCmdVal('type', cmd_val)
870 arg = arg_types.type(attrs.attrs)
871
872 if arg.f: # suppress function lookup
873 procs = None # type: state.Procs
874 else:
875 procs = self.procs
876
877 status = 0
878 names = arg_r.Rest()
879
880 if arg.P: # -P should forces PATH search, regardless of builtin/alias/function/etc.
881 for name in names:
882 paths = self.search_path.LookupReflect(name, arg.a)
883 if len(paths):
884 for path in paths:
885 print(path)
886 else:
887 status = 1
888 return status
889
890 for argument in names:
891 r = _ResolveName(argument, procs, self.aliases, self.search_path,
892 arg.a)
893 if arg.a:
894 for row in r:
895 _PrintEntry(arg, row)
896 else:
897 if len(r): # Just print the first one
898 _PrintEntry(arg, r[0])
899
900 # Error case
901 if len(r) == 0:
902 if not arg.t: # 'type -t' is silent in this case
903 # match bash behavior by printing to stderr
904 print_stderr('%s: not found' % argument)
905 status = 1 # nothing printed, but we fail
906
907 return status