10#include "fmt/format.h"
22const std::filesystem::path tileset_anims_c_rel_path = std::filesystem::path{
"src"} /
"tileset_anims.c";
23const std::filesystem::path tileset_anims_h_rel_path = std::filesystem::path{
"include"} /
"tileset_anims.h";
25const std::filesystem::path porytiles_generated_include_base =
26 std::filesystem::path{
"porytiles_generated"} /
"tilesets";
40[[nodiscard]] std::string generate_include_directive(
const std::string &snake_dir)
43 "#include \"{}/{}/generated_anim_code.h\"", porytiles_generated_include_base.string(), snake_dir);
49[[nodiscard]] std::string generate_porytiles_include_comment()
51 return "// [Porytiles] Auto-generated include. Do not remove.";
57[[nodiscard]] std::string generate_porytiles_declaration_comment()
59 return "// [Porytiles] Auto-generated declaration. Do not remove.";
71[[nodiscard]] std::string generate_declaration(
const std::string &shorthand)
73 return fmt::format(
"void InitTilesetAnim_PorytilesManaged_{}(void);", shorthand);
85[[nodiscard]] std::string generate_declaration_pattern(
const std::string &shorthand)
87 return fmt::format(
"InitTilesetAnim_PorytilesManaged_{}", shorthand);
99[[nodiscard]] std::string generate_include_pattern(
const std::string &snake_dir)
101 return fmt::format(
"{}/generated_anim_code.h", snake_dir);
109[[nodiscard]] std::optional<std::size_t>
110find_existing_include(
const std::vector<std::string> &lines,
const std::string &snake_dir)
112 const std::string pattern = generate_include_pattern(snake_dir);
114 for (std::size_t i = 0; i < lines.size(); ++i) {
115 if (lines[i].find(pattern) != std::string::npos) {
127[[nodiscard]] std::optional<std::size_t>
128find_existing_declaration(
const std::vector<std::string> &lines,
const std::string &shorthand)
130 const std::string pattern = generate_declaration_pattern(shorthand);
132 for (std::size_t i = 0; i < lines.size(); ++i) {
133 if (lines[i].find(pattern) != std::string::npos) {
145[[nodiscard]] std::optional<std::size_t> find_endif_guard(
const std::vector<std::string> &lines)
147 for (std::size_t i = lines.size(); i > 0; --i) {
148 const auto &line = lines[i - 1];
149 if (line.find(
"#endif") != std::string::npos) {
159[[nodiscard]]
bool is_porytiles_comment(
const std::string &line)
161 return line.find(
"[Porytiles]") != std::string::npos;
168read_file_lines(
const std::filesystem::path &path,
const UserDiagnostics *diag)
170 std::ifstream in{path};
176 std::vector<std::string> lines;
178 while (std::getline(in, line)) {
179 lines.push_back(line);
188write_file_lines(
const std::filesystem::path &path,
const std::vector<std::string> &lines,
const UserDiagnostics *diag)
190 std::ofstream out{path};
191 if (!out.is_open()) {
196 for (
const auto &line : lines) {
214 std::filesystem::path project_root,
215 gsl::not_null<const InfraConfig *> config,
216 gsl::not_null<const UserDiagnostics *> diagnostics)
217 : project_root_{std::move(project_root)}, config_{config}, diagnostics_{diagnostics}
225 constexpr std::string_view tileset_prefix =
"gTileset_";
226 if (!tileset_name.starts_with(tileset_prefix)) {
233 const std::string shorthand = tileset_cased.to_pascal_case();
234 const std::string snake_dir = tileset_cased.to_snake_case();
237 const auto anims_c_path = project_root_ / tileset_anims_c_rel_path;
240 read_file_lines(anims_c_path, diagnostics_),
242 "Failed to read tileset_anims.c for tileset '{}'.",
246 const auto anims_h_path = project_root_ / tileset_anims_h_rel_path;
249 read_file_lines(anims_h_path, diagnostics_),
251 "Failed to read tileset_anims.h for tileset '{}'.",
255 const bool include_exists = find_existing_include(c_lines, snake_dir).has_value();
256 const bool declaration_exists = find_existing_declaration(h_lines, shorthand).has_value();
258 if (include_exists && declaration_exists) {
260 "wire-tileset-animation",
261 "Animation include and declaration for '{}' already exist, skipping wire operation.",
267 if (!include_exists) {
268 const std::string include_comment = generate_porytiles_include_comment();
269 const std::string include_line = generate_include_directive(snake_dir);
271 c_lines.emplace_back(
"");
272 c_lines.push_back(include_comment);
273 c_lines.push_back(include_line);
277 if (!declaration_exists) {
279 const auto endif_index = find_endif_guard(h_lines);
280 if (!endif_index.has_value()) {
282 "Failed to find #endif guard in tileset_anims.h for tileset '{}'.",
286 const std::string decl_comment = generate_porytiles_declaration_comment();
287 const std::string decl_line = generate_declaration(shorthand);
290 auto insert_pos = h_lines.begin() +
static_cast<std::ptrdiff_t
>(endif_index.value());
293 const std::size_t idx = endif_index.value();
294 if (idx == 0 || !h_lines[idx - 1].empty()) {
295 insert_pos = h_lines.insert(insert_pos,
"");
299 insert_pos = h_lines.insert(insert_pos, decl_comment);
301 insert_pos = h_lines.insert(insert_pos, decl_line);
304 h_lines.insert(insert_pos,
"");
308 auto c_write_result = write_file_lines(anims_c_path, c_lines, diagnostics_);
309 if (!c_write_result.has_value()) {
310 return c_write_result;
313 return write_file_lines(anims_h_path, h_lines, diagnostics_);
325 constexpr std::string_view tileset_prefix =
"gTileset_";
326 if (!tileset_name.starts_with(tileset_prefix)) {
333 const std::string shorthand = tileset_cased.to_pascal_case();
334 const std::string snake_dir = tileset_cased.to_snake_case();
337 const auto anims_c_path = project_root_ / tileset_anims_c_rel_path;
340 read_file_lines(anims_c_path, diagnostics_),
342 "Failed to read tileset_anims.c for tileset '{}'.",
346 const auto anims_h_path = project_root_ / tileset_anims_h_rel_path;
349 read_file_lines(anims_h_path, diagnostics_),
351 "Failed to read tileset_anims.h for tileset '{}'.",
355 const auto include_index = find_existing_include(c_lines, snake_dir);
356 const auto decl_index = find_existing_declaration(h_lines, shorthand);
358 if (!include_index.has_value() && !decl_index.has_value()) {
360 "wire-tileset-animation",
361 "Animation include and declaration for '{}' not found, skipping remove operation.",
367 if (include_index.has_value()) {
368 std::size_t idx = include_index.value();
369 c_lines.erase(c_lines.begin() +
static_cast<std::ptrdiff_t
>(idx));
372 if (idx > 0 && is_porytiles_comment(c_lines[idx - 1])) {
373 c_lines.erase(c_lines.begin() +
static_cast<std::ptrdiff_t
>(idx - 1));
377 if (idx > 0 && c_lines[idx - 1].empty()) {
378 c_lines.erase(c_lines.begin() +
static_cast<std::ptrdiff_t
>(idx - 1));
384 if (decl_index.has_value()) {
385 std::size_t idx = decl_index.value();
386 h_lines.erase(h_lines.begin() +
static_cast<std::ptrdiff_t
>(idx));
389 if (idx > 0 && is_porytiles_comment(h_lines[idx - 1])) {
390 h_lines.erase(h_lines.begin() +
static_cast<std::ptrdiff_t
>(idx - 1));
394 if (idx > 0 && h_lines[idx - 1].empty()) {
395 h_lines.erase(h_lines.begin() +
static_cast<std::ptrdiff_t
>(idx - 1));
401 auto c_write_result = write_file_lines(anims_c_path, c_lines, diagnostics_);
402 if (!c_write_result.has_value()) {
403 return c_write_result;
406 return write_file_lines(anims_h_path, h_lines, diagnostics_);
#define PT_TRY_ASSIGN_CHAIN_ERR(var, expr, return_type,...)
Unwraps a ChainableResult, chaining a new error message on failure.
A result type that maintains a chainable sequence of errors for debugging and error reporting.
ChainableResult< void > wire_include_for_tileset(const std::string &tileset_name, bool is_secondary) const
Adds an #include directive for the tileset's generated animation code.
ProjectTilesetAnimsModifier(std::filesystem::path project_root, gsl::not_null< const InfraConfig * > config, gsl::not_null< const UserDiagnostics * > diagnostics)
Constructs a TilesetAnimsModifier with required dependencies.
ChainableResult< void > remove_include_for_tileset(const std::string &tileset_name, bool is_secondary) const
Removes the #include directive for the tileset's generated animation code.
static const Style bold
Bold text formatting.
virtual std::string format(const std::string &format_str, const std::vector< FormatParam > ¶ms) const
Formats a string with styled parameters using fmtlib syntax.
Abstract class for structured error reporting and diagnostic output.
virtual void remark(const std::string &tag, const std::vector< std::string > &lines) const =0
Display a tagged remark message.
const TextFormatter & formatter() const
DynamicCasedName extract_tileset_cased_name(const std::string &tileset_name)
Extracts the tileset short name and wraps it in a DynamicCasedName.
Utility functions for string manipulation and formatting.