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";
38[[nodiscard]] std::string generate_include_directive(
const std::string &snake_dir)
41 "#include \"{}/{}/generated_anim_code.h\"", porytiles_generated_include_base.string(), snake_dir);
45[[nodiscard]] std::string generate_porytiles_include_comment()
47 return "// [Porytiles] Auto-generated include. Do not remove.";
51[[nodiscard]] std::string generate_porytiles_declaration_comment()
53 return "// [Porytiles] Auto-generated declaration. Do not remove.";
63[[nodiscard]] std::string generate_declaration(
const std::string &shorthand)
65 return fmt::format(
"void InitTilesetAnim_PorytilesManaged_{}(void);", shorthand);
75[[nodiscard]] std::string generate_declaration_pattern(
const std::string &shorthand)
77 return fmt::format(
"InitTilesetAnim_PorytilesManaged_{}", shorthand);
87[[nodiscard]] std::string generate_include_pattern(
const std::string &snake_dir)
89 return fmt::format(
"{}/generated_anim_code.h", snake_dir);
95[[nodiscard]] std::optional<std::size_t>
96find_existing_include(
const std::vector<std::string> &lines,
const std::string &snake_dir)
98 const std::string pattern = generate_include_pattern(snake_dir);
100 for (std::size_t i = 0; i < lines.size(); ++i) {
101 if (lines[i].find(pattern) != std::string::npos) {
111[[nodiscard]] std::optional<std::size_t>
112find_existing_declaration(
const std::vector<std::string> &lines,
const std::string &shorthand)
114 const std::string pattern = generate_declaration_pattern(shorthand);
116 for (std::size_t i = 0; i < lines.size(); ++i) {
117 if (lines[i].find(pattern) != std::string::npos) {
127[[nodiscard]] std::optional<std::size_t> find_endif_guard(
const std::vector<std::string> &lines)
129 for (std::size_t i = lines.size(); i > 0; --i) {
130 const auto &line = lines[i - 1];
131 if (line.find(
"#endif") != std::string::npos) {
139[[nodiscard]]
bool is_porytiles_comment(
const std::string &line)
141 return line.find(
"[Porytiles]") != std::string::npos;
146read_file_lines(
const std::filesystem::path &path,
const UserDiagnostics *diag)
148 std::ifstream in{path};
154 std::vector<std::string> lines;
156 while (std::getline(in, line)) {
157 lines.push_back(line);
164write_file_lines(
const std::filesystem::path &path,
const std::vector<std::string> &lines,
const UserDiagnostics *diag)
166 std::ofstream out{path};
167 if (!out.is_open()) {
172 for (
const auto &line : lines) {
190 std::filesystem::path project_root,
191 gsl::not_null<const InfraConfig *> config,
192 gsl::not_null<const UserDiagnostics *> diagnostics)
193 : project_root_{std::move(project_root)}, config_{config}, diagnostics_{diagnostics}
201 constexpr std::string_view tileset_prefix =
"gTileset_";
202 if (!tileset_name.starts_with(tileset_prefix)) {
209 const std::string shorthand = tileset_cased.to_pascal_case();
210 const std::string snake_dir = tileset_cased.to_snake_case();
213 const auto anims_c_path = project_root_ / tileset_anims_c_rel_path;
216 read_file_lines(anims_c_path, diagnostics_),
218 "Failed to read tileset_anims.c for tileset '{}'.",
222 const auto anims_h_path = project_root_ / tileset_anims_h_rel_path;
225 read_file_lines(anims_h_path, diagnostics_),
227 "Failed to read tileset_anims.h for tileset '{}'.",
231 const bool include_exists = find_existing_include(c_lines, snake_dir).has_value();
232 const bool declaration_exists = find_existing_declaration(h_lines, shorthand).has_value();
234 if (include_exists && declaration_exists) {
236 "wire-tileset-animation",
237 "Animation include and declaration for '{}' already exist, skipping wire operation.",
243 if (!include_exists) {
244 const std::string include_comment = generate_porytiles_include_comment();
245 const std::string include_line = generate_include_directive(snake_dir);
247 c_lines.emplace_back(
"");
248 c_lines.push_back(include_comment);
249 c_lines.push_back(include_line);
253 if (!declaration_exists) {
255 const auto endif_index = find_endif_guard(h_lines);
256 if (!endif_index.has_value()) {
258 "Failed to find #endif guard in tileset_anims.h for tileset '{}'.",
262 const std::string decl_comment = generate_porytiles_declaration_comment();
263 const std::string decl_line = generate_declaration(shorthand);
266 auto insert_pos = h_lines.begin() +
static_cast<std::ptrdiff_t
>(endif_index.value());
269 const std::size_t idx = endif_index.value();
270 if (idx == 0 || !h_lines[idx - 1].empty()) {
271 insert_pos = h_lines.insert(insert_pos,
"");
275 insert_pos = h_lines.insert(insert_pos, decl_comment);
277 insert_pos = h_lines.insert(insert_pos, decl_line);
280 h_lines.insert(insert_pos,
"");
284 auto c_write_result = write_file_lines(anims_c_path, c_lines, diagnostics_);
285 if (!c_write_result.has_value()) {
286 return c_write_result;
289 return write_file_lines(anims_h_path, h_lines, diagnostics_);
301 constexpr std::string_view tileset_prefix =
"gTileset_";
302 if (!tileset_name.starts_with(tileset_prefix)) {
309 const std::string shorthand = tileset_cased.to_pascal_case();
310 const std::string snake_dir = tileset_cased.to_snake_case();
313 const auto anims_c_path = project_root_ / tileset_anims_c_rel_path;
316 read_file_lines(anims_c_path, diagnostics_),
318 "Failed to read tileset_anims.c for tileset '{}'.",
322 const auto anims_h_path = project_root_ / tileset_anims_h_rel_path;
325 read_file_lines(anims_h_path, diagnostics_),
327 "Failed to read tileset_anims.h for tileset '{}'.",
331 const auto include_index = find_existing_include(c_lines, snake_dir);
332 const auto decl_index = find_existing_declaration(h_lines, shorthand);
334 if (!include_index.has_value() && !decl_index.has_value()) {
336 "wire-tileset-animation",
337 "Animation include and declaration for '{}' not found, skipping remove operation.",
343 if (include_index.has_value()) {
344 std::size_t idx = include_index.value();
345 c_lines.erase(c_lines.begin() +
static_cast<std::ptrdiff_t
>(idx));
348 if (idx > 0 && is_porytiles_comment(c_lines[idx - 1])) {
349 c_lines.erase(c_lines.begin() +
static_cast<std::ptrdiff_t
>(idx - 1));
353 if (idx > 0 && c_lines[idx - 1].empty()) {
354 c_lines.erase(c_lines.begin() +
static_cast<std::ptrdiff_t
>(idx - 1));
360 if (decl_index.has_value()) {
361 std::size_t idx = decl_index.value();
362 h_lines.erase(h_lines.begin() +
static_cast<std::ptrdiff_t
>(idx));
365 if (idx > 0 && is_porytiles_comment(h_lines[idx - 1])) {
366 h_lines.erase(h_lines.begin() +
static_cast<std::ptrdiff_t
>(idx - 1));
370 if (idx > 0 && h_lines[idx - 1].empty()) {
371 h_lines.erase(h_lines.begin() +
static_cast<std::ptrdiff_t
>(idx - 1));
377 auto c_write_result = write_file_lines(anims_c_path, c_lines, diagnostics_);
378 if (!c_write_result.has_value()) {
379 return c_write_result;
382 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.