def DoRedirect(self, node, local_symbols): #print(node, file=sys.stderr) op_spid = node.op.span_id op_id = node.op.id self.cursor.PrintUntil(op_spid) # TODO: # - Do < and <& the same way. # - How to handle here docs and here docs? # - >> becomes >+ or >-, or maybe >>> #if node.tag == redir_e.Redir: if False: if node.fd == runtime.NO_SPID: if op_id == Id.Redir_Great: self.f.write('>') # Allow us to replace the operator self.cursor.SkipUntil(op_spid + 1) elif op_id == Id.Redir_GreatAnd: self.f.write('> !') # Replace >& 2 with > !2 spid = word_.LeftMostSpanForWord(node.arg_word) self.cursor.SkipUntil(spid) #self.DoWordInCommand(node.arg_word) else: # NOTE: Spacing like !2>err.txt vs !2 > err.txt can be done in the # formatter. self.f.write('!%d ' % node.fd) if op_id == Id.Redir_Great: self.f.write('>') self.cursor.SkipUntil(op_spid + 1) elif op_id == Id.Redir_GreatAnd: self.f.write('> !') # Replace 1>& 2 with !1 > !2 spid = word_.LeftMostSpanForWord(node.arg_word) self.cursor.SkipUntil(spid) self.DoWordInCommand(node.arg_word, local_symbols) #elif node.tag == redir_e.HereDoc: elif False: ok, delimiter, delim_quoted = word_.StaticEval(node.here_begin) if not ok: p_die('Invalid here doc delimiter', word=node.here_begin) # Turn everything into <<. We just change the quotes self.f.write('<<') #here_begin_spid2 = word_.RightMostSpanForWord(node.here_begin) if delim_quoted: self.f.write(" '''") else: self.f.write(' """') delim_end_spid = word_.RightMostSpanForWord(node.here_begin) self.cursor.SkipUntil(delim_end_spid + 1) #self.cursor.SkipUntil(here_begin_spid + 1) # Now print the lines. TODO: Have a flag to indent these to the level of # the owning command, e.g. # cat <<EOF # EOF # Or since most here docs are the top level, you could just have a hack # for a fixed indent? TODO: Look at real use cases. for part in node.stdin_parts: self.DoWordPart(part, local_symbols) self.cursor.SkipUntil(node.here_end_span_id + 1) if delim_quoted: self.f.write("'''\n") else: self.f.write('"""\n') # Need #self.cursor.SkipUntil(here_end_spid2) else: raise AssertionError(node.__class__.__name__) # <<< 'here word' # << 'here word' # # 2> out.txt # !2 > out.txt # cat 1<< EOF # hello $name # EOF # cat !1 << """ # hello $name # """ # # cat << 'EOF' # no expansion # EOF # cat <<- 'EOF' # no expansion and indented # # cat << ''' # no expansion # ''' # cat << ''' # no expansion and indented # ''' # Warn about multiple here docs on a line. # As an obscure feature, allow # cat << \'ONE' << \"TWO" # 123 # ONE # 234 # TWO # The _ is an indicator that it's not a string to be piped in. pass
def Eval(self, line): """Returns an expanded line.""" if not self.readline_mod: return line tokens = list(HISTORY_LEXER.Tokens(line)) # Common case: no history expansion. if all(id_ == Id.History_Other for (id_, _) in tokens): return line history_len = self.readline_mod.get_current_history_length() if history_len <= 0: # no commands to expand return line self.debug_f.log('history length = %d', history_len) parts = [] for id_, val in tokens: if id_ == Id.History_Other: out = val elif id_ == Id.History_Op: prev = self.readline_mod.get_history_item(history_len) ch = val[1] if ch == '!': out = prev else: self.parse_ctx.trail.Clear() # not strictly necessary? line_reader = reader.StringLineReader( prev, self.parse_ctx.arena) c_parser = self.parse_ctx.MakeOshParser(line_reader) try: c_parser.ParseLogicalLine() except util.ParseError as e: #from core import ui #ui.PrettyPrintError(e, self.parse_ctx.arena) # Invalid command in history. TODO: We should never enter these. self.debug_f.log( "Couldn't parse historical command %r: %s", prev, e) # NOTE: We're using the trail rather than the return value of # ParseLogicalLine because it handles cases like # $ for i in 1 2 3; do sleep ${i}; done # $ echo !$ # which should expand to 'echo ${i}' words = self.parse_ctx.trail.words #self.debug_f.log('TRAIL WORDS: %s', words) if ch == '^': try: w = words[1] except IndexError: raise util.HistoryError("No first word in %r", prev) spid1 = word_.LeftMostSpanForWord(w) spid2 = word_.RightMostSpanForWord(w) elif ch == '$': try: w = words[-1] except IndexError: raise util.HistoryError("No last word in %r", prev) spid1 = word_.LeftMostSpanForWord(w) spid2 = word_.RightMostSpanForWord(w) elif ch == '*': try: w1 = words[1] w2 = words[-1] except IndexError: raise util.HistoryError( "Couldn't find words in %r", prev) spid1 = word_.LeftMostSpanForWord(w1) spid2 = word_.RightMostSpanForWord(w2) else: raise AssertionError(ch) arena = self.parse_ctx.arena span1 = arena.GetLineSpan(spid1) span2 = arena.GetLineSpan(spid2) begin = span1.col end = span2.col + span2.length out = prev[begin:end] elif id_ == Id.History_Num: index = int( val[1:]) # regex ensures this. Maybe have - on the front. if index < 0: num = history_len + 1 + index else: num = index out = self.readline_mod.get_history_item(num) if out is None: # out of range raise util.HistoryError('%s: not found', val) elif id_ == Id.History_Search: # Remove the required space at the end and save it. A simple hack than # the one bash has. last_char = val[-1] val = val[:-1] # Search backward prefix = None substring = None if val[1] == '?': substring = val[2:] else: prefix = val[1:] out = None for i in xrange(history_len, 1, -1): cmd = self.readline_mod.get_history_item(i) if prefix and cmd.startswith(prefix): out = cmd if substring and substring in cmd: out = cmd if out is not None: out += last_char # restore required space break if out is None: raise util.HistoryError('%r found no results', val) else: raise AssertionError(id_) parts.append(out) line = ''.join(parts) # show what we expanded to sys.stdout.write('! %s' % line) return line