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(), [](
const auto& a,
const auto& b) {
224 if (a.at(
"type") != b.at(
"type")) {
225 return a.at(
"type") ==
"directory";
227 return a.at(
"name") < b.at(
"name");
231 formatter.BeginArray(
"entries");
232 for (
const auto& entry : entries) {
233 formatter.BeginObject();
234 for (
const auto& [key, value] : entry) {
235 formatter.AddField(key, value);
237 formatter.EndObject();
239 formatter.EndArray();
241 formatter.AddField(
"total_entries", std::to_string(entries.size()));
242 formatter.EndObject();
244 return absl::OkStatus();
260 auto path_str = parser.
GetString(
"path").value();
261 int max_lines = parser.
GetInt(
"lines").value_or(-1);
262 int offset = parser.
GetInt(
"offset").value_or(0);
266 if (!path_result.ok()) {
267 return path_result.status();
269 fs::path file_path = *path_result;
273 if (!fs::exists(file_path, ec)) {
274 return absl::NotFoundError(
275 absl::StrCat(
"File not found: ", file_path.string()));
278 if (!fs::is_regular_file(file_path, ec)) {
279 return absl::InvalidArgumentError(
280 absl::StrCat(
"Path is not a file: ", file_path.string()));
285 return absl::InvalidArgumentError(
286 absl::StrCat(
"File appears to be binary: ", file_path.string(),
287 ". Only text files can be read."));
291 std::ifstream file(file_path, std::ios::in);
293 return absl::InternalError(
294 absl::StrCat(
"Failed to open file: ", file_path.string()));
298 formatter.
AddField(
"path", file_path.string());
301 std::vector<std::string> lines;
306 while (line_num < offset && std::getline(file, line)) {
311 while (std::getline(file, line)) {
312 if (max_lines > 0 && lines.size() >=
static_cast<size_t>(max_lines)) {
315 lines.push_back(line);
318 formatter.
AddField(
"lines_read", std::to_string(lines.size()));
319 formatter.
AddField(
"starting_line", std::to_string(offset + 1));
322 if (parser.
GetString(
"format").value_or(
"text") ==
"json") {
324 for (
const auto& content_line : lines) {
329 std::stringstream content;
330 for (
size_t i = 0; i < lines.size(); ++i) {
332 if (i < lines.size() - 1) {
336 formatter.
AddField(
"content", content.str());
341 return absl::OkStatus();
346 std::string ext = path.extension().string();
347 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
350 std::set<std::string> text_extensions = {
351 ".txt",
".md",
".cc",
".cpp",
".c",
352 ".h",
".hpp",
".py",
".js",
".ts",
353 ".json",
".xml",
".yaml",
".yml",
".toml",
354 ".ini",
".cfg",
".conf",
".sh",
".bash",
355 ".zsh",
".fish",
".cmake",
".mk",
".makefile",
356 ".html",
".css",
".scss",
".sass",
".less",
357 ".jsx",
".tsx",
".rs",
".go",
".java",
358 ".kt",
".swift",
".rb",
".pl",
".php",
359 ".lua",
".vim",
".el",
".lisp",
".clj",
360 ".hs",
".ml",
".fs",
".asm",
".s",
361 ".S",
".proto",
".thrift",
".graphql",
".sql",
362 ".gitignore",
".dockerignore",
".editorconfig",
".eslintrc"};
364 if (text_extensions.count(ext) > 0) {
369 std::ifstream file(path, std::ios::binary);
376 file.read(buffer,
sizeof(buffer));
377 std::streamsize bytes_read = file.gcount();
380 for (std::streamsize i = 0; i < bytes_read; ++i) {
381 if (buffer[i] ==
'\0') {
386 if (!std::isprint(buffer[i]) && buffer[i] !=
'\n' && buffer[i] !=
'\r' &&
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());
524 formatter.
AddField(
"is_hidden", info_path.filename().string().starts_with(
".")
530 return absl::OkStatus();
534 const fs::path& path)
const {
536 auto perms = fs::status(path, ec).permissions();
545 result += (perms & fs::perms::owner_read) != fs::perms::none ?
'r' :
'-';
546 result += (perms & fs::perms::owner_write) != fs::perms::none ?
'w' :
'-';
547 result += (perms & fs::perms::owner_exec) != fs::perms::none ?
'x' :
'-';
550 result += (perms & fs::perms::group_read) != fs::perms::none ?
'r' :
'-';
551 result += (perms & fs::perms::group_write) != fs::perms::none ?
'w' :
'-';
552 result += (perms & fs::perms::group_exec) != fs::perms::none ?
'x' :
'-';
555 result += (perms & fs::perms::others_read) != fs::perms::none ?
'r' :
'-';
556 result += (perms & fs::perms::others_write) != fs::perms::none ?
'w' :
'-';
557 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)