yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
message.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <iostream>
5#include <string>
6#include <vector>
7
8#include "absl/flags/declare.h"
9#include "absl/flags/flag.h"
10#include "absl/status/status.h"
11#include "absl/status/statusor.h"
12#include "absl/strings/ascii.h"
13#include "absl/strings/match.h"
14#include "absl/strings/numbers.h"
15#include "absl/strings/str_format.h"
17#include "app/rom.h"
18#include "util/macro.h"
19
20ABSL_DECLARE_FLAG(std::string, rom);
21
22namespace yaze {
23namespace cli {
24namespace message {
25
26namespace {
27
28absl::StatusOr<Rom> LoadRomFromFlag() {
29 std::string rom_path = absl::GetFlag(FLAGS_rom);
30 if (rom_path.empty()) {
31 return absl::FailedPreconditionError(
32 "No ROM loaded. Use --rom=<path> to specify ROM file.");
33 }
34
35 Rom rom;
36 auto status = rom.LoadFromFile(rom_path);
37 if (!status.ok()) {
38 return absl::FailedPreconditionError(absl::StrFormat(
39 "Failed to load ROM from '%s': %s", rom_path, status.message()));
40 }
41
42 return rom;
43}
44
45std::vector<editor::MessageData> LoadMessages(Rom* rom) {
46 // Fix: Cast away constness for ReadAllTextData, which expects uint8_t*
47 return editor::ReadAllTextData(const_cast<uint8_t*>(rom->data()), editor::kTextData);
48}
49
50} // namespace
51
52absl::Status HandleMessageListCommand(const std::vector<std::string>& arg_vec,
53 Rom* rom_context) {
54 std::string format = "json";
55 int start_id = 0;
56 int end_id = -1; // -1 means all
57
58 for (size_t i = 0; i < arg_vec.size(); ++i) {
59 const std::string& token = arg_vec[i];
60 if (token == "--format") {
61 if (i + 1 >= arg_vec.size()) {
62 return absl::InvalidArgumentError("--format requires a value.");
63 }
64 format = absl::AsciiStrToLower(arg_vec[++i]);
65 } else if (absl::StartsWith(token, "--format=")) {
66 format = absl::AsciiStrToLower(token.substr(9));
67 } else if (token == "--range") {
68 if (i + 1 >= arg_vec.size()) {
69 return absl::InvalidArgumentError("--range requires a value (start-end).");
70 }
71 std::string range = arg_vec[++i];
72 size_t dash_pos = range.find('-');
73 if (dash_pos == std::string::npos) {
74 return absl::InvalidArgumentError("--range format must be start-end (e.g. 0-100)");
75 }
76 if (!absl::SimpleAtoi(range.substr(0, dash_pos), &start_id) ||
77 !absl::SimpleAtoi(range.substr(dash_pos + 1), &end_id)) {
78 return absl::InvalidArgumentError("Invalid range format");
79 }
80 } else if (absl::StartsWith(token, "--range=")) {
81 std::string range = token.substr(8);
82 size_t dash_pos = range.find('-');
83 if (dash_pos == std::string::npos) {
84 return absl::InvalidArgumentError("--range format must be start-end (e.g. 0-100)");
85 }
86 if (!absl::SimpleAtoi(range.substr(0, dash_pos), &start_id) ||
87 !absl::SimpleAtoi(range.substr(dash_pos + 1), &end_id)) {
88 return absl::InvalidArgumentError("Invalid range format");
89 }
90 }
91 }
92
93 if (format != "json" && format != "text") {
94 return absl::InvalidArgumentError("--format must be either json or text");
95 }
96
97 Rom rom_storage;
98 Rom* rom = nullptr;
99 if (rom_context != nullptr && rom_context->is_loaded()) {
100 rom = rom_context;
101 } else {
102 auto rom_or = LoadRomFromFlag();
103 if (!rom_or.ok()) {
104 return rom_or.status();
105 }
106 rom_storage = std::move(rom_or.value());
107 rom = &rom_storage;
108 }
109
110 auto messages = LoadMessages(rom);
111
112 if (end_id < 0) {
113 end_id = static_cast<int>(messages.size()) - 1;
114 }
115
116 start_id = std::max(0, std::min(start_id, static_cast<int>(messages.size()) - 1));
117 end_id = std::max(start_id, std::min(end_id, static_cast<int>(messages.size()) - 1));
118
119 if (format == "json") {
120 std::cout << "{\n";
121 std::cout << absl::StrFormat(" \"total_messages\": %zu,\n", messages.size());
122 std::cout << absl::StrFormat(" \"range\": [%d, %d],\n", start_id, end_id);
123 std::cout << " \"messages\": [\n";
124
125 bool first = true;
126 for (int i = start_id; i <= end_id; ++i) {
127 const auto& msg = messages[i];
128 if (!first) std::cout << ",\n";
129 std::cout << " {\n";
130 std::cout << absl::StrFormat(" \"id\": %d,\n", msg.ID);
131 std::cout << absl::StrFormat(" \"address\": \"0x%06X\",\n", msg.Address);
132
133 // Escape quotes in the text
134 std::string escaped_text = msg.ContentsParsed;
135 size_t pos = 0;
136 while ((pos = escaped_text.find('"', pos)) != std::string::npos) {
137 escaped_text.insert(pos, "\\");
138 pos += 2;
139 }
140 std::cout << absl::StrFormat(" \"text\": \"%s\"\n", escaped_text);
141 std::cout << " }";
142 first = false;
143 }
144 std::cout << "\n ]\n";
145 std::cout << "}\n";
146 } else {
147 std::cout << absl::StrFormat("📝 Messages %d-%d (Total: %zu)\n",
148 start_id, end_id, messages.size());
149 std::cout << std::string(60, '=') << "\n";
150 for (int i = start_id; i <= end_id; ++i) {
151 const auto& msg = messages[i];
152 std::cout << absl::StrFormat("[%03d] @ 0x%06X\n", msg.ID, msg.Address);
153 std::cout << " " << msg.ContentsParsed << "\n";
154 std::cout << std::string(60, '-') << "\n";
155 }
156 }
157
158 return absl::OkStatus();
159}
160
161absl::Status HandleMessageReadCommand(const std::vector<std::string>& arg_vec,
162 Rom* rom_context) {
163 int message_id = -1;
164 std::string format = "json";
165
166 for (size_t i = 0; i < arg_vec.size(); ++i) {
167 const std::string& token = arg_vec[i];
168 if (token == "--id") {
169 if (i + 1 >= arg_vec.size()) {
170 return absl::InvalidArgumentError("--id requires a value.");
171 }
172 if (!absl::SimpleAtoi(arg_vec[++i], &message_id)) {
173 return absl::InvalidArgumentError("Invalid message ID format.");
174 }
175 } else if (absl::StartsWith(token, "--id=")) {
176 if (!absl::SimpleAtoi(token.substr(5), &message_id)) {
177 return absl::InvalidArgumentError("Invalid message ID format.");
178 }
179 } else if (token == "--format") {
180 if (i + 1 >= arg_vec.size()) {
181 return absl::InvalidArgumentError("--format requires a value.");
182 }
183 format = absl::AsciiStrToLower(arg_vec[++i]);
184 } else if (absl::StartsWith(token, "--format=")) {
185 format = absl::AsciiStrToLower(token.substr(9));
186 }
187 }
188
189 if (message_id < 0) {
190 return absl::InvalidArgumentError(
191 "Usage: message-read --id <message_id> [--format <json|text>]");
192 }
193
194 if (format != "json" && format != "text") {
195 return absl::InvalidArgumentError("--format must be either json or text");
196 }
197
198 Rom rom_storage;
199 Rom* rom = nullptr;
200 if (rom_context != nullptr && rom_context->is_loaded()) {
201 rom = rom_context;
202 } else {
203 auto rom_or = LoadRomFromFlag();
204 if (!rom_or.ok()) {
205 return rom_or.status();
206 }
207 rom_storage = std::move(rom_or.value());
208 rom = &rom_storage;
209 }
210
211 auto messages = LoadMessages(rom);
212
213 if (message_id >= static_cast<int>(messages.size())) {
214 return absl::NotFoundError(
215 absl::StrFormat("Message ID %d not found (max: %d)",
216 message_id, messages.size() - 1));
217 }
218
219 const auto& msg = messages[message_id];
220
221 if (format == "json") {
222 std::cout << "{\n";
223 std::cout << absl::StrFormat(" \"id\": %d,\n", msg.ID);
224 std::cout << absl::StrFormat(" \"address\": \"0x%06X\",\n", msg.Address);
225
226 // Escape quotes
227 std::string escaped_text = msg.ContentsParsed;
228 size_t pos = 0;
229 while ((pos = escaped_text.find('"', pos)) != std::string::npos) {
230 escaped_text.insert(pos, "\\");
231 pos += 2;
232 }
233 std::cout << absl::StrFormat(" \"text\": \"%s\",\n", escaped_text);
234 std::cout << absl::StrFormat(" \"length\": %zu\n", msg.Data.size());
235 std::cout << "}\n";
236 } else {
237 std::cout << absl::StrFormat("📝 Message #%d\n", msg.ID);
238 std::cout << absl::StrFormat("Address: 0x%06X\n", msg.Address);
239 std::cout << absl::StrFormat("Length: %zu bytes\n", msg.Data.size());
240 std::cout << std::string(60, '-') << "\n";
241 std::cout << msg.ContentsParsed << "\n";
242 }
243
244 return absl::OkStatus();
245}
246
247absl::Status HandleMessageSearchCommand(const std::vector<std::string>& arg_vec,
248 Rom* rom_context) {
249 std::string query;
250 std::string format = "json";
251
252 for (size_t i = 0; i < arg_vec.size(); ++i) {
253 const std::string& token = arg_vec[i];
254 if (token == "--query") {
255 if (i + 1 >= arg_vec.size()) {
256 return absl::InvalidArgumentError("--query requires a value.");
257 }
258 query = arg_vec[++i];
259 } else if (absl::StartsWith(token, "--query=")) {
260 query = token.substr(8);
261 } else if (token == "--format") {
262 if (i + 1 >= arg_vec.size()) {
263 return absl::InvalidArgumentError("--format requires a value.");
264 }
265 format = absl::AsciiStrToLower(arg_vec[++i]);
266 } else if (absl::StartsWith(token, "--format=")) {
267 format = absl::AsciiStrToLower(token.substr(9));
268 }
269 }
270
271 if (query.empty()) {
272 return absl::InvalidArgumentError(
273 "Usage: message-search --query <text> [--format <json|text>]");
274 }
275
276 if (format != "json" && format != "text") {
277 return absl::InvalidArgumentError("--format must be either json or text");
278 }
279
280 Rom rom_storage;
281 Rom* rom = nullptr;
282 if (rom_context != nullptr && rom_context->is_loaded()) {
283 rom = rom_context;
284 } else {
285 auto rom_or = LoadRomFromFlag();
286 if (!rom_or.ok()) {
287 return rom_or.status();
288 }
289 rom_storage = std::move(rom_or.value());
290 rom = &rom_storage;
291 }
292
293 auto messages = LoadMessages(rom);
294 std::string lowered_query = absl::AsciiStrToLower(query);
295
296 std::vector<int> matches;
297 for (const auto& msg : messages) {
298 std::string lowered_text = absl::AsciiStrToLower(msg.ContentsParsed);
299 if (lowered_text.find(lowered_query) != std::string::npos) {
300 matches.push_back(msg.ID);
301 }
302 }
303
304 if (format == "json") {
305 std::cout << "{\n";
306 std::cout << absl::StrFormat(" \"query\": \"%s\",\n", query);
307 std::cout << absl::StrFormat(" \"match_count\": %zu,\n", matches.size());
308 std::cout << " \"matches\": [\n";
309
310 for (size_t i = 0; i < matches.size(); ++i) {
311 const auto& msg = messages[matches[i]];
312 if (i > 0) std::cout << ",\n";
313
314 std::string escaped_text = msg.ContentsParsed;
315 size_t pos = 0;
316 while ((pos = escaped_text.find('"', pos)) != std::string::npos) {
317 escaped_text.insert(pos, "\\");
318 pos += 2;
319 }
320
321 std::cout << " {\n";
322 std::cout << absl::StrFormat(" \"id\": %d,\n", msg.ID);
323 std::cout << absl::StrFormat(" \"address\": \"0x%06X\",\n", msg.Address);
324 std::cout << absl::StrFormat(" \"text\": \"%s\"\n", escaped_text);
325 std::cout << " }";
326 }
327 std::cout << "\n ]\n";
328 std::cout << "}\n";
329 } else {
330 std::cout << absl::StrFormat("🔍 Search: \"%s\" → %zu match(es)\n",
331 query, matches.size());
332 std::cout << std::string(60, '=') << "\n";
333
334 for (int match_id : matches) {
335 const auto& msg = messages[match_id];
336 std::cout << absl::StrFormat("[%03d] @ 0x%06X\n", msg.ID, msg.Address);
337 std::cout << " " << msg.ContentsParsed << "\n";
338 std::cout << std::string(60, '-') << "\n";
339 }
340 }
341
342 return absl::OkStatus();
343}
344
345absl::Status HandleMessageStatsCommand(const std::vector<std::string>& arg_vec,
346 Rom* rom_context) {
347 std::string format = "json";
348
349 for (size_t i = 0; i < arg_vec.size(); ++i) {
350 const std::string& token = arg_vec[i];
351 if (token == "--format") {
352 if (i + 1 >= arg_vec.size()) {
353 return absl::InvalidArgumentError("--format requires a value.");
354 }
355 format = absl::AsciiStrToLower(arg_vec[++i]);
356 } else if (absl::StartsWith(token, "--format=")) {
357 format = absl::AsciiStrToLower(token.substr(9));
358 }
359 }
360
361 if (format != "json" && format != "text") {
362 return absl::InvalidArgumentError("--format must be either json or text");
363 }
364
365 Rom rom_storage;
366 Rom* rom = nullptr;
367 if (rom_context != nullptr && rom_context->is_loaded()) {
368 rom = rom_context;
369 } else {
370 auto rom_or = LoadRomFromFlag();
371 if (!rom_or.ok()) {
372 return rom_or.status();
373 }
374 rom_storage = std::move(rom_or.value());
375 rom = &rom_storage;
376 }
377
378 auto messages = LoadMessages(rom);
379
380 size_t total_bytes = 0;
381 size_t max_length = 0;
382 size_t min_length = SIZE_MAX;
383
384 for (const auto& msg : messages) {
385 size_t len = msg.Data.size();
386 total_bytes += len;
387 max_length = std::max(max_length, len);
388 min_length = std::min(min_length, len);
389 }
390
391 double avg_length = messages.empty() ? 0.0 :
392 static_cast<double>(total_bytes) / messages.size();
393
394 if (format == "json") {
395 std::cout << "{\n";
396 std::cout << absl::StrFormat(" \"total_messages\": %zu,\n", messages.size());
397 std::cout << absl::StrFormat(" \"total_bytes\": %zu,\n", total_bytes);
398 std::cout << absl::StrFormat(" \"average_length\": %.2f,\n", avg_length);
399 std::cout << absl::StrFormat(" \"min_length\": %zu,\n", min_length);
400 std::cout << absl::StrFormat(" \"max_length\": %zu\n", max_length);
401 std::cout << "}\n";
402 } else {
403 std::cout << "📊 Message Statistics\n";
404 std::cout << std::string(40, '=') << "\n";
405 std::cout << absl::StrFormat("Total Messages: %zu\n", messages.size());
406 std::cout << absl::StrFormat("Total Bytes: %zu\n", total_bytes);
407 std::cout << absl::StrFormat("Average Length: %.2f bytes\n", avg_length);
408 std::cout << absl::StrFormat("Min Length: %zu bytes\n", min_length);
409 std::cout << absl::StrFormat("Max Length: %zu bytes\n", max_length);
410 }
411
412 return absl::OkStatus();
413}
414
415} // namespace message
416} // namespace cli
417} // namespace yaze
The Rom class is used to load, save, and modify Rom data.
Definition rom.h:71
absl::Status LoadFromFile(const std::string &filename, bool z3_load=true)
Definition rom.cc:289
auto data() const
Definition rom.h:203
bool is_loaded() const
Definition rom.h:197
ABSL_DECLARE_FLAG(std::string, rom)
std::vector< editor::MessageData > LoadMessages(Rom *rom)
Definition message.cc:45
absl::Status HandleMessageSearchCommand(const std::vector< std::string > &arg_vec, Rom *rom_context)
Search for messages containing specific text.
Definition message.cc:247
absl::Status HandleMessageReadCommand(const std::vector< std::string > &arg_vec, Rom *rom_context)
Read a specific message by ID.
Definition message.cc:161
absl::Status HandleMessageListCommand(const std::vector< std::string > &arg_vec, Rom *rom_context)
List all messages in the ROM.
Definition message.cc:52
absl::Status HandleMessageStatsCommand(const std::vector< std::string > &arg_vec, Rom *rom_context)
Get message statistics and overview.
Definition message.cc:345
std::vector< MessageData > ReadAllTextData(uint8_t *rom, int pos)
constexpr int kTextData
Main namespace for the application.