8#include "absl/strings/str_cat.h"
9#include "absl/strings/str_format.h"
10#include "absl/strings/str_split.h"
17namespace fs = std::filesystem;
24 const std::string& path_str)
const {
25 if (path_str.empty()) {
26 return absl::InvalidArgumentError(
"Path cannot be empty");
30 if (path_str.find(
"..") != std::string::npos) {
31 return absl::InvalidArgumentError(
32 "Path traversal (..) is not allowed for security reasons");
39 if (fs::path(path_str).is_relative()) {
42 path = fs::absolute(path_str, ec);
46 return absl::InvalidArgumentError(
47 absl::StrCat(
"Failed to resolve path: ", ec.message()));
51 path = fs::canonical(path, ec);
52 if (ec && ec != std::errc::no_such_file_or_directory) {
54 path = fs::weakly_canonical(path, ec);
56 return absl::InvalidArgumentError(
57 absl::StrCat(
"Failed to normalize path: ", ec.message()));
63 return absl::PermissionDeniedError(
64 absl::StrCat(
"Access denied: Path '", path.string(),
65 "' is outside the project directory"));
73 fs::path current = fs::current_path();
74 fs::path root = current;
77 while (!root.empty() && root != root.root_path()) {
79 if (fs::exists(root /
"CMakeLists.txt") &&
80 fs::exists(root /
"src" /
"yaze.cc")) {
84 if (fs::exists(root /
".git")) {
86 if (fs::exists(root /
"src" /
"cli") &&
87 fs::exists(root /
"src" /
"app")) {
91 root = root.parent_path();
100 fs::path normalized_path = fs::weakly_canonical(path);
101 fs::path normalized_root = fs::canonical(project_root);
104 auto path_str = normalized_path.string();
105 auto root_str = normalized_root.string();
107 return path_str.find(root_str) == 0;
111 const char* units[] = {
"B",
"KB",
"MB",
"GB",
"TB"};
113 double size =
static_cast<double>(size_bytes);
115 while (size >= 1024.0 && unit_index < 4) {
120 if (unit_index == 0) {
121 return absl::StrFormat(
"%d %s",
static_cast<int>(size), units[unit_index]);
123 return absl::StrFormat(
"%.2f %s", size, units[unit_index]);
128 const fs::file_time_type& time)
const {
130 auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
131 time - fs::file_time_type::clock::now() +
132 std::chrono::system_clock::now());
135 std::time_t tt = std::chrono::system_clock::to_time_t(sctp);
138 std::stringstream ss;
139 ss << std::put_time(std::localtime(&tt),
"%Y-%m-%d %H:%M:%S");
156 auto path_str = parser.
GetString(
"path").value_or(
".");
157 bool recursive = parser.
HasFlag(
"recursive");
161 if (!path_result.ok()) {
162 return path_result.status();
164 fs::path dir_path = *path_result;
168 if (!fs::exists(dir_path, ec)) {
169 return absl::NotFoundError(
170 absl::StrCat(
"Directory not found: ", dir_path.string()));
173 if (!fs::is_directory(dir_path, ec)) {
174 return absl::InvalidArgumentError(
175 absl::StrCat(
"Path is not a directory: ", dir_path.string()));
179 formatter.
AddField(
"path", dir_path.string());
180 formatter.
AddField(
"recursive", recursive ?
"true" :
"false");
182 std::vector<std::map<std::string, std::string>> entries;
186 for (
const auto& entry : fs::recursive_directory_iterator(
187 dir_path, fs::directory_options::skip_permission_denied, ec)) {
192 std::map<std::string, std::string> file_info;
193 file_info[
"name"] = entry.path().filename().string();
194 file_info[
"path"] = fs::relative(entry.path(), dir_path).string();
195 file_info[
"type"] = entry.is_directory() ?
"directory" :
"file";
197 if (entry.is_regular_file()) {
201 entries.push_back(file_info);
204 for (
const auto& entry : fs::directory_iterator(
205 dir_path, fs::directory_options::skip_permission_denied, ec)) {
210 std::map<std::string, std::string> file_info;
211 file_info[
"name"] = entry.path().filename().string();
212 file_info[
"type"] = entry.is_directory() ?
"directory" :
"file";
214 if (entry.is_regular_file()) {
218 entries.push_back(file_info);
223 std::sort(entries.begin(), entries.end(),
224 [](
const auto& a,
const auto& b) {
225 if (a.at(
"type") != b.at(
"type")) {
226 return a.at(
"type") ==
"directory";
228 return a.at(
"name") < b.at(
"name");
232 formatter.BeginArray(
"entries");
233 for (
const auto& entry : entries) {
234 formatter.BeginObject();
235 for (
const auto& [key, value] : entry) {
236 formatter.AddField(key, value);
238 formatter.EndObject();
240 formatter.EndArray();
242 formatter.AddField(
"total_entries", std::to_string(entries.size()));
243 formatter.EndObject();
245 return absl::OkStatus();
261 auto path_str = parser.
GetString(
"path").value();
262 int max_lines = parser.
GetInt(
"lines").value_or(-1);
263 int offset = parser.
GetInt(
"offset").value_or(0);
267 if (!path_result.ok()) {
268 return path_result.status();
270 fs::path file_path = *path_result;
274 if (!fs::exists(file_path, ec)) {
275 return absl::NotFoundError(
276 absl::StrCat(
"File not found: ", file_path.string()));
279 if (!fs::is_regular_file(file_path, ec)) {
280 return absl::InvalidArgumentError(
281 absl::StrCat(
"Path is not a file: ", file_path.string()));
286 return absl::InvalidArgumentError(
287 absl::StrCat(
"File appears to be binary: ", file_path.string(),
288 ". Only text files can be read."));
292 std::ifstream file(file_path, std::ios::in);
294 return absl::InternalError(
295 absl::StrCat(
"Failed to open file: ", file_path.string()));
299 formatter.
AddField(
"path", file_path.string());
302 std::vector<std::string> lines;
307 while (line_num < offset && std::getline(file, line)) {
312 while (std::getline(file, line)) {
313 if (max_lines > 0 && lines.size() >=
static_cast<size_t>(max_lines)) {
316 lines.push_back(line);
319 formatter.
AddField(
"lines_read", std::to_string(lines.size()));
320 formatter.
AddField(
"starting_line", std::to_string(offset + 1));
323 if (parser.
GetString(
"format").value_or(
"text") ==
"json") {
325 for (
const auto& content_line : lines) {
330 std::stringstream content;
331 for (
size_t i = 0; i < lines.size(); ++i) {
333 if (i < lines.size() - 1) {
337 formatter.
AddField(
"content", content.str());
342 return absl::OkStatus();
347 std::string ext = path.extension().string();
348 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
351 std::set<std::string> text_extensions = {
352 ".txt",
".md",
".cc",
".cpp",
".c",
".h",
".hpp",
".py",
".js",
".ts",
353 ".json",
".xml",
".yaml",
".yml",
".toml",
".ini",
".cfg",
".conf",
354 ".sh",
".bash",
".zsh",
".fish",
".cmake",
".mk",
".makefile",
355 ".html",
".css",
".scss",
".sass",
".less",
".jsx",
".tsx",
356 ".rs",
".go",
".java",
".kt",
".swift",
".rb",
".pl",
".php",
357 ".lua",
".vim",
".el",
".lisp",
".clj",
".hs",
".ml",
".fs",
358 ".asm",
".s",
".S",
".proto",
".thrift",
".graphql",
".sql",
359 ".gitignore",
".dockerignore",
".editorconfig",
".eslintrc"
362 if (text_extensions.count(ext) > 0) {
367 std::ifstream file(path, std::ios::binary);
374 file.read(buffer,
sizeof(buffer));
375 std::streamsize bytes_read = file.gcount();
378 for (std::streamsize i = 0; i < bytes_read; ++i) {
379 if (buffer[i] ==
'\0') {
384 if (!std::isprint(buffer[i]) &&
408 auto path_str = parser.
GetString(
"path").value();
412 if (!path_result.ok()) {
414 if (absl::IsPermissionDenied(path_result.status())) {
415 return path_result.status();
419 formatter.
AddField(
"path", path_str);
420 formatter.
AddField(
"exists",
"false");
421 formatter.
AddField(
"error", std::string(path_result.status().message()));
423 return absl::OkStatus();
426 fs::path check_path = *path_result;
428 bool exists = fs::exists(check_path, ec);
431 formatter.
AddField(
"path", check_path.string());
432 formatter.
AddField(
"exists", exists ?
"true" :
"false");
435 if (fs::is_directory(check_path, ec)) {
436 formatter.
AddField(
"type",
"directory");
437 }
else if (fs::is_regular_file(check_path, ec)) {
439 }
else if (fs::is_symlink(check_path, ec)) {
440 formatter.
AddField(
"type",
"symlink");
442 formatter.
AddField(
"type",
"other");
448 return absl::OkStatus();
464 auto path_str = parser.
GetString(
"path").value();
468 if (!path_result.ok()) {
469 return path_result.status();
471 fs::path info_path = *path_result;
475 if (!fs::exists(info_path, ec)) {
476 return absl::NotFoundError(
477 absl::StrCat(
"Path not found: ", info_path.string()));
481 formatter.
AddField(
"path", info_path.string());
482 formatter.
AddField(
"name", info_path.filename().string());
483 formatter.
AddField(
"parent", info_path.parent_path().string());
486 if (fs::is_directory(info_path, ec)) {
487 formatter.
AddField(
"type",
"directory");
490 size_t entry_count = 0;
491 for (
auto& _ : fs::directory_iterator(info_path, ec)) {
494 formatter.
AddField(
"entries", std::to_string(entry_count));
495 }
else if (fs::is_regular_file(info_path, ec)) {
497 formatter.
AddField(
"extension", info_path.extension().string());
500 auto size = fs::file_size(info_path, ec);
501 formatter.
AddField(
"size_bytes", std::to_string(size));
503 }
else if (fs::is_symlink(info_path, ec)) {
504 formatter.
AddField(
"type",
"symlink");
505 auto target = fs::read_symlink(info_path, ec);
507 formatter.
AddField(
"target", target.string());
510 formatter.
AddField(
"type",
"other");
514 auto last_write = fs::last_write_time(info_path, ec);
523 formatter.
AddField(
"absolute_path", fs::absolute(info_path).
string());
525 info_path.filename().string().starts_with(
".") ?
"true" :
"false");
529 return absl::OkStatus();
533 const fs::path& path)
const {
535 auto perms = fs::status(path, ec).permissions();
544 result += (perms & fs::perms::owner_read) != fs::perms::none ?
'r' :
'-';
545 result += (perms & fs::perms::owner_write) != fs::perms::none ?
'w' :
'-';
546 result += (perms & fs::perms::owner_exec) != fs::perms::none ?
'x' :
'-';
549 result += (perms & fs::perms::group_read) != fs::perms::none ?
'r' :
'-';
550 result += (perms & fs::perms::group_write) != fs::perms::none ?
'w' :
'-';
551 result += (perms & fs::perms::group_exec) != fs::perms::none ?
'x' :
'-';
554 result += (perms & fs::perms::others_read) != fs::perms::none ?
'r' :
'-';
555 result += (perms & fs::perms::others_write) != fs::perms::none ?
'w' :
'-';
556 result += (perms & fs::perms::others_exec) != fs::perms::none ?
'x' :
'-';
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Utility for parsing common CLI argument patterns.
std::optional< std::string > GetString(const std::string &name) const
Parse a named argument (e.g., –format=json or –format json)
bool HasFlag(const std::string &name) const
Check if a flag is present.
absl::Status RequireArgs(const std::vector< std::string > &required) const
Validate that required arguments are present.
absl::StatusOr< int > GetInt(const std::string &name) const
Parse an integer argument (supports hex with 0x prefix)