OILS / builtin / meta_oils.py View on Github | oilshell.org

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