Porytiles
Loading...
Searching...
No Matches
project_tileset_metadata_writer.cpp
Go to the documentation of this file.
2
3#include <filesystem>
4#include <fstream>
5#include <map>
6#include <sstream>
7#include <string>
8#include <utility>
9#include <vector>
10
15
16#include <ranges>
17
18namespace {
19
20using namespace porytiles;
21
22// Project source file path for headers.h
23const std::filesystem::path headers_rel_path = std::filesystem::path{"src"} / "data" / "tilesets" / "headers.h";
24
25} // namespace
26
27namespace porytiles {
28
30 std::filesystem::path project_root, gsl::not_null<const TextFormatter *> format)
31 : project_root_{std::move(project_root)}, format_{format}
32{
33}
34
36 const std::string &tileset_name, const std::string &new_callback_value) const
37{
38 return update_fields(tileset_name, {{"callback", new_callback_value}});
39}
40
42 const std::string &tileset_name, const std::map<std::string, std::string> &field_updates) const
43{
44 if (field_updates.empty()) {
45 return {};
46 }
47
48 const auto headers_path = project_root_ / headers_rel_path;
49
50 // Parse the file to locate the tileset struct
51 CParserFacade parser{headers_path, format_};
53 struct_decls,
54 parser.parse_struct_initializers("gTileset_"),
55 void,
56 format_->format("{}: failed to parse tileset headers", FormatParam{headers_path.string(), Style::bold}));
57
58 // Find the target tileset struct by exact name match
59 const StructInitializerDeclaration *target_struct = nullptr;
60 for (const auto &struct_decl : struct_decls) {
61 if (struct_decl.variable_name() == tileset_name) {
62 target_struct = &struct_decl;
63 break;
64 }
65 }
66
67 if (target_struct == nullptr) {
68 return FormattableError{
69 format_->format("tileset '{}' not found in headers.h", FormatParam{tileset_name, Style::bold})};
70 }
71
72 // Build a map of field_name -> line_index for fields we need to update
73 std::map<std::string, std::size_t> field_line_indices;
74 for (const auto &field : target_struct->fields()) {
75 if (field_updates.contains(field.field_name())) {
76 field_line_indices[field.field_name()] = field.position().line - 1; // Convert to 0-based
77 }
78 }
79
80 // Verify all requested fields were found
81 for (const auto &field_name : field_updates | std::views::keys) {
82 if (!field_line_indices.contains(field_name)) {
83 return FormattableError{format_->format(
84 "tileset '{}' has no .{} field",
85 FormatParam{tileset_name, Style::bold},
86 FormatParam{field_name, Style::bold})};
87 }
88 }
89
90 // Get file lines and update all fields
91 std::vector<std::string> file_lines = parser.file_lines();
92 for (const auto &[field_name, new_value] : field_updates) {
93 const std::size_t line_index = field_line_indices.at(field_name);
94 if (line_index >= file_lines.size()) {
95 return FormattableError{format_->format(
96 ".{} field line {} out of bounds for file with {} lines",
97 FormatParam{field_name, Style::bold},
98 FormatParam{std::to_string(line_index + 1), Style::bold},
99 FormatParam{std::to_string(file_lines.size()), Style::bold})};
100 }
101 file_lines[line_index] = " ." + field_name + " = " + new_value + ",";
102 }
103
104 // Write the file back
105 std::ofstream out{headers_path};
106 if (!out.is_open()) {
107 return FormattableError{
108 format_->format("{}: failed to open for writing", FormatParam{headers_path.string(), Style::bold})};
109 }
110
111 for (const auto &line : file_lines) {
112 out << line << '\n';
113 }
114 out.flush();
115
116 if (out.fail()) {
117 return FormattableError{
118 format_->format("{}: failed to write file", FormatParam{headers_path.string(), Style::bold})};
119 }
120
121 return {};
122}
123
125{
126 // Extract shorthand from tileset name (e.g., "gTileset_General" -> "General")
127 const std::string prefix = "gTileset_";
128 if (!tileset_name.starts_with(prefix)) {
129 return FormattableError{format_->format(
130 "tileset name '{}' does not start with '{}'",
131 FormatParam{tileset_name, Style::bold},
132 FormatParam{prefix, Style::bold})};
133 }
134 const std::string shorthand = tileset_name.substr(prefix.size());
135
136 std::map<std::string, std::string> updates{
137 {"tiles", "gTilesetTiles_PorytilesManaged_" + shorthand},
138 {"palettes", "gTilesetPalettes_PorytilesManaged_" + shorthand},
139 {"metatiles", "gMetatiles_PorytilesManaged_" + shorthand},
140 {"metatileAttributes", "gMetatileAttributes_PorytilesManaged_" + shorthand}};
141
142 return update_fields(tileset_name, updates);
143}
144
146ProjectTilesetMetadataWriter::create_tileset_struct(const std::string &tileset_name, bool is_secondary) const
147{
148 // Extract shorthand from tileset name (e.g., "gTileset_MyTileset" -> "MyTileset")
149 const std::string prefix = "gTileset_";
150 if (!tileset_name.starts_with(prefix)) {
151 return FormattableError{format_->format(
152 "tileset name '{}' does not start with '{}'",
153 FormatParam{tileset_name, Style::bold},
154 FormatParam{prefix, Style::bold})};
155 }
156 const std::string shorthand = tileset_name.substr(prefix.size());
157
158 const auto headers_path = project_root_ / headers_rel_path;
159
160 // First, verify the tileset doesn't already exist
161 CParserFacade parser{headers_path, format_};
163 struct_decls,
164 parser.parse_struct_initializers("gTileset_"),
165 void,
166 format_->format("{}: failed to parse tileset headers", FormatParam{headers_path.string(), Style::bold}));
167
168 // Check if tileset already exists
169 for (const auto &struct_decl : struct_decls) {
170 if (struct_decl.variable_name() == tileset_name) {
171 return FormattableError{
172 format_->format("tileset '{}' already exists in headers.h", FormatParam{tileset_name, Style::bold})};
173 }
174 }
175
176 // Generate the new tileset struct with Porytiles-managed field values
177 const std::string is_secondary_value = is_secondary ? "TRUE" : "FALSE";
178 const std::string tiles_value = "gTilesetTiles_PorytilesManaged_" + shorthand;
179 const std::string palettes_value = "gTilesetPalettes_PorytilesManaged_" + shorthand;
180 const std::string metatiles_value = "gMetatiles_PorytilesManaged_" + shorthand;
181 const std::string attributes_value = "gMetatileAttributes_PorytilesManaged_" + shorthand;
182
183 std::ostringstream struct_text;
184 struct_text << "\n";
185 struct_text << "const struct Tileset " << tileset_name << " =\n";
186 struct_text << "{\n";
187 struct_text << " .isCompressed = TRUE,\n";
188 struct_text << " .isSecondary = " << is_secondary_value << ",\n";
189 struct_text << " .tiles = " << tiles_value << ",\n";
190 struct_text << " .palettes = " << palettes_value << ",\n";
191 struct_text << " .metatiles = " << metatiles_value << ",\n";
192 struct_text << " .metatileAttributes = " << attributes_value << ",\n";
193 struct_text << " .callback = NULL,\n";
194 struct_text << "};\n";
195
196 // Append to the end of headers.h
197 std::ofstream out{headers_path, std::ios::app};
198 if (!out.is_open()) {
199 return FormattableError{
200 format_->format("{}: failed to open for appending", FormatParam{headers_path.string(), Style::bold})};
201 }
202
203 out << struct_text.str();
204 out.flush();
205
206 if (out.fail()) {
207 return FormattableError{
208 format_->format("{}: failed to append tileset struct", FormatParam{headers_path.string(), Style::bold})};
209 }
210
211 return {};
212}
213
214} // namespace porytiles
#define PT_TRY_ASSIGN_CHAIN_ERR(var, expr, return_type,...)
Unwraps a ChainableResult, chaining a new error message on failure.
High-level facade for parsing C/C++ source files.
A result type that maintains a chainable sequence of errors for debugging and error reporting.
A text parameter with associated styling for formatted output.
General-purpose error implementation with formatted message support.
Definition error.hpp:63
ChainableResult< void > create_tileset_struct(const std::string &tileset_name, bool is_secondary) const
Creates a new tileset struct and appends it to headers.h.
ProjectTilesetMetadataWriter(std::filesystem::path project_root, gsl::not_null< const TextFormatter * > format)
ChainableResult< void > update_to_porytiles_managed(const std::string &tileset_name) const
Updates asset fields to Porytiles-managed values.
ChainableResult< void > update_callback(const std::string &tileset_name, const std::string &new_callback_value) const
Updates the .callback field for a specific tileset.
ChainableResult< void > update_fields(const std::string &tileset_name, const std::map< std::string, std::string > &field_updates) const
Updates multiple fields for a specific tileset in headers.h.
Represents a parsed C struct variable declaration with its initializer fields.
const std::vector< DesignatedInitializerField > & fields() const
Returns all designated initializer fields.
static const Style bold
Bold text formatting.
virtual std::string format(const std::string &format_str, const std::vector< FormatParam > &params) const
Formats a string with styled parameters using fmtlib syntax.