Files
2026-06-09 00:22:22 -05:00

148 lines
4.9 KiB
C++

#include "sedpp/Input.h"
#include <fstream>
#include <filesystem>
#include <iostream>
#include <sstream>
#include <stdexcept>
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<Record> InputLoader::splitBuffer(const std::string &buffer,
const std::string &fileName,
std::uint64_t &globalLine) const {
std::vector<Record> 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<Record> 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<Record> InputLoader::loadAll() const {
std::vector<Record> all;
std::uint64_t globalLine = 0;
const std::vector<std::string> files =
options_.inputFiles.empty() ? std::vector<std::string>{"-"}
: options_.inputFiles;
for (const std::string &fileName : files) {
std::vector<Record> 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