yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
command_context.cc
Go to the documentation of this file.
2
3#include <iostream>
4
5#include "absl/flags/declare.h"
6#include "absl/flags/flag.h"
7#include "absl/strings/ascii.h"
8#include "absl/strings/match.h"
9#include "absl/strings/str_format.h"
10#include "absl/strings/str_join.h"
12#include "cli/util/hex_util.h"
13#include "core/project.h"
15
17
18ABSL_DECLARE_FLAG(std::string, rom);
19ABSL_DECLARE_FLAG(bool, mock_rom);
20
21namespace yaze {
22namespace cli {
23namespace resources {
24
25// ============================================================================
26// CommandContext Implementation
27// ============================================================================
28
29CommandContext::CommandContext(const Config& config) : config_(config) {}
30
32 if (initialized_) {
33 return absl::OkStatus();
34 }
35
36 // If external ROM context is provided, use it
37 if (config_.external_rom_context != nullptr &&
40 initialized_ = true;
41 return absl::OkStatus();
42 }
43
44 // Check if mock ROM mode is enabled
47 if (!status.ok()) {
48 return status;
49 }
51 initialized_ = true;
52 return absl::OkStatus();
53 }
54
55 // Load ROM from file if path is provided
56 if (config_.rom_path.has_value() && !config_.rom_path->empty()) {
58 if (!status.ok()) {
59 return absl::FailedPreconditionError(
60 absl::StrFormat("Failed to load ROM from '%s': %s", *config_.rom_path,
61 status.message()));
62 }
64 initialized_ = true;
65 return absl::OkStatus();
66 }
67
68 // Try loading from flags as fallback
69 std::string rom_path = absl::GetFlag(FLAGS_rom);
70 if (!rom_path.empty()) {
71 auto status = rom_storage_.LoadFromFile(rom_path);
72 if (!status.ok()) {
73 return absl::FailedPreconditionError(absl::StrFormat(
74 "Failed to load ROM from '%s': %s", rom_path, status.message()));
75 }
77 initialized_ = true;
78 return absl::OkStatus();
79 }
80
81 return absl::FailedPreconditionError(
82 "No ROM loaded. Use --rom=<path> or --mock-rom for testing.");
83}
84
85absl::StatusOr<Rom*> CommandContext::GetRom() {
86 if (!initialized_) {
87 auto status = Initialize();
88 if (!status.ok()) {
89 return status;
90 }
91 }
92
93 if (active_rom_ == nullptr) {
94 return absl::FailedPreconditionError("ROM not loaded");
95 }
96
97 return active_rom_;
98}
99
101 if (!rom->resource_label()) {
102 return absl::FailedPreconditionError("ROM has no resource label manager");
103 }
104
105 if (!rom->resource_label()->labels_loaded_ ||
106 rom->resource_label()->labels_.empty()) {
107 project::YazeProject project;
108 project.use_embedded_labels = true;
109 auto labels_status = project.InitializeEmbeddedLabels(
111 if (labels_status.ok()) {
112 rom->resource_label()->labels_ = project.resource_labels;
113 rom->resource_label()->labels_loaded_ = true;
114 } else {
115 return labels_status;
116 }
117 }
118
119 return absl::OkStatus();
120}
121
122// ============================================================================
123// ArgumentParser Implementation
124// ============================================================================
125
126ArgumentParser::ArgumentParser(const std::vector<std::string>& args)
127 : args_(args) {}
128
129std::optional<std::string> ArgumentParser::FindArgValue(
130 const std::string& name) const {
131 std::string flag = "--" + name;
132 std::string equals_form = flag + "=";
133
134 for (size_t i = 0; i < args_.size(); ++i) {
135 const std::string& arg = args_[i];
136
137 // Check for --name=value form
138 if (absl::StartsWith(arg, equals_form)) {
139 return arg.substr(equals_form.length());
140 }
141
142 // Check for --name value form
143 if (arg == flag && i + 1 < args_.size()) {
144 return args_[i + 1];
145 }
146 }
147
148 return std::nullopt;
149}
150
151std::optional<std::string> ArgumentParser::GetString(
152 const std::string& name) const {
153 return FindArgValue(name);
154}
155
156absl::StatusOr<int> ArgumentParser::GetInt(const std::string& name) const {
157 auto value = FindArgValue(name);
158 if (!value.has_value()) {
159 return absl::NotFoundError(
160 absl::StrFormat("Argument '--%s' not found", name));
161 }
162
163 // Try hex first (with 0x prefix)
164 if (absl::StartsWith(*value, "0x") || absl::StartsWith(*value, "0X")) {
165 int result;
166 if (ParseHexString(value->substr(2), &result)) {
167 return result;
168 }
169 return absl::InvalidArgumentError(
170 absl::StrFormat("Invalid hex integer for '--%s': %s", name, *value));
171 }
172
173 // Try decimal
174 int result;
175 if (absl::SimpleAtoi(*value, &result)) {
176 return result;
177 }
178
179 return absl::InvalidArgumentError(
180 absl::StrFormat("Invalid integer for '--%s': %s", name, *value));
181}
182
183absl::StatusOr<int> ArgumentParser::GetHex(const std::string& name) const {
184 auto value = FindArgValue(name);
185 if (!value.has_value()) {
186 return absl::NotFoundError(
187 absl::StrFormat("Argument '--%s' not found", name));
188 }
189
190 // Strip 0x prefix if present
191 std::string hex_str = *value;
192 if (absl::StartsWith(hex_str, "0x") || absl::StartsWith(hex_str, "0X")) {
193 hex_str = hex_str.substr(2);
194 }
195
196 int result;
197 if (ParseHexString(hex_str, &result)) {
198 return result;
199 }
200
201 return absl::InvalidArgumentError(
202 absl::StrFormat("Invalid hex value for '--%s': %s", name, *value));
203}
204
205bool ArgumentParser::HasFlag(const std::string& name) const {
206 std::string flag = "--" + name;
207 for (const auto& arg : args_) {
208 if (arg == flag) {
209 return true;
210 }
211 }
212 return false;
213}
214
215std::vector<std::string> ArgumentParser::GetPositional() const {
216 std::vector<std::string> positional;
217 for (size_t i = 0; i < args_.size(); ++i) {
218 const std::string& arg = args_[i];
219 if (!absl::StartsWith(arg, "--")) {
220 positional.push_back(arg);
221 } else if (arg.find('=') == std::string::npos && i + 1 < args_.size()) {
222 // Skip the next argument as it's the value for this flag
223 ++i;
224 }
225 }
226 return positional;
227}
228
230 const std::vector<std::string>& required) const {
231 std::vector<std::string> missing;
232 for (const auto& arg : required) {
233 if (!FindArgValue(arg).has_value()) {
234 missing.push_back("--" + arg);
235 }
236 }
237
238 if (!missing.empty()) {
239 return absl::InvalidArgumentError(absl::StrFormat(
240 "Missing required arguments: %s", absl::StrJoin(missing, ", ")));
241 }
242
243 return absl::OkStatus();
244}
245
246// ============================================================================
247// OutputFormatter Implementation
248// ============================================================================
249
250absl::StatusOr<OutputFormatter> OutputFormatter::FromString(
251 const std::string& format) {
252 std::string lower = absl::AsciiStrToLower(format);
253 if (lower == "json") {
255 } else if (lower == "text") {
257 } else {
258 return absl::InvalidArgumentError(absl::StrFormat(
259 "Unknown format: %s (expected 'json' or 'text')", format));
260 }
261}
262
263void OutputFormatter::BeginObject(const std::string& title) {
264 if (IsJson()) {
265 buffer_ += "{\n";
267 first_field_ = true;
268 } else if (IsText() && !title.empty()) {
269 buffer_ += absl::StrFormat("=== %s ===\n", title);
270 }
271}
272
274 if (IsJson()) {
276 if (first_field_) {
277 if (!buffer_.empty() && buffer_.back() == '\n') {
278 buffer_.pop_back();
279 }
280 buffer_ += "}";
281 return;
282 }
283 buffer_ += "\n";
284 AddIndent();
285 buffer_ += "}";
286 }
287}
288
289void OutputFormatter::AddField(const std::string& key,
290 const std::string& value) {
291 if (IsJson()) {
292 if (!first_field_) {
293 buffer_ += ",\n";
294 }
295 first_field_ = false;
296 AddIndent();
297 buffer_ +=
298 absl::StrFormat("\"%s\": \"%s\"", EscapeJson(key), EscapeJson(value));
299 } else {
300 buffer_ += absl::StrFormat(" %-20s : %s\n", key, value);
301 }
302}
303
304void OutputFormatter::AddField(const std::string& key, const char* value) {
305 AddField(key, value != nullptr ? std::string(value) : std::string());
306}
307
308void OutputFormatter::AddField(const std::string& key, int value) {
309 if (IsJson()) {
310 if (!first_field_) {
311 buffer_ += ",\n";
312 }
313 first_field_ = false;
314 AddIndent();
315 buffer_ += absl::StrFormat("\"%s\": %d", EscapeJson(key), value);
316 } else {
317 buffer_ += absl::StrFormat(" %-20s : %d\n", key, value);
318 }
319}
320
321void OutputFormatter::AddField(const std::string& key, uint64_t value) {
322 if (IsJson()) {
323 if (!first_field_) {
324 buffer_ += ",\n";
325 }
326 first_field_ = false;
327 AddIndent();
328 buffer_ += absl::StrFormat("\"%s\": %llu", EscapeJson(key), value);
329 } else {
330 buffer_ += absl::StrFormat(" %-20s : %llu\n", key, value);
331 }
332}
333
334void OutputFormatter::AddField(const std::string& key, bool value) {
335 if (IsJson()) {
336 if (!first_field_) {
337 buffer_ += ",\n";
338 }
339 first_field_ = false;
340 AddIndent();
341 buffer_ += absl::StrFormat("\"%s\": %s", EscapeJson(key),
342 value ? "true" : "false");
343 } else {
344 buffer_ += absl::StrFormat(" %-20s : %s\n", key, value ? "yes" : "no");
345 }
346}
347
348void OutputFormatter::AddHexField(const std::string& key, uint64_t value,
349 int width) {
350 if (IsJson()) {
351 if (!first_field_) {
352 buffer_ += ",\n";
353 }
354 first_field_ = false;
355 AddIndent();
356 buffer_ +=
357 absl::StrFormat("\"%s\": \"0x%0*X\"", EscapeJson(key), width, value);
358 } else {
359 buffer_ += absl::StrFormat(" %-20s : 0x%0*X\n", key, width, value);
360 }
361}
362
363void OutputFormatter::BeginArray(const std::string& key) {
364 in_array_ = true;
366
367 if (IsJson()) {
368 if (!first_field_) {
369 buffer_ += ",\n";
370 }
371 first_field_ = false;
372 AddIndent();
373 buffer_ += absl::StrFormat("\"%s\": [\n", EscapeJson(key));
375 } else {
376 buffer_ += absl::StrFormat(" %s:\n", key);
377 }
378}
379
381 in_array_ = false;
382
383 if (IsJson()) {
385 if (array_item_count_ == 0) {
386 if (!buffer_.empty() && buffer_.back() == '\n') {
387 buffer_.pop_back();
388 }
389 buffer_ += "]";
390 return;
391 }
392 buffer_ += "\n";
393 AddIndent();
394 buffer_ += "]";
395 }
396}
397
398void OutputFormatter::AddArrayItem(const std::string& item) {
399 if (IsJson()) {
400 if (array_item_count_ > 0) {
401 buffer_ += ",\n";
402 }
403 AddIndent();
404 buffer_ += absl::StrFormat("\"%s\"", EscapeJson(item));
405 } else {
406 buffer_ += absl::StrFormat(" - %s\n", item);
407 }
409}
410
411std::string OutputFormatter::GetOutput() const {
412 return buffer_;
413}
414
416 std::cout << buffer_;
417 if (IsJson()) {
418 std::cout << "\n";
419 }
420}
421
423 for (int i = 0; i < indent_level_; ++i) {
424 buffer_ += " ";
425 }
426}
427
428std::string OutputFormatter::EscapeJson(const std::string& str) const {
429 std::string result;
430 result.reserve(str.size() + 10);
431
432 for (char c : str) {
433 switch (c) {
434 case '"':
435 result += "\\\"";
436 break;
437 case '\\':
438 result += "\\\\";
439 break;
440 case '\b':
441 result += "\\b";
442 break;
443 case '\f':
444 result += "\\f";
445 break;
446 case '\n':
447 result += "\\n";
448 break;
449 case '\r':
450 result += "\\r";
451 break;
452 case '\t':
453 result += "\\t";
454 break;
455 default:
456 if (c < 0x20) {
457 result += absl::StrFormat("\\u%04x", static_cast<int>(c));
458 } else {
459 result += c;
460 }
461 }
462 }
463
464 return result;
465}
466
467} // namespace resources
468} // namespace cli
469} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
project::ResourceLabelManager * resource_label()
Definition rom.h:146
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:75
bool is_loaded() const
Definition rom.h:128
std::optional< std::string > FindArgValue(const std::string &name) const
std::vector< std::string > args_
ArgumentParser(const std::vector< std::string > &args)
std::vector< std::string > GetPositional() const
Get all remaining positional arguments.
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 > GetHex(const std::string &name) const
Parse a hex integer argument.
absl::StatusOr< int > GetInt(const std::string &name) const
Parse an integer argument (supports hex with 0x prefix)
absl::StatusOr< Rom * > GetRom()
Get the ROM instance (loads if not already loaded)
absl::Status EnsureLabelsLoaded(Rom *rom)
Ensure resource labels are loaded.
absl::Status Initialize()
Initialize the context and load ROM if needed.
void BeginArray(const std::string &key)
Begin an array.
std::string GetOutput() const
Get the formatted output.
static absl::StatusOr< OutputFormatter > FromString(const std::string &format)
Create formatter from string ("json" or "text")
void AddArrayItem(const std::string &item)
Add an item to current array.
void BeginObject(const std::string &title="")
Start a JSON object or text section.
void EndObject()
End a JSON object or text section.
std::string EscapeJson(const std::string &str) const
void AddField(const std::string &key, const std::string &value)
Add a key-value pair.
bool IsJson() const
Check if using JSON format.
bool IsText() const
Check if using text format.
void AddHexField(const std::string &key, uint64_t value, int width=2)
Add a hex-formatted field.
void Print() const
Print the formatted output to stdout.
ABSL_DECLARE_FLAG(std::string, rom)
bool ParseHexString(absl::string_view str, int *out)
Definition hex_util.h:17
absl::Status InitializeMockRom(Rom &rom)
Initialize a mock ROM for testing without requiring an actual ROM file.
Definition mock_rom.cc:16
Configuration for command context.
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > labels_
Definition project.h:303
Modern project structure with comprehensive settings consolidation.
Definition project.h:84
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > resource_labels
Definition project.h:109
absl::Status InitializeEmbeddedLabels(const std::unordered_map< std::string, std::unordered_map< std::string, std::string > > &labels)
Definition project.cc:1350
static std::unordered_map< std::string, std::unordered_map< std::string, std::string > > ToResourceLabels()
Convert all labels to a structured map for project embedding.