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

856 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 # TODO: Add debug_frame here, with ctx_Eval or ctx_EvalDebugFrame
94 src = source.Dynamic('eval arg', eval_loc)
95 with dev.ctx_Tracer(self.tracer, 'eval', None):
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):
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, props, error_strs):
268 with state.ctx_ThisDir(self.mem, path):
269 src = source.OtherFile(path, path_loc)
270 with alloc.ctx_SourceCode(self.arena, src):
271 try:
272 status = main_loop.Batch(
273 self.cmd_ev,
274 c_parser,
275 self.errfmt,
276 cmd_flags=cmd_eval.RaiseControlFlow)
277 except vm.IntControlFlow as e:
278 if e.IsReturn():
279 status = e.StatusCode()
280 else:
281 raise
282 if status != 0:
283 return status
284 #e_die("'use' failed 2", path_loc)
285
286 if len(error_strs):
287 for s in error_strs:
288 self.errfmt.PrintMessage('Error: %s' % s, path_loc)
289 return 1
290
291 return 0
292
293 def _Source(self, cmd_val):
294 # type: (cmd_value.Argv) -> int
295 attrs, arg_r = flag_util.ParseCmdVal('source', cmd_val)
296 arg = arg_types.source(attrs.attrs)
297
298 path_arg, path_loc = arg_r.ReadRequired2('requires a file path')
299
300 # Old:
301 # source --builtin two.sh # looks up stdlib/two.sh
302 # New:
303 # source $LIB_OSH/two.sh # looks up stdlib/osh/two.sh
304 # source ///osh/two.sh # looks up stdlib/osh/two.sh
305 embed_path = None # type: Optional[str]
306 if arg.builtin:
307 embed_path = path_arg
308 elif path_arg.startswith('///'):
309 embed_path = path_arg[3:]
310
311 if embed_path is not None:
312 load_path, c_parser = self.LoadEmbeddedFile(embed_path, path_loc)
313 if c_parser is None:
314 return 1 # error was already shown
315
316 return self._SourceExec(cmd_val, arg_r, load_path, c_parser)
317
318 else:
319 # 'source' respects $PATH
320 resolved = self.search_path.LookupOne(path_arg,
321 exec_required=False)
322 if resolved is None:
323 resolved = path_arg
324
325 f, c_parser = self._LoadDiskFile(resolved, path_loc)
326 if c_parser is None:
327 return 1 # error was already shown
328
329 with process.ctx_FileCloser(f):
330 return self._SourceExec(cmd_val, arg_r, path_arg, c_parser)
331
332 raise AssertionError()
333
334 def _BindNames(self, module_obj, module_name, pick_names, pick_locs):
335 # type: (Obj, str, Optional[List[str]], Optional[List[CompoundWord]]) -> int
336 state.SetGlobalValue(self.mem, module_name, module_obj)
337
338 if pick_names is None:
339 return 0
340
341 for i, name in enumerate(pick_names):
342 val = module_obj.d.get(name)
343 # ctx_ModuleEval ensures this
344 if val is None:
345 # note: could be more precise
346 self.errfmt.Print_("use: module doesn't provide name %r" %
347 name,
348 blame_loc=pick_locs[i])
349 return 1
350 state.SetGlobalValue(self.mem, name, val)
351 return 0
352
353 def _Use(self, cmd_val):
354 # type: (cmd_value.Argv) -> int
355 """
356 Module system with all the power of Python, but still a proc
357
358 use util.ysh # util is a value.Obj
359
360 # Importing a bunch of words
361 use dialect-ninja.ysh --all-provided
362 use dialect-github.ysh --all-provided
363
364 # This declares some names
365 use --extern grep sed
366
367 # Renaming
368 use util.ysh (&myutil)
369
370 # Ignore
371 use util.ysh (&_)
372
373 # Picking specifics
374 use util.ysh --names log die
375
376 # Rename
377 var mylog = log
378 """
379 attrs, arg_r = flag_util.ParseCmdVal('use', cmd_val)
380 arg = arg_types.use(attrs.attrs)
381
382 # Accepts any args
383 if arg.extern_: # use --extern grep # no-op for static analysis
384 return 0
385
386 path_arg, path_loc = arg_r.ReadRequired2('requires a module path')
387
388 pick_names = None # type: Optional[List[str]]
389 pick_locs = None # type: Optional[List[CompoundWord]]
390
391 # There is only one flag
392 flag, flag_loc = arg_r.Peek2()
393 if flag is not None:
394 if flag == '--pick':
395 arg_r.Next()
396 p = arg_r.Peek()
397 if p is None:
398 raise error.Usage('with --pick expects one or more names',
399 flag_loc)
400 pick_names, pick_locs = arg_r.Rest2()
401
402 elif flag == '--all-provided':
403 arg_r.Next()
404 arg_r.Done()
405 print('TODO: --all-provided not implemented')
406
407 elif flag == '--all-for-testing':
408 arg_r.Next()
409 arg_r.Done()
410 print('TODO: --all-for testing not implemented')
411
412 else:
413 raise error.Usage(
414 'expected flag like --pick after module path', flag_loc)
415
416 # Similar logic as 'source'
417 if path_arg.startswith('///'):
418 embed_path = path_arg[3:]
419 else:
420 embed_path = None
421
422 if self.mem.InsideFunction():
423 raise error.Usage("may only be used at the top level", path_loc)
424
425 # Important, consider:
426 # use symlink.ysh # where symlink.ysh -> realfile.ysh
427 #
428 # Then the cache key would be '/some/path/realfile.ysh'
429 # But the variable name bound is 'symlink'
430 var_name = _VarName(path_arg)
431 #log('var %s', var_name)
432
433 if embed_path is not None:
434 # Embedded modules are cached using /// path as cache key
435 cached_obj = self._embed_cache.get(embed_path)
436 if cached_obj:
437 return self._BindNames(cached_obj, var_name, pick_names,
438 pick_locs)
439
440 load_path, c_parser = self.LoadEmbeddedFile(embed_path, path_loc)
441 if c_parser is None:
442 return 1 # error was already shown
443
444 module_obj = self._NewModule()
445
446 # Cache BEFORE executing, to prevent circular import
447 self._embed_cache[embed_path] = module_obj
448
449 status = self._UseExec(cmd_val, load_path, path_loc, c_parser,
450 module_obj.d)
451 if status != 0:
452 return status
453
454 return self._BindNames(module_obj, var_name, pick_names, pick_locs)
455
456 else:
457 normalized = libc.realpath(path_arg)
458 if normalized is None:
459 self.errfmt.Print_("use: couldn't find %r" % path_arg,
460 blame_loc=path_loc)
461 return 1
462
463 # Disk modules are cached using normalized path as cache key
464 cached_obj = self._disk_cache.get(normalized)
465 if cached_obj:
466 return self._BindNames(cached_obj, var_name, pick_names,
467 pick_locs)
468
469 f, c_parser = self._LoadDiskFile(normalized, path_loc)
470 if c_parser is None:
471 return 1 # error was already shown
472
473 module_obj = self._NewModule()
474
475 # Cache BEFORE executing, to prevent circular import
476 self._disk_cache[normalized] = module_obj
477
478 with process.ctx_FileCloser(f):
479 status = self._UseExec(cmd_val, path_arg, path_loc, c_parser,
480 module_obj.d)
481 if status != 0:
482 return status
483
484 return self._BindNames(module_obj, var_name, pick_names, pick_locs)
485
486 return 0
487
488
489def _PrintFreeForm(row):
490 # type: (Tuple[str, str, Optional[str]]) -> None
491 name, kind, resolved = row
492
493 if kind == 'file':
494 what = resolved
495 elif kind == 'alias':
496 what = ('an alias for %s' %
497 j8_lite.EncodeString(resolved, unquoted_ok=True))
498 elif kind in ('proc', 'invokable'):
499 # Note: haynode should be an invokable
500 what = 'a YSH %s' % kind
501 else: # builtin, function, keyword
502 what = 'a shell %s' % kind
503
504 print('%s is %s' % (name, what))
505
506 # if kind == 'function':
507 # bash is the only shell that prints the function
508
509
510def _PrintEntry(arg, row):
511 # type: (arg_types.type, Tuple[str, str, Optional[str]]) -> None
512
513 _, kind, resolved = row
514 assert kind is not None
515
516 if arg.t: # short string
517 print(kind)
518
519 elif arg.p:
520 #log('%s %s %s', name, kind, resolved)
521 if kind == 'file':
522 print(resolved)
523
524 else: # free-form text
525 _PrintFreeForm(row)
526
527
528class Command(vm._Builtin):
529 """'command ls' suppresses function lookup."""
530
531 def __init__(
532 self,
533 shell_ex, # type: vm._Executor
534 funcs, # type: state.Procs
535 aliases, # type: Dict[str, str]
536 search_path, # type: executor.SearchPath
537 ):
538 # type: (...) -> None
539 self.shell_ex = shell_ex
540 self.funcs = funcs
541 self.aliases = aliases
542 self.search_path = search_path
543
544 def Run(self, cmd_val):
545 # type: (cmd_value.Argv) -> int
546
547 # accept_typed_args=True because we invoke other builtins
548 attrs, arg_r = flag_util.ParseCmdVal('command',
549 cmd_val,
550 accept_typed_args=True)
551 arg = arg_types.command(attrs.attrs)
552
553 argv, locs = arg_r.Rest2()
554
555 if arg.v or arg.V:
556 status = 0
557 for argument in argv:
558 r = _ResolveName(argument, self.funcs, self.aliases,
559 self.search_path, False)
560 if len(r):
561 # Print only the first occurrence
562 row = r[0]
563 if arg.v:
564 name, _, path = row
565 if path is not None:
566 print(path) # /usr/bin/awk
567 else:
568 print(name) # myfunc
569 else:
570 _PrintFreeForm(row)
571 else:
572 # match bash behavior by printing to stderr
573 print_stderr('%s: not found' % argument)
574 status = 1 # nothing printed, but we fail
575
576 return status
577
578 cmd_val2 = cmd_value.Argv(argv, locs, cmd_val.is_last_cmd,
579 cmd_val.self_obj, cmd_val.proc_args)
580
581 cmd_st = CommandStatus.CreateNull(alloc_lists=True)
582
583 # If we respected do_fork here instead of passing DO_FORK
584 # unconditionally, the case 'command date | wc -l' would take 2
585 # processes instead of 3. See test/syscall
586 run_flags = executor.NO_CALL_PROCS
587 if cmd_val.is_last_cmd:
588 run_flags |= executor.IS_LAST_CMD
589 if arg.p:
590 run_flags |= executor.USE_DEFAULT_PATH
591
592 return self.shell_ex.RunSimpleCommand(cmd_val2, cmd_st, run_flags)
593
594
595def _ShiftArgv(cmd_val):
596 # type: (cmd_value.Argv) -> cmd_value.Argv
597 return cmd_value.Argv(cmd_val.argv[1:], cmd_val.arg_locs[1:],
598 cmd_val.is_last_cmd, cmd_val.self_obj,
599 cmd_val.proc_args)
600
601
602class Builtin(vm._Builtin):
603
604 def __init__(self, shell_ex, errfmt):
605 # type: (vm._Executor, ui.ErrorFormatter) -> None
606 self.shell_ex = shell_ex
607 self.errfmt = errfmt
608
609 def Run(self, cmd_val):
610 # type: (cmd_value.Argv) -> int
611
612 if len(cmd_val.argv) == 1:
613 return 0 # this could be an error in strict mode?
614
615 name = cmd_val.argv[1]
616
617 # Run regular builtin or special builtin
618 to_run = consts.LookupNormalBuiltin(name)
619 if to_run == consts.NO_INDEX:
620 to_run = consts.LookupSpecialBuiltin(name)
621 if to_run == consts.NO_INDEX:
622 location = cmd_val.arg_locs[1]
623 if consts.LookupAssignBuiltin(name) != consts.NO_INDEX:
624 # NOTE: There's a similar restriction for 'command'
625 self.errfmt.Print_("Can't run assignment builtin recursively",
626 blame_loc=location)
627 else:
628 self.errfmt.Print_("%r isn't a shell builtin" % name,
629 blame_loc=location)
630 return 1
631
632 cmd_val2 = _ShiftArgv(cmd_val)
633 return self.shell_ex.RunBuiltin(to_run, cmd_val2)
634
635
636class RunProc(vm._Builtin):
637
638 def __init__(self, shell_ex, procs, errfmt):
639 # type: (vm._Executor, state.Procs, ui.ErrorFormatter) -> None
640 self.shell_ex = shell_ex
641 self.procs = procs
642 self.errfmt = errfmt
643
644 def Run(self, cmd_val):
645 # type: (cmd_value.Argv) -> int
646 _, arg_r = flag_util.ParseCmdVal('runproc',
647 cmd_val,
648 accept_typed_args=True)
649 argv, locs = arg_r.Rest2()
650
651 if len(argv) == 0:
652 raise error.Usage('requires arguments', loc.Missing)
653
654 name = argv[0]
655 proc, _ = self.procs.GetInvokable(name)
656 if not proc:
657 # note: should runproc be invoke?
658 self.errfmt.PrintMessage('runproc: no invokable named %r' % name)
659 return 1
660
661 cmd_val2 = cmd_value.Argv(argv, locs, cmd_val.is_last_cmd,
662 cmd_val.self_obj, cmd_val.proc_args)
663
664 cmd_st = CommandStatus.CreateNull(alloc_lists=True)
665 run_flags = executor.IS_LAST_CMD if cmd_val.is_last_cmd else 0
666 return self.shell_ex.RunSimpleCommand(cmd_val2, cmd_st, run_flags)
667
668
669class Invoke(vm._Builtin):
670 """
671 Introspection:
672
673 invoke - YSH introspection on first word
674 type --all - introspection on variables too?
675 - different than = type(x)
676
677 3 Coarsed-grained categories
678 - invoke --builtin aka builtin
679 - including special builtins
680 - invoke --proc-like aka runproc
681 - myproc (42)
682 - sh-func
683 - invokable-obj
684 - invoke --extern aka extern
685
686 Note: If you don't distinguish between proc, sh-func, and invokable-obj,
687 then 'runproc' suffices.
688
689 invoke --proc-like reads more nicely though, and it also combines.
690
691 invoke --builtin --extern # this is like 'command'
692
693 You can also negate:
694
695 invoke --no-proc-like --no-builtin --no-extern
696
697 - type -t also has 'keyword' and 'assign builtin'
698
699 With no args, print a table of what's available
700
701 invoke --builtin
702 invoke --builtin true
703 """
704
705 def __init__(self, shell_ex, procs, errfmt):
706 # type: (vm._Executor, state.Procs, ui.ErrorFormatter) -> None
707 self.shell_ex = shell_ex
708 self.procs = procs
709 self.errfmt = errfmt
710
711 def Run(self, cmd_val):
712 # type: (cmd_value.Argv) -> int
713 _, arg_r = flag_util.ParseCmdVal('invoke',
714 cmd_val,
715 accept_typed_args=True)
716 #argv, locs = arg_r.Rest2()
717
718 print('TODO: invoke')
719 # TODO
720 return 0
721
722
723class Extern(vm._Builtin):
724
725 def __init__(self, shell_ex, procs, errfmt):
726 # type: (vm._Executor, state.Procs, ui.ErrorFormatter) -> None
727 self.shell_ex = shell_ex
728 self.procs = procs
729 self.errfmt = errfmt
730
731 def Run(self, cmd_val):
732 # type: (cmd_value.Argv) -> int
733 _, arg_r = flag_util.ParseCmdVal('extern',
734 cmd_val,
735 accept_typed_args=True)
736 #argv, locs = arg_r.Rest2()
737
738 print('TODO: extern')
739
740 return 0
741
742
743def _ResolveName(
744 name, # type: str
745 procs, # type: state.Procs
746 aliases, # type: Dict[str, str]
747 search_path, # type: executor.SearchPath
748 do_all, # type: bool
749):
750 # type: (...) -> List[Tuple[str, str, Optional[str]]]
751 """
752 Returns:
753 A list of (name, type, optional file system path)
754
755 TODO: All of these could be in YSH:
756
757 type, type -t, type -a
758 pp proc
759
760 We could builtin functions like isShellFunc() and isInvokableObj()
761 """
762 # MyPy tuple type
763 no_str = None # type: Optional[str]
764
765 results = [] # type: List[Tuple[str, str, Optional[str]]]
766
767 if procs:
768 if procs.IsShellFunc(name):
769 results.append((name, 'function', no_str))
770
771 if procs.IsProc(name):
772 results.append((name, 'proc', no_str))
773 elif procs.IsInvokableObj(name): # can't be both proc and obj
774 results.append((name, 'invokable', no_str))
775
776 if name in aliases:
777 results.append((name, 'alias', aliases[name]))
778
779 # See if it's a builtin
780 if consts.LookupNormalBuiltin(name) != 0:
781 results.append((name, 'builtin', no_str))
782 elif consts.LookupSpecialBuiltin(name) != 0:
783 results.append((name, 'builtin', no_str))
784 elif consts.LookupAssignBuiltin(name) != 0:
785 results.append((name, 'builtin', no_str))
786
787 # See if it's a keyword
788 if consts.IsControlFlow(name): # continue, etc.
789 results.append((name, 'keyword', no_str))
790 elif consts.IsKeyword(name):
791 results.append((name, 'keyword', no_str))
792
793 # See if it's external
794 for path in search_path.LookupReflect(name, do_all):
795 if posix.access(path, X_OK):
796 results.append((name, 'file', path))
797
798 return results
799
800
801class Type(vm._Builtin):
802
803 def __init__(
804 self,
805 funcs, # type: state.Procs
806 aliases, # type: Dict[str, str]
807 search_path, # type: executor.SearchPath
808 errfmt, # type: ui.ErrorFormatter
809 ):
810 # type: (...) -> None
811 self.funcs = funcs
812 self.aliases = aliases
813 self.search_path = search_path
814 self.errfmt = errfmt
815
816 def Run(self, cmd_val):
817 # type: (cmd_value.Argv) -> int
818 attrs, arg_r = flag_util.ParseCmdVal('type', cmd_val)
819 arg = arg_types.type(attrs.attrs)
820
821 if arg.f: # suppress function lookup
822 funcs = None # type: state.Procs
823 else:
824 funcs = self.funcs
825
826 status = 0
827 names = arg_r.Rest()
828
829 if arg.P: # -P should forces PATH search, regardless of builtin/alias/function/etc.
830 for name in names:
831 paths = self.search_path.LookupReflect(name, arg.a)
832 if len(paths):
833 for path in paths:
834 print(path)
835 else:
836 status = 1
837 return status
838
839 for argument in names:
840 r = _ResolveName(argument, funcs, self.aliases, self.search_path,
841 arg.a)
842 if arg.a:
843 for row in r:
844 _PrintEntry(arg, row)
845 else:
846 if len(r): # Just print the first one
847 _PrintEntry(arg, r[0])
848
849 # Error case
850 if len(r) == 0:
851 if not arg.t: # 'type -t' is silent in this case
852 # match bash behavior by printing to stderr
853 print_stderr('%s: not found' % argument)
854 status = 1 # nothing printed, but we fail
855
856 return status