Kaynağa Gözat

Shell: Allow the heredoc node to act as a redirection too

This will be used in a future commit to implement POSIX sh heredocs.
Ali Mohammad Pur 2 yıl önce
ebeveyn
işleme
4efc632e15

+ 69 - 18
Userland/Shell/AST.cpp

@@ -1366,6 +1366,10 @@ void Heredoc::dump(int level) const
     print_indented(m_end, level + 2);
     print_indented("(Allows Interpolation)"sv, level + 1);
     print_indented(DeprecatedString::formatted("{}", m_allows_interpolation), level + 2);
+    if (!evaluates_to_string()) {
+        print_indented("(Target FD)"sv, level + 1);
+        print_indented(DeprecatedString::number(*m_target_fd), level + 2);
+    }
     print_indented("(Contents)"sv, level + 1);
     if (m_contents)
         m_contents->dump(level + 2);
@@ -1375,29 +1379,75 @@ void Heredoc::dump(int level) const
 
 RefPtr<Value> Heredoc::run(RefPtr<Shell> shell)
 {
-    if (!m_deindent)
-        return m_contents->run(shell);
+    if (!m_contents) {
+        if (shell)
+            shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "Attempt to evaluate an unresolved heredoc"sv, position());
+        return nullptr;
+    }
 
-    // To deindent, first split to lines...
-    auto value = m_contents->run(shell);
-    if (shell && shell->has_any_error())
-        return make_ref_counted<ListValue>({});
+    auto value = [&]() -> RefPtr<Value> {
+        if (!m_deindent)
+            return m_contents->run(shell);
+
+        // To deindent, first split to lines...
+        auto value = m_contents->run(shell);
+        if (shell && shell->has_any_error())
+            return make_ref_counted<ListValue>({});
 
-    if (!value)
+        if (!value)
+            return value;
+        auto list = value->resolve_as_list(shell);
+        // The list better have one entry, otherwise we've put the wrong kind of node inside this heredoc
+        VERIFY(list.size() == 1);
+        auto lines = list.first().split_view('\n');
+
+        // Now just trim each line and put them back in a string
+        StringBuilder builder { list.first().length() };
+        for (auto& line : lines) {
+            builder.append(line.trim_whitespace(TrimMode::Left));
+            builder.append('\n');
+        }
+
+        return make_ref_counted<StringValue>(builder.to_deprecated_string());
+    }();
+
+    if (evaluates_to_string())
         return value;
-    auto list = value->resolve_as_list(shell);
-    // The list better have one entry, otherwise we've put the wrong kind of node inside this heredoc
-    VERIFY(list.size() == 1);
-    auto lines = list.first().split_view('\n');
 
-    // Now just trim each line and put them back in a string
-    StringBuilder builder { list.first().length() };
-    for (auto& line : lines) {
-        builder.append(line.trim_whitespace(TrimMode::Left));
-        builder.append('\n');
+    int fds[2];
+    auto rc = pipe(fds);
+    if (rc != 0) {
+        // pipe() failed for {}
+        if (shell)
+            shell->raise_error(Shell::ShellError::PipeFailure, DeprecatedString::formatted("heredoc: {}", strerror(errno)), position());
+        return nullptr;
     }
 
-    return make_ref_counted<StringValue>(builder.to_deprecated_string());
+    auto read_end = fds[0];
+    auto write_end = fds[1];
+
+    // Dump all of 'value' into the pipe.
+    auto* file = fdopen(write_end, "wb");
+    if (!file) {
+        if (shell)
+            shell->raise_error(Shell::ShellError::OpenFailure, "heredoc"sv, position());
+        return nullptr;
+    }
+
+    auto text = value->resolve_as_string(shell);
+
+    auto written = fwrite(text.characters(), 1, text.length(), file);
+    fflush(file);
+    if (written != text.length()) {
+        if (shell)
+            shell->raise_error(Shell::ShellError::WriteFailure, "heredoc"sv, position());
+    }
+    fclose(file);
+
+    Command command;
+    command.position = position();
+    command.redirections.append(FdRedirection::create(read_end, *target_fd(), Rewiring::Close::None));
+    return make_ref_counted<CommandValue>(move(command));
 }
 
 void Heredoc::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
@@ -1422,11 +1472,12 @@ HitTestResult Heredoc::hit_test_position(size_t offset) const
     return m_contents->hit_test_position(offset);
 }
 
-Heredoc::Heredoc(Position position, DeprecatedString end, bool allow_interpolation, bool deindent)
+Heredoc::Heredoc(Position position, DeprecatedString end, bool allow_interpolation, bool deindent, Optional<int> target_fd)
     : Node(move(position))
     , m_end(move(end))
     , m_allows_interpolation(allow_interpolation)
     , m_deindent(deindent)
+    , m_target_fd(target_fd)
 {
 }
 

+ 5 - 2
Userland/Shell/AST.h

@@ -1338,20 +1338,22 @@ private:
 
 class Heredoc final : public Node {
 public:
-    Heredoc(Position, DeprecatedString end, bool allow_interpolation, bool deindent);
+    Heredoc(Position, DeprecatedString end, bool allow_interpolation, bool deindent, Optional<int> target_fd = {});
     virtual ~Heredoc();
     virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
 
     DeprecatedString const& end() const { return m_end; }
     bool allow_interpolation() const { return m_allows_interpolation; }
     bool deindent() const { return m_deindent; }
+    Optional<int> target_fd() const { return m_target_fd; }
+    bool evaluates_to_string() const { return !m_target_fd.has_value(); }
     RefPtr<AST::Node> const& contents() const { return m_contents; }
     void set_contents(RefPtr<AST::Node> contents)
     {
         m_contents = move(contents);
         if (m_contents->is_syntax_error())
             set_is_syntax_error(m_contents->syntax_error_node());
-        else
+        else if (is_syntax_error())
             clear_syntax_error();
     }
 
@@ -1366,6 +1368,7 @@ private:
     DeprecatedString m_end;
     bool m_allows_interpolation { false };
     bool m_deindent { false };
+    Optional<int> m_target_fd;
     RefPtr<AST::Node> m_contents;
 };
 

+ 6 - 0
Userland/Shell/Shell.cpp

@@ -2352,6 +2352,12 @@ void Shell::possibly_print_error() const
     case ShellError::LaunchError:
         warnln("Shell: {}", m_error_description);
         break;
+    case ShellError::PipeFailure:
+        warnln("Shell: pipe() failed for {}", m_error_description);
+        break;
+    case ShellError::WriteFailure:
+        warnln("Shell: write() failed for {}", m_error_description);
+        break;
     case ShellError::InternalControlFlowBreak:
     case ShellError::InternalControlFlowContinue:
     case ShellError::InternalControlFlowInterrupted:

+ 2 - 0
Userland/Shell/Shell.h

@@ -343,6 +343,8 @@ public:
         OpenFailure,
         OutOfMemory,
         LaunchError,
+        PipeFailure,
+        WriteFailure,
     };
 
     void raise_error(ShellError kind, DeprecatedString description, Optional<AST::Position> position = {})