def __call__(self, argv): arg_r = args.Reader(argv) arg = INIT_COMPLETION_SPEC.Parse(arg_r) var_names = arg_r.Rest() # Output variables to set for name in var_names: # Ironically we could complete these if name not in ['cur', 'prev', 'words', 'cword']: raise args.UsageError('Invalid output variable name %r' % name) #print(arg) # TODO: How does the user test a completion function programmatically? Set # COMP_ARGV? val = self.mem.GetVar('COMP_ARGV') if val.tag != value_e.StrArray: raise args.UsageError("COMP_ARGV should be an array") comp_argv = val.strs # These are the ones from COMP_WORDBREAKS that we care about. The rest occur # "outside" of words. break_chars = [':', '='] if arg.s: # implied break_chars.remove('=') # NOTE: The syntax is -n := and not -n : -n =. omit_chars = arg.n or '' for c in omit_chars: if c in break_chars: break_chars.remove(c) # argv adjusted according to 'break_chars'. adjusted_argv = [] for a in comp_argv: completion.AdjustArg(a, break_chars, adjusted_argv) if 'words' in var_names: state.SetArrayDynamic(self.mem, 'words', adjusted_argv) n = len(adjusted_argv) cur = adjusted_argv[-1] prev = '' if n < 2 else adjusted_argv[-2] if arg.s: if cur.startswith( '--') and '=' in cur: # Split into flag name and value prev, cur = cur.split('=', 1) split = 'true' else: split = 'false' # Do NOT set 'split' without -s. Caller might not have declared it. # Also does not respect var_names, because we don't need it. state.SetStringDynamic(self.mem, 'split', split) if 'cur' in var_names: state.SetStringDynamic(self.mem, 'cur', cur) if 'prev' in var_names: state.SetStringDynamic(self.mem, 'prev', prev) if 'cword' in var_names: # Same weird invariant after adjustment state.SetStringDynamic(self.mem, 'cword', str(n - 1)) return 0
def __call__(self, cmd_val): arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # NOTE: If first char is a colon, error reporting is different. Alpine # might not use that? spec_str = arg_r.ReadRequired('requires an argspec') var_name, var_spid = arg_r.ReadRequired2( 'requires the name of a variable to set') try: spec = self.spec_cache[spec_str] except KeyError: spec = _ParseOptSpec(spec_str) self.spec_cache[spec_str] = spec # These errors are fatal errors, not like the builtin exiting with code 1. # Because the invariants of the shell have been violated! v = self.mem.GetVar('OPTIND') if v.tag != value_e.Str: e_die('OPTIND should be a string, got %r', v) try: optind = int(v.s) except ValueError: e_die("OPTIND doesn't look like an integer, got %r", v.s) user_argv = arg_r.Rest() or self.mem.GetArgv() #util.log('user_argv %s', user_argv) status, opt_char, optarg, optind = _GetOpts(spec, user_argv, optind, self.errfmt) # Bug fix: bash-completion uses a *local* OPTIND ! Not global. state.SetStringDynamic(self.mem, 'OPTARG', optarg) state.SetStringDynamic(self.mem, 'OPTIND', str(optind)) if match.IsValidVarName(var_name): state.SetStringDynamic(self.mem, var_name, opt_char) else: # NOTE: The builtin has PARTIALLY filed. This happens in all shells # except mksh. raise args.UsageError('got invalid variable name %r' % var_name, span_id=var_spid) return status
def GetOpts(argv, mem): """ Vars to set: OPTIND - initialized to 1 at startup OPTARG - argument Vars used: OPTERR: disable printing of error messages """ # TODO: need to handle explicit args. try: # NOTE: If first char is a colon, error reporting is different. Alpine # might not use that? spec_str = argv[0] var_name = argv[1] except IndexError: raise args.UsageError('getopts optstring name [arg]') try: spec = _GETOPTS_CACHE[spec_str] except KeyError: spec = _ParseOptSpec(spec_str) _GETOPTS_CACHE[spec_str] = spec # These errors are fatal errors, not like the builtin exiting with code 1. # Because the invariants of the shell have been violated! v = mem.GetVar('OPTIND') if v.tag != value_e.Str: e_die('OPTIND should be a string, got %r', v) try: optind = int(v.s) except ValueError: e_die("OPTIND doesn't look like an integer, got %r", v.s) user_argv = argv[2:] or mem.GetArgv() status, opt_char, optarg, optind = _GetOpts(spec, user_argv, optind) # Bug fix: bash-completion uses a *local* OPTIND ! Not global. state.SetStringDynamic(mem, var_name, opt_char) state.SetStringDynamic(mem, 'OPTARG', optarg) state.SetStringDynamic(mem, 'OPTIND', str(optind)) return status
def Read(argv, splitter, mem): arg, i = READ_SPEC.Parse(argv) names = argv[i:] if arg.n is not None: # read a certain number of bytes try: name = names[0] except IndexError: name = 'REPLY' # default variable name s = posix.read(sys.stdin.fileno(), arg.n) #log('read -n: %s = %s', name, s) state.SetLocalString(mem, name, s) # NOTE: Even if we don't get n bytes back, there is no error? return 0 if not names: names.append('REPLY') # leftover words assigned to the last name if arg.a: max_results = 0 # no max else: max_results = len(names) # We have to read more than one line if there is a line continuation (and # it's not -r). parts = [] join_next = False while True: line = ReadLineFromStdin() #log('LINE %r', line) if not line: # EOF status = 1 break if line.endswith('\n'): # strip trailing newline line = line[:-1] status = 0 else: # odd bash behavior: fail even if we can set variables. status = 1 spans = splitter.SplitForRead(line, not arg.r) done, join_next = _AppendParts(line, spans, max_results, join_next, parts) #log('PARTS %s continued %s', parts, continued) if done: break if arg.a: state.SetArrayDynamic(mem, arg.a, parts) else: for i in xrange(max_results): try: s = parts[i] except IndexError: s = '' # if there are too many variables #log('read: %s = %s', names[i], s) state.SetStringDynamic(mem, names[i], s) return status
def __call__(self, arg_vec): arg, i = READ_SPEC.ParseVec(arg_vec) names = arg_vec.strs[i:] if arg.n is not None: # read a certain number of bytes stdin = sys.stdin.fileno() try: name = names[0] except IndexError: name = 'REPLY' # default variable name s = "" if sys.stdin.isatty(): # set stdin to read in unbuffered mode orig_attrs = termios.tcgetattr(stdin) attrs = termios.tcgetattr(stdin) # disable canonical (buffered) mode # see `man termios` for an extended discussion attrs[3] &= ~termios.ICANON try: termios.tcsetattr(stdin, termios.TCSANOW, attrs) # posix.read always returns a single character in unbuffered mode while arg.n > 0: s += posix.read(stdin, 1) arg.n -= 1 finally: termios.tcsetattr(stdin, termios.TCSANOW, orig_attrs) else: s_len = 0 while arg.n > 0: buf = posix.read(stdin, arg.n) # EOF if buf == '': break arg.n -= len(buf) s += buf state.SetLocalString(self.mem, name, s) # NOTE: Even if we don't get n bytes back, there is no error? return 0 if not names: names.append('REPLY') # leftover words assigned to the last name if arg.a: max_results = 0 # no max else: max_results = len(names) # We have to read more than one line if there is a line continuation (and # it's not -r). parts = [] join_next = False while True: line = ReadLineFromStdin() #log('LINE %r', line) if not line: # EOF status = 1 break if line.endswith('\n'): # strip trailing newline line = line[:-1] status = 0 else: # odd bash behavior: fail even if we can set variables. status = 1 spans = self.splitter.SplitForRead(line, not arg.r) done, join_next = _AppendParts(line, spans, max_results, join_next, parts) #log('PARTS %s continued %s', parts, continued) if done: break if arg.a: state.SetArrayDynamic(self.mem, arg.a, parts) else: for i in xrange(max_results): try: s = parts[i] except IndexError: s = '' # if there are too many variables #log('read: %s = %s', names[i], s) state.SetStringDynamic(self.mem, names[i], s) return status
def __call__(self, cmd_val): """ printf: printf [-v var] format [argument ...] """ arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # skip argv[0] arg, _ = PRINTF_SPEC.Parse(arg_r) fmt, fmt_spid = arg_r.ReadRequired2('requires a format string') varargs, spids = arg_r.Rest2() #log('fmt %s', fmt) #log('vals %s', vals) arena = self.parse_ctx.arena if fmt in self.parse_cache: parts = self.parse_cache[fmt] else: line_reader = reader.StringLineReader(fmt, arena) # TODO: Make public lexer = self.parse_ctx._MakeLexer(line_reader) p = _FormatStringParser(lexer) arena.PushSource(source.ArgvWord(fmt_spid)) try: parts = p.Parse() except error.Parse as e: self.errfmt.PrettyPrintError(e) return 2 # parse error finally: arena.PopSource() self.parse_cache[fmt] = parts if 0: print() for part in parts: part.PrettyPrint() print() out = [] arg_index = 0 num_args = len(varargs) while True: for part in parts: if isinstance(part, printf_part.Literal): token = part.token if token.id == Id.Format_EscapedPercent: s = '%' else: s = word_compile.EvalCStringToken(token.id, token.val) out.append(s) elif isinstance(part, printf_part.Percent): try: s = varargs[arg_index] word_spid = spids[arg_index] except IndexError: s = '' word_spid = runtime.NO_SPID typ = part.type.val if typ == 's': if part.precision: precision = int(part.precision.val) s = s[:precision] # truncate elif typ == 'q': s = string_ops.ShellQuoteOneLine(s) elif typ in 'diouxX': try: d = int(s) except ValueError: if len(s) >= 2 and s[0] in '\'"': # TODO: utf-8 decode s[1:] to be more correct. Probably # depends on issue #366, a utf-8 library. d = ord(s[1]) else: # This works around the fact that in the arg recycling case, you have no spid. if word_spid == runtime.NO_SPID: self.errfmt.Print( "printf got invalid number %r for this substitution", s, span_id=part.type.span_id) else: self.errfmt.Print( "printf got invalid number %r", s, span_id=word_spid) return 1 if typ in 'di': s = str(d) elif typ in 'ouxX': if d < 0: e_die( "Can't format negative number %d with %%%s", d, typ, span_id=part.type.span_id) if typ == 'u': s = str(d) elif typ == 'o': s = '%o' % d elif typ == 'x': s = '%x' % d elif typ == 'X': s = '%X' % d else: raise AssertionError else: raise AssertionError if part.width: width = int(part.width.val) if part.flag: flag = part.flag.val if flag == '-': s = s.ljust(width, ' ') elif flag == '0': s = s.rjust(width, '0') else: pass else: s = s.rjust(width, ' ') out.append(s) arg_index += 1 else: raise AssertionError if arg_index >= num_args: break # Otherwise there are more args. So cycle through the loop once more to # implement the 'arg recycling' behavior. result = ''.join(out) if arg.v: var_name = arg.v # Notes: # - bash allows a[i] here (as in unset and ${!x}), but we haven't # implemented it. # - TODO: get the span_id for arg.v! if not match.IsValidVarName(var_name): raise args.UsageError('got invalid variable name %r' % var_name) state.SetStringDynamic(self.mem, var_name, result) else: sys.stdout.write(result) return 0
def __call__(self, arg_vec): """ printf: printf [-v var] format [argument ...] """ arg_r = args.Reader(arg_vec.strs, spids=arg_vec.spids) arg_r.Next() # skip argv[0] arg, _ = PRINTF_SPEC.Parse(arg_r) fmt, fmt_spid = arg_r.ReadRequired2('requires a format string') varargs, spids = arg_r.Rest2() #from core.util import log #log('fmt %s', fmt) #log('vals %s', vals) arena = self.parse_ctx.arena if fmt in self.parse_cache: parts = self.parse_cache[fmt] else: line_reader = reader.StringLineReader(fmt, arena) # TODO: Make public lexer = self.parse_ctx._MakeLexer(line_reader) p = _FormatStringParser(lexer) arena.PushSource(source.ArgvWord(fmt_spid)) try: parts = p.Parse() except util.ParseError as e: self.errfmt.PrettyPrintError(e) return 2 # parse error finally: arena.PopSource() self.parse_cache[fmt] = parts if 0: print() for part in parts: part.PrettyPrint() print() out = [] arg_index = 0 num_args = len(varargs) while True: for part in parts: if isinstance(part, printf_part.Literal): token = part.token if token.id == Id.Format_EscapedPercent: s = '%' else: s = word_compile.EvalCStringToken(token.id, token.val) out.append(s) elif isinstance(part, printf_part.Percent): try: s = varargs[arg_index] word_spid = spids[arg_index] except IndexError: s = '' word_spid = const.NO_INTEGER typ = part.type.val if typ == 's': pass # val remains the same elif typ == 'q': s = string_ops.ShellQuoteOneLine(s) elif typ in 'di': try: d = int(s) except ValueError: # This works around the fact that in the arg recycling case, you have no spid. if word_spid == const.NO_INTEGER: self.errfmt.Print( "printf got invalid number %r for this substitution", s, span_id=part.type.span_id) else: self.errfmt.Print( "printf got invalid number %r", s, span_id=word_spid) return 1 s = str(d) else: raise AssertionError if part.width: width = int(part.width.val) if part.flag: flag = part.flag.val if flag == '-': s = s.ljust(width, ' ') elif flag == '0': s = s.rjust(width, '0') else: pass else: s = s.rjust(width, ' ') out.append(s) arg_index += 1 else: raise AssertionError if arg_index >= num_args: break # Otherwise there are more args. So cycle through the loop once more to # implement the 'arg recycling' behavior. result = ''.join(out) if arg.v: var_name = arg.v # Notes: # - bash allows a[i] here (as in unset and ${!x}), but we haven't # implemented it. # - TODO: get the span_id for arg.v! if not match.IsValidVarName(var_name): raise args.UsageError('got invalid variable name %r' % var_name) state.SetStringDynamic(self.mem, var_name, result) else: sys.stdout.write(result) return 0