Differentially verified against tai.py (4890 cases incl. real pty runs). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
74 lines
2.4 KiB
C++
74 lines
2.4 KiB
C++
#include "shellio.h"
|
|
#include "ansi.h"
|
|
#include "utf8.h"
|
|
#include <cstdio>
|
|
#include <fcntl.h>
|
|
#include <iostream>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
namespace detz::shell {
|
|
namespace {
|
|
// getpass.getpass(prompt): prompt on the tty, no echo, newline after
|
|
std::optional<std::string> GetPass(const std::string& prompt) {
|
|
int fd = ::open("/dev/tty", O_RDWR | O_NOCTTY);
|
|
bool own = fd >= 0;
|
|
if (fd < 0) fd = 0;
|
|
struct termios old, quiet;
|
|
bool changed = false;
|
|
if (::tcgetattr(fd, &old) == 0) {
|
|
quiet = old;
|
|
quiet.c_lflag &= ~(tcflag_t)ECHO;
|
|
changed = ::tcsetattr(fd, TCSAFLUSH, &quiet) == 0;
|
|
}
|
|
int out = own ? fd : 2;
|
|
(void)!::write(out, prompt.data(), prompt.size());
|
|
std::string line;
|
|
bool got = false, eof = false;
|
|
char c;
|
|
while (true) {
|
|
ssize_t n = ::read(fd, &c, 1);
|
|
if (n <= 0) { eof = true; break; }
|
|
got = true;
|
|
if (c == '\n') break;
|
|
line.push_back(c);
|
|
}
|
|
if (changed) ::tcsetattr(fd, TCSAFLUSH, &old);
|
|
(void)!::write(out, "\n", 1);
|
|
if (own) ::close(fd);
|
|
if (eof && !got) return std::nullopt; // EOFError
|
|
return line;
|
|
}
|
|
std::optional<std::string> Input(const std::string& prompt) {
|
|
std::fputs(prompt.c_str(), stdout);
|
|
std::fflush(stdout);
|
|
std::string line;
|
|
if (!std::getline(std::cin, line)) return std::nullopt; // EOFError
|
|
return line;
|
|
}
|
|
} // namespace
|
|
|
|
std::unique_ptr<StreamSink> ShellIo::Stream(ShellApp& app, const std::string& command, bool live) {
|
|
if (app.HasStreamFactory()) return app.StreamFactory(app.Redact(command));
|
|
if (live && ::isatty(1)) return std::make_unique<LiveStream>();
|
|
return nullptr;
|
|
}
|
|
|
|
AskFn ShellIo::Ask(ShellApp& app, const std::string& command) {
|
|
if (app.Auto() || !app.Persist() || app.Depth() > 0) return nullptr;
|
|
ShellApp* a = &app;
|
|
auto answered = [a](std::optional<std::string> value) {
|
|
if (value) a->MarkUserSteppedIn();
|
|
return value;
|
|
};
|
|
if (app.HasPrompter()) {
|
|
std::string head = Utf8::Head(command, 60);
|
|
return [a, head, answered](const std::string& text, bool) { return answered(a->Prompt("ask", head + " asks:\n" + text, {})); };
|
|
}
|
|
if (!(::isatty(0) && ::isatty(1))) return nullptr;
|
|
return [answered](const std::string& text, bool secret) -> std::optional<std::string> {
|
|
std::string prompt = Paint("\xc2\xbb " + text + " ", Ansi::kYellow);
|
|
return answered(secret ? GetPass(prompt) : Input(prompt));
|
|
};
|
|
}
|
|
} // namespace detz::shell
|