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();
110 if (path_str ==
"/.yaze" || path_str.rfind(
"/.yaze/", 0) == 0) {
115 return path_str.find(root_str) == 0;
119 const char* units[] = {
"B",
"KB",
"MB",
"GB",
"TB"};
121 double size =
static_cast<double>(size_bytes);
123 while (size >= 1024.0 && unit_index < 4) {
128 if (unit_index == 0) {
129 return absl::StrFormat(
"%d %s",
static_cast<int>(size), units[unit_index]);
131 return absl::StrFormat(
"%.2f %s", size, units[unit_index]);
136 const fs::file_time_type& time)
const {
138 auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
139 time - fs::file_time_type::clock::now() +
140 std::chrono::system_clock::now());
143 std::time_t tt = std::chrono::system_clock::to_time_t(sctp);
146 std::stringstream ss;
147 ss << std::put_time(std::localtime(&tt),
"%Y-%m-%d %H:%M:%S");
164 auto path_str = parser.
GetString(
"path").value_or(
".");
165 bool recursive = parser.
HasFlag(
"recursive");
169 if (!path_result.ok()) {
170 return path_result.status();
172 fs::path dir_path = *path_result;
176 if (!fs::exists(dir_path, ec)) {
177 return absl::NotFoundError(
178 absl::StrCat(
"Directory not found: ", dir_path.string()));
181 if (!fs::is_directory(dir_path, ec)) {
182 return absl::InvalidArgumentError(
183 absl::StrCat(
"Path is not a directory: ", dir_path.string()));
187 formatter.
AddField(
"path", dir_path.string());
188 formatter.
AddField(
"recursive", recursive ?
"true" :
"false");
190 std::vector<std::map<std::string, std::string>> entries;
194 for (
const auto& entry : fs::recursive_directory_iterator(
195 dir_path, fs::directory_options::skip_permission_denied, ec)) {
200 std::map<std::string, std::string> file_info;
201 file_info[
"name"] = entry.path().filename().string();
202 file_info[
"path"] = fs::relative(entry.path(), dir_path).string();
203 file_info[
"type"] = entry.is_directory() ?
"directory" :
"file";
205 if (entry.is_regular_file()) {
209 entries.push_back(file_info);
212 for (
const auto& entry : fs::directory_iterator(
213 dir_path, fs::directory_options::skip_permission_denied, ec)) {
218 std::map<std::string, std::string> file_info;
219 file_info[
"name"] = entry.path().filename().string();
220 file_info[
"type"] = entry.is_directory() ?
"directory" :
"file";
222 if (entry.is_regular_file()) {
226 entries.push_back(file_info);
231 std::sort(entries.begin(), entries.end(), [](
const auto& a,
const auto& b) {
232 if (a.at(
"type") != b.at(
"type")) {
233 return a.at(
"type") ==
"directory";
235 return a.at(
"name") < b.at(
"name");
239 formatter.BeginArray(
"entries");
240 for (
const auto& entry : entries) {
241 formatter.BeginObject();
242 for (
const auto& [key, value] : entry) {
243 formatter.AddField(key, value);
245 formatter.EndObject();
247 formatter.EndArray();
249 formatter.AddField(
"total_entries", std::to_string(entries.size()));
250 formatter.EndObject();
252 return absl::OkStatus();
268 auto path_str = parser.
GetString(
"path").value();
269 int max_lines = parser.
GetInt(
"lines").value_or(-1);
270 int offset = parser.
GetInt(
"offset").value_or(0);
274 if (!path_result.ok()) {
275 return path_result.status();
277 fs::path file_path = *path_result;
281 if (!fs::exists(file_path, ec)) {
282 return absl::NotFoundError(
283 absl::StrCat(
"File not found: ", file_path.string()));
286 if (!fs::is_regular_file(file_path, ec)) {
287 return absl::InvalidArgumentError(
288 absl::StrCat(
"Path is not a file: ", file_path.string()));
293 return absl::InvalidArgumentError(
294 absl::StrCat(
"File appears to be binary: ", file_path.string(),
295 ". Only text files can be read."));
299 std::ifstream file(file_path, std::ios::in);
301 return absl::InternalError(
302 absl::StrCat(
"Failed to open file: ", file_path.string()));
306 formatter.
AddField(
"path", file_path.string());
309 std::vector<std::string> lines;
314 while (line_num < offset && std::getline(file, line)) {
319 while (std::getline(file, line)) {
320 if (max_lines > 0 && lines.size() >=
static_cast<size_t>(max_lines)) {
323 lines.push_back(line);
326 formatter.
AddField(
"lines_read", std::to_string(lines.size()));
327 formatter.
AddField(
"starting_line", std::to_string(offset + 1));
330 if (parser.
GetString(
"format").value_or(
"text") ==
"json") {
332 for (
const auto& content_line : lines) {
337 std::stringstream content;
338 for (
size_t i = 0; i < lines.size(); ++i) {
340 if (i < lines.size() - 1) {
344 formatter.
AddField(
"content", content.str());
349 return absl::OkStatus();
354 std::string ext = path.extension().string();
355 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
358 std::set<std::string> text_extensions = {
359 ".txt",
".md",
".cc",
".cpp",
".c",
360 ".h",
".hpp",
".py",
".js",
".ts",
361 ".json",
".xml",
".yaml",
".yml",
".toml",
362 ".ini",
".cfg",
".conf",
".sh",
".bash",
363 ".zsh",
".fish",
".cmake",
".mk",
".makefile",
364 ".html",
".css",
".scss",
".sass",
".less",
365 ".jsx",
".tsx",
".rs",
".go",
".java",
366 ".kt",
".swift",
".rb",
".pl",
".php",
367 ".lua",
".vim",
".el",
".lisp",
".clj",
368 ".hs",
".ml",
".fs",
".asm",
".s",
369 ".S",
".proto",
".thrift",
".graphql",
".sql",
370 ".gitignore",
".dockerignore",
".editorconfig",
".eslintrc"};
372 if (text_extensions.count(ext) > 0) {
377 std::ifstream file(path, std::ios::binary);
384 file.read(buffer,
sizeof(buffer));
385 std::streamsize bytes_read = file.gcount();
388 for (std::streamsize i = 0; i < bytes_read; ++i) {
389 if (buffer[i] ==
'\0') {
394 if (!std::isprint(buffer[i]) && buffer[i] !=
'\n' && buffer[i] !=
'\r' &&
416 auto path_str = parser.
GetString(
"path").value();
420 if (!path_result.ok()) {
422 if (absl::IsPermissionDenied(path_result.status())) {
423 return path_result.status();
427 formatter.
AddField(
"path", path_str);
428 formatter.
AddField(
"exists",
"false");
429 formatter.
AddField(
"error", std::string(path_result.status().message()));
431 return absl::OkStatus();
434 fs::path check_path = *path_result;
436 bool exists = fs::exists(check_path, ec);
439 formatter.
AddField(
"path", check_path.string());
440 formatter.
AddField(
"exists", exists ?
"true" :
"false");
443 if (fs::is_directory(check_path, ec)) {
444 formatter.
AddField(
"type",
"directory");
445 }
else if (fs::is_regular_file(check_path, ec)) {
447 }
else if (fs::is_symlink(check_path, ec)) {
448 formatter.
AddField(
"type",
"symlink");
450 formatter.
AddField(
"type",
"other");
456 return absl::OkStatus();
472 auto path_str = parser.
GetString(
"path").value();
476 if (!path_result.ok()) {
477 return path_result.status();
479 fs::path info_path = *path_result;
483 if (!fs::exists(info_path, ec)) {
484 return absl::NotFoundError(
485 absl::StrCat(
"Path not found: ", info_path.string()));
489 formatter.
AddField(
"path", info_path.string());
490 formatter.
AddField(
"name", info_path.filename().string());
491 formatter.
AddField(
"parent", info_path.parent_path().string());
494 if (fs::is_directory(info_path, ec)) {
495 formatter.
AddField(
"type",
"directory");
498 size_t entry_count = 0;
499 for (
auto& _ : fs::directory_iterator(info_path, ec)) {
502 formatter.
AddField(
"entries", std::to_string(entry_count));
503 }
else if (fs::is_regular_file(info_path, ec)) {
505 formatter.
AddField(
"extension", info_path.extension().string());
508 auto size = fs::file_size(info_path, ec);
509 formatter.
AddField(
"size_bytes", std::to_string(size));
511 }
else if (fs::is_symlink(info_path, ec)) {
512 formatter.
AddField(
"type",
"symlink");
513 auto target = fs::read_symlink(info_path, ec);
515 formatter.
AddField(
"target", target.string());
518 formatter.
AddField(
"type",
"other");
522 auto last_write = fs::last_write_time(info_path, ec);
531 formatter.
AddField(
"absolute_path", fs::absolute(info_path).
string());
532 formatter.
AddField(
"is_hidden", info_path.filename().string().starts_with(
".")
538 return absl::OkStatus();
542 const fs::path& path)
const {
544 auto perms = fs::status(path, ec).permissions();
553 result += (perms & fs::perms::owner_read) != fs::perms::none ?
'r' :
'-';
554 result += (perms & fs::perms::owner_write) != fs::perms::none ?
'w' :
'-';
555 result += (perms & fs::perms::owner_exec) != fs::perms::none ?
'x' :
'-';
558 result += (perms & fs::perms::group_read) != fs::perms::none ?
'r' :
'-';
559 result += (perms & fs::perms::group_write) != fs::perms::none ?
'w' :
'-';
560 result += (perms & fs::perms::group_exec) != fs::perms::none ?
'x' :
'-';
563 result += (perms & fs::perms::others_read) != fs::perms::none ?
'r' :
'-';
564 result += (perms & fs::perms::others_write) != fs::perms::none ?
'w' :
'-';
565 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)