Porytiles
Loading...
Searching...
No Matches
command_dump_attribute_schema.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <format>
4#include <iostream>
5#include <ostream>
6#include <string>
7
8#include "CLI/CLI.hpp"
9
16
17#include "command.hpp"
18#include "option.hpp"
20
21class DumpAttributeSchemaCommand final : public Command {
22 public:
23 explicit DumpAttributeSchemaCommand(CLI::App &parent_app)
24 : Command{parent_app, command_name, command_desc, command_group}
25 {
26 CLI::App &cmd = get_app();
27 cmd.add_option("<tileset-name>", tileset_name_, "Name of the tileset to resolve the schema for")->required();
28 cmd.add_flag(
29 "--allow-missing-tileset",
30 allow_missing_tileset_,
31 "Resolve the schema even when the tileset does not exist, to preview what a new tileset would get.");
32 project_root_opt_.RegisterOpt(cmd);
33 porytiles::register_config_options(cmd, cli_storage_);
34 }
35
36 private:
37 void Run() override
38 {
39 using namespace porytiles;
40
41 TilesetCommandEnv env{project_root_opt_.project_root(), cli_storage_};
42 auto *text_formatter = env.text_formatter;
43
44 // Env failures report through the unfiltered stderr diagnostics: the filtered diagnostics handle is not built
45 // until initialize() succeeds.
46 const auto env_result = env.initialize(tileset_name_);
47 if (!env_result.has_value()) {
48 const auto env_fail_result = ChainableResult<void>{
50 "Failed to dump attribute schema for tileset '{}'.", FormatParam{tileset_name_, Style::bold}},
51 env_result};
52 env.stderr_diag.fatal(env_fail_result);
53 throw CLI::RuntimeError{1};
54 }
55
56 // Verify the tileset exists in the project before resolving. The schema itself is supposed to be
57 // project-global, but our config system wires all values through the full resolution path, which always allows
58 // tileset-specific settings.
59 if (!allow_missing_tileset_) {
60 ProjectTilesetMetadataProvider metadata_provider{env.project_root, text_formatter, env.diag.get()};
61 if (!metadata_provider.exists(tileset_name_)) {
62 const auto not_found_err = ChainableResult<void>{FormattableError{
63 "Tileset '{}' does not exist. Pass '{}' to resolve its schema anyway.",
64 FormatParam{tileset_name_, Style::bold},
65 FormatParam{"--allow-missing-tileset", Style::bold}}};
66 env.diag->fatal(not_found_err);
67 throw CLI::RuntimeError{1};
68 }
69 }
70
71 // Resolve the schema with the users configured diagnostic settings. Users who opted in to all remarks may see
72 // redundant output. But that's fine because:
73 // 1. They opted in to noisy output, and there's an easy escape hatch
74 // 2. The remark noise goes to stderr, while the dump goes to stdout, so a capturing script can choose
75 MetatileAttributeSchemaResolver schema_resolver{env.project_root, &env.config, text_formatter, env.diag.get()};
76 auto resolved_result = schema_resolver.resolve(tileset_name_);
77
78 // A resolution failure is the same hard error that aborts compile and import: an ambiguous attribute size, a
79 // mask selection failure, or an invalid field. It reports as a fatal on stderr and exits nonzero rather than
80 // printing to stdout, since there is no schema to dump and a script checking the exit code must not read the
81 // failure as success.
82 if (!resolved_result.has_value()) {
83 const auto resolve_fail_result = ChainableResult<void>{
85 "Failed to dump attribute schema for tileset '{}'.", FormatParam{tileset_name_, Style::bold}},
86 resolved_result};
87 env.diag->fatal(resolve_fail_result);
88 throw CLI::RuntimeError{1};
89 }
90 const LoadedMetatileAttributeSchema &resolved = resolved_result.value();
91
92 std::ostream &out = std::cout;
93 const std::string section_title = "Resolved Metatile Attribute Schema";
94 out << text_formatter->style(section_title, Style::bold) << "\n";
95 out << text_formatter->style(std::string(section_title.size(), '='), Style::faint) << "\n\n";
96
97 out << " "
98 << text_formatter->format(
99 "Attribute size: {} bytes ({})",
100 FormatParam{std::to_string(resolved.attribute_bytes), Style::bold},
101 FormatParam{resolved.size_origin})
102 << "\n";
103
104 out << " "
105 << text_formatter->format(
106 "Declaration size: {} bytes (const u{}, {})",
107 FormatParam{std::to_string(resolved.declaration_bytes), Style::bold},
108 FormatParam{std::to_string(resolved.declaration_bytes * 8)},
110 << "\n";
111
112 if (!resolved.fields_origin.empty()) {
113 out << " " << text_formatter->format("Fields from: {}", FormatParam{resolved.fields_origin, Style::bold})
114 << "\n";
115 }
116 out << "\n";
117
118 out << " " << text_formatter->style("Fields:", Style::faint) << "\n";
119 for (const Field &field : resolved.schema.fields()) {
120 std::string provider_desc;
121 if (field.has_provider()) {
122 provider_desc = text_formatter->format(
123 " provider={} ({})",
124 FormatParam{field.provider_definition().header.string()},
125 FormatParam{field.provider_definition().prefix});
126 }
127 std::string role_desc;
128 if (field.role().has_value()) {
129 role_desc = text_formatter->format(" role={}", FormatParam{to_string(field.role().value())});
130 }
131 out << " "
132 << text_formatter->format(
133 "{} mask={} offset={} width={} default={}",
134 FormatParam{field.name(), Style::bold},
135 FormatParam{std::format("0x{:X}", field.mask())},
136 FormatParam{std::to_string(field.offset())},
137 FormatParam{std::to_string(field.width())},
138 FormatParam{std::to_string(field.default_value())})
139 << provider_desc << role_desc << "\n";
140 }
141 if (resolved.schema.layer_type_field() == nullptr) {
142 out << " "
143 << text_formatter->format(
144 "{}",
145 FormatParam{"(no field carries the layer_type role: layer types are disabled)", Style::faint})
146 << "\n";
147 }
148 out << "\n";
149 }
150
151 static constexpr auto command_name = "dump-attribute-schema";
152 static constexpr auto command_desc = "Dump the resolved metatile attribute schema for a tileset.";
153 static constexpr auto command_group = "UTILITIES";
154 std::string tileset_name_;
155 bool allow_missing_tileset_{false};
156 OptProjectRoot project_root_opt_;
157 porytiles::CliOptionStorage cli_storage_;
158};
Command is an abstract class that provides basic command functionality for the Porytiles CLI driver.
Definition command.hpp:16
CLI::App & get_app() const
Definition command.hpp:42
std::filesystem::path project_root() const
Definition option.hpp:58
void RegisterOpt(CLI::App &app) override
Definition option.hpp:46
A result type that maintains a chainable sequence of errors for debugging and error reporting.
One named bit-field within a metatile attribute layout.
A text parameter with associated styling for formatted output.
General-purpose error implementation with formatted message support.
Definition error.hpp:57
Resolves the invocation's metatile attribute schema: fetch config, scan, infer, reconcile.
ChainableResult< LoadedMetatileAttributeSchema > resolve(const std::string &tileset_name) const
Resolves the metatile attribute schema for the invocation.
Provides a pokeemerald project filesystem-based implementation for TilesetMetadataProvider.
const Field * layer_type_field() const
Returns the field carrying the layer_type role, or nullptr when the layer type is disabled.
Bootstrap class so every tileset command can share common config and diagnostic setup.
void register_config_options(CLI::App &app, CliOptionStorage &storage)
Registers all config options with a CLI11 App.
std::string to_string(const PrimaryPairingMode m)
Converts a PrimaryPairingMode to its canonical string representation.
Storage struct for CLI option values.
The product of reconciling the project's metatile attribute schema.
std::size_t declaration_bytes
The byte declaration width for gMetatileAttributes_* C decls.
std::string declaration_origin
Human-readable provenance notes.
std::string fields_origin
Human-readable provenance notes.
std::string size_origin
Human-readable provenance notes.