#include "sedpp/Input.h" #include #include #include #include #include namespace sedpp { namespace { // Used for --follow-symlinks display names. This follows the same bounded walk // as in-place editing but leaves actual reading to the caller. std::string followSymlinkName(const std::string &fileName) { std::filesystem::path current(fileName); for (int depth = 0; depth < 40; ++depth) { std::error_code ec; const std::filesystem::file_status status = std::filesystem::symlink_status(current, ec); if (ec) { throw std::runtime_error("couldn't readlink " + fileName + ": " + ec.message()); } if (!std::filesystem::is_symlink(status)) { return current.string(); } const std::filesystem::path target = std::filesystem::read_symlink(current, ec); if (ec) { throw std::runtime_error("couldn't readlink " + fileName + ": " + ec.message()); } current = target.is_absolute() ? target : current.parent_path() / target; } throw std::runtime_error("couldn't readlink " + fileName + ": Too many levels of symbolic links"); } } // namespace InputLoader::InputLoader(const Options &options) : options_(options) {} std::vector InputLoader::splitBuffer(const std::string &buffer, const std::string &fileName, std::uint64_t &globalLine) const { std::vector records; const char delimiter = options_.nullData ? '\0' : '\n'; std::uint64_t fileLine = 0; // Split manually instead of using getline so NUL-delimited mode and missing // final delimiters are represented exactly. std::size_t start = 0; while (start < buffer.size()) { const std::size_t end = buffer.find(delimiter, start); Record record; record.text = buffer.substr(start, end == std::string::npos ? std::string::npos : end - start); record.hadDelimiter = end != std::string::npos; record.fileName = fileName.empty() ? "-" : fileName; record.lineNumber = ++globalLine; record.fileLineNumber = ++fileLine; records.push_back(std::move(record)); if (end == std::string::npos) { break; } start = end + 1; } if (buffer.empty()) { // Empty files produce no sed cycles. return records; } if (!records.empty()) { // $ in --separate mode needs to know the final record in each file. records.back().lastInFile = true; } return records; } std::vector InputLoader::loadFile(const std::string &fileName) const { std::uint64_t globalLine = 0; if (fileName == "-") { // In single-file loading, stdin is treated as the whole input stream. std::ostringstream buffer; buffer << std::cin.rdbuf(); auto records = splitBuffer(buffer.str(), "-", globalLine); if (!records.empty()) { records.back().lastOverall = true; } return records; } const std::string displayName = options_.followSymlinks ? followSymlinkName(fileName) : fileName; // Open the original argument even when --follow-symlinks is set; the resolved // name is for diagnostics/F command output, not for changing read semantics. std::ifstream input(fileName, std::ios::binary); if (!input) { throw std::runtime_error("can't read " + fileName); } std::ostringstream buffer; buffer << input.rdbuf(); auto records = splitBuffer(buffer.str(), displayName, globalLine); if (!records.empty()) { records.back().lastOverall = true; } return records; } std::vector InputLoader::loadAll() const { std::vector all; std::uint64_t globalLine = 0; const std::vector files = options_.inputFiles.empty() ? std::vector{"-"} : options_.inputFiles; for (const std::string &fileName : files) { std::vector records; if (fileName == "-") { // GNU sed consumes stdin at the point "-" appears among input files. std::ostringstream buffer; buffer << std::cin.rdbuf(); records = splitBuffer(buffer.str(), "-", globalLine); } else { const std::string displayName = options_.followSymlinks ? followSymlinkName(fileName) : fileName; std::ifstream input(fileName, std::ios::binary); if (!input) { throw std::runtime_error("can't read " + fileName); } std::ostringstream buffer; buffer << input.rdbuf(); records = splitBuffer(buffer.str(), displayName, globalLine); } all.insert(all.end(), records.begin(), records.end()); } if (!all.empty()) { // $ without --separate is only true for the final record of the combined // input stream. all.back().lastOverall = true; } return all; } } // namespace sedpp