mirror of
https://github.com/RGBCube/serenity
synced 2025-07-19 18:57:40 +00:00
Shell: Allow the heredoc node to act as a redirection too
This will be used in a future commit to implement POSIX sh heredocs.
This commit is contained in:
parent
9c61fed37c
commit
4efc632e15
4 changed files with 86 additions and 24 deletions
|
@ -1366,6 +1366,10 @@ void Heredoc::dump(int level) const
|
||||||
print_indented(m_end, level + 2);
|
print_indented(m_end, level + 2);
|
||||||
print_indented("(Allows Interpolation)"sv, level + 1);
|
print_indented("(Allows Interpolation)"sv, level + 1);
|
||||||
print_indented(DeprecatedString::formatted("{}", m_allows_interpolation), level + 2);
|
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);
|
print_indented("(Contents)"sv, level + 1);
|
||||||
if (m_contents)
|
if (m_contents)
|
||||||
m_contents->dump(level + 2);
|
m_contents->dump(level + 2);
|
||||||
|
@ -1375,29 +1379,75 @@ void Heredoc::dump(int level) const
|
||||||
|
|
||||||
RefPtr<Value> Heredoc::run(RefPtr<Shell> shell)
|
RefPtr<Value> Heredoc::run(RefPtr<Shell> shell)
|
||||||
{
|
{
|
||||||
if (!m_deindent)
|
if (!m_contents) {
|
||||||
return m_contents->run(shell);
|
if (shell)
|
||||||
|
shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "Attempt to evaluate an unresolved heredoc"sv, position());
|
||||||
// To deindent, first split to lines...
|
return nullptr;
|
||||||
auto value = m_contents->run(shell);
|
|
||||||
if (shell && shell->has_any_error())
|
|
||||||
return make_ref_counted<ListValue>({});
|
|
||||||
|
|
||||||
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());
|
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)
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
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);
|
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))
|
: Node(move(position))
|
||||||
, m_end(move(end))
|
, m_end(move(end))
|
||||||
, m_allows_interpolation(allow_interpolation)
|
, m_allows_interpolation(allow_interpolation)
|
||||||
, m_deindent(deindent)
|
, m_deindent(deindent)
|
||||||
|
, m_target_fd(target_fd)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1338,20 +1338,22 @@ private:
|
||||||
|
|
||||||
class Heredoc final : public Node {
|
class Heredoc final : public Node {
|
||||||
public:
|
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 ~Heredoc();
|
||||||
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
|
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
|
||||||
|
|
||||||
DeprecatedString const& end() const { return m_end; }
|
DeprecatedString const& end() const { return m_end; }
|
||||||
bool allow_interpolation() const { return m_allows_interpolation; }
|
bool allow_interpolation() const { return m_allows_interpolation; }
|
||||||
bool deindent() const { return m_deindent; }
|
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; }
|
RefPtr<AST::Node> const& contents() const { return m_contents; }
|
||||||
void set_contents(RefPtr<AST::Node> contents)
|
void set_contents(RefPtr<AST::Node> contents)
|
||||||
{
|
{
|
||||||
m_contents = move(contents);
|
m_contents = move(contents);
|
||||||
if (m_contents->is_syntax_error())
|
if (m_contents->is_syntax_error())
|
||||||
set_is_syntax_error(m_contents->syntax_error_node());
|
set_is_syntax_error(m_contents->syntax_error_node());
|
||||||
else
|
else if (is_syntax_error())
|
||||||
clear_syntax_error();
|
clear_syntax_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1366,6 +1368,7 @@ private:
|
||||||
DeprecatedString m_end;
|
DeprecatedString m_end;
|
||||||
bool m_allows_interpolation { false };
|
bool m_allows_interpolation { false };
|
||||||
bool m_deindent { false };
|
bool m_deindent { false };
|
||||||
|
Optional<int> m_target_fd;
|
||||||
RefPtr<AST::Node> m_contents;
|
RefPtr<AST::Node> m_contents;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2352,6 +2352,12 @@ void Shell::possibly_print_error() const
|
||||||
case ShellError::LaunchError:
|
case ShellError::LaunchError:
|
||||||
warnln("Shell: {}", m_error_description);
|
warnln("Shell: {}", m_error_description);
|
||||||
break;
|
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::InternalControlFlowBreak:
|
||||||
case ShellError::InternalControlFlowContinue:
|
case ShellError::InternalControlFlowContinue:
|
||||||
case ShellError::InternalControlFlowInterrupted:
|
case ShellError::InternalControlFlowInterrupted:
|
||||||
|
|
|
@ -343,6 +343,8 @@ public:
|
||||||
OpenFailure,
|
OpenFailure,
|
||||||
OutOfMemory,
|
OutOfMemory,
|
||||||
LaunchError,
|
LaunchError,
|
||||||
|
PipeFailure,
|
||||||
|
WriteFailure,
|
||||||
};
|
};
|
||||||
|
|
||||||
void raise_error(ShellError kind, DeprecatedString description, Optional<AST::Position> position = {})
|
void raise_error(ShellError kind, DeprecatedString description, Optional<AST::Position> position = {})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue