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

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