Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
cli/configuration.hpp
Go to the documentation of this file.
1
5#pragma once
6#include "cli.hpp"
7#include <algorithm>
8#include <concepts>
9#include <cstddef>
10#include <cstdlib>
11#include <functional>
12#include <optional>
13#include <stdexcept>
14#include <string>
16#include <utility>
17#include <vector>
18
19namespace uni20::cli
20{
26 public:
27 explicit configuration(
28 CLI::App& app,
30 [](std::string const& name) -> std::optional<std::string> {
31 if (auto p = std::getenv(name.c_str())) return std::string(p);
32 return std::nullopt;
33 })
34 : app_(app), values_(std::move(environment))
35 {}
36 configuration(configuration const&) = delete;
38
43 template <ConfigurationValue T>
45 configuration_binding& bind(CLI::Option* option, T& target, std::string id)
46 {
47 if (parsed_) throw std::logic_error("configuration bindings must precede parsing");
48 if (!option) throw std::invalid_argument("null CLI option");
49 if constexpr (!std::same_as<T, bool>)
50 if (option->get_type_size_max() == 0)
51 throw std::invalid_argument("only boolean flags can be configuration bindings");
52 if (std::ranges::any_of(options_, [&](auto const& b) { return b.option == option; }))
53 throw std::invalid_argument("CLI option already bound");
54 auto& binding = values_.add<T>(id, target);
55 if (option->get_required()) binding.required().without_default();
56 if (!option->get_envname().empty()) binding.environment(option->get_envname());
57 option->envname("")->configurable(false);
58 binding.convert_ = [option, &target](metadata_value const& value, value_origin const& origin) {
59 // CLI11 owns its validators, transforms and callback. Do not reimplement them.
60 // Explicit CLI input has already run that chain once; transforms need not be idempotent.
61 if (origin.source == configuration_source::command_line)
62 {
63 target = value.get<T>();
64 return value;
65 }
66 if (value.missing()) throw std::invalid_argument("missing value for CLI scalar option");
67 auto text = [&] {
68 if constexpr (std::same_as<T, std::string>)
69 return value.text({}, true);
70 else
71 return value.type() == typeid(std::string) ? value.get<std::string>()
72 : metadata_value(value.as<T>()).text({}, true);
73 }();
74 option->clear();
75 option->add_result(text);
76 option->run_callback();
77 return metadata_value(target);
78 };
79 options_.push_back({option, std::move(id), [&target] { return metadata_value(target); }});
80 return binding;
81 }
82
84 CLI::Option* option_file(std::string names = "--config")
85 {
86 if (parsed_ || file_option_) throw std::logic_error("option file must be declared once before parsing");
87 file_option_ = app_.add_option(std::move(names), file_path_, "Read selected options from a TOML/INI file");
88 file_option_->configurable(false);
89 return file_option_;
90 }
91
92 [[nodiscard]] parse_result parse(int argc, char const* const* argv, parse_policy policy = {})
93 {
94 if (parsed_) throw std::logic_error("configuration parser is single-use");
95 if (app_.get_config_ptr())
96 throw std::invalid_argument("use configuration::option_file instead of CLI11 set_config");
97 parsed_ = true;
98 for (auto const& b : options_)
99 {
100 for (auto const* other : app_.get_options())
101 if (other->get_needs().contains(b.option) || other->get_excludes().contains(b.option))
102 throw std::invalid_argument("resolve cross-option constraints after configuration sources");
103 if (!b.option->get_needs().empty() || !b.option->get_excludes().empty() || b.option->get_trigger_on_parse() ||
104 b.option->get_force_callback() || b.option->get_expected_max() > 1 || b.option->get_type_size_max() > 1)
105 throw std::invalid_argument("bound configuration options must be independent scalar options");
106 }
107 std::vector<bool> required;
108 for (auto const& b : options_)
109 {
110 required.push_back(b.option->get_required());
111 if (b.option->get_required()) values_.binding(b.id).required().without_default();
112 if (!b.option->get_envname().empty())
113 {
114 values_.binding(b.id).environment(b.option->get_envname());
115 b.option->envname("");
116 }
117 b.option->required(false);
118 }
119 auto restore = [&] {
120 for (std::size_t i = 0; i < options_.size(); ++i)
121 options_[i].option->required(required[i]);
122 };
123 parse_result result;
124 try
125 {
126 result = cli::parse(app_, argc, argv, policy);
127 }
128 catch (...)
129 {
130 restore();
131 throw;
132 }
133 restore();
134 if (result.requested != action::run) return result;
135 for (auto const& b : options_)
136 if (b.option->count())
137 {
138 values_.set(b.id, b.read());
139 values_.cli_.insert(b.id);
140 }
141 ready_ = true;
142 return result;
143 }
144
148 {
149 if (!ready_) throw std::logic_error("configuration is not ready for resolution");
150 if (!file_option_ || !file_option_->count()) return;
151 attribute_map fields;
152 for (auto const& item : app_.get_config_formatter()->from_file(file_path_))
153 {
154 if (item.name == "++" || item.name == "--") continue;
155 if (item.inputs.size() != 1) throw std::invalid_argument("configuration file requires scalar values");
156 if (!fields.emplace(item.fullname(), item.inputs.front()).second)
157 throw std::invalid_argument("duplicate option-file key: " + item.fullname());
158 }
159 values_.option_file(file_path_, std::move(fields));
160 }
161
162 [[nodiscard]] uni20::configuration& values() { return values_; }
165 parse_result const& result) const
166 {
167 auto report = cli::result_report(app_, program, result);
168 if (result.requested != action::help) return report;
169 auto& table = report.table("Configuration sources (highest priority first)");
170 table.column("Option").column("Sources after explicit CLI/API input").preserve_tokens();
171 for (auto const& option : options_)
172 {
173 auto const& b = values_.binding(option.id);
174 std::string sources;
175 auto append = [&](std::string text) {
176 if (!sources.empty()) sources += "; ";
177 sources += text;
178 };
179 if (b.file_) append("file key " + *b.file_);
180 if (b.attribute_) append("attribute " + b.attribute_->first + ":" + b.attribute_->second);
181 if (b.environment_) append("environment " + *b.environment_);
182 if (b.default_)
183 append("default " + b.default_->text());
184 else if (b.required_)
185 append("required after loading sources");
186 table.row(option.option->get_name(), sources);
187 }
188 return report;
189 }
191 [[nodiscard]] metadata_document resolve() const
192 {
193 if (!ready_) throw std::logic_error("configuration is not ready for resolution");
194 return values_.snapshot();
195 }
196
197 private:
198 struct bound_option
199 {
200 CLI::Option* option;
201 std::string id;
202 std::function<metadata_value()> read;
203 };
204 CLI::App& app_;
205 uni20::configuration values_;
206 std::vector<bound_option> options_;
207 CLI::Option* file_option_ = nullptr;
208 std::string file_path_;
209 bool parsed_ = false;
210 bool ready_ = false;
211};
212} // namespace uni20::cli
Collect CLI input first, then resolve declared fallback sources after loading input attributes.
Definition cli/configuration.hpp:25
configuration(configuration const &)=delete
configuration & operator=(configuration const &)=delete
configuration_binding & bind(CLI::Option *option, T &target, std::string id)
Bind a declared CLI11 option to a stable metadata id, capturing its initialized value as default.
Definition cli/configuration.hpp:45
configuration(CLI::App &app, uni20::configuration::environment_reader environment=[](std::string const &name) -> std::optional< std::string > { if(auto p=std::getenv(name.c_str())) return std::string(p);return std::nullopt;})
Definition cli/configuration.hpp:27
parse_result parse(int argc, char const *const *argv, parse_policy policy={})
Definition cli/configuration.hpp:92
void load_option_file()
Read the explicitly requested file using CLI11's configured TOML/INI reader.
Definition cli/configuration.hpp:147
metadata_document resolve() const
Resolve all bindings, assigning native CLI variables and returning owned metadata.
Definition cli/configuration.hpp:191
uni20::configuration & values()
Definition cli/configuration.hpp:162
presentation::report_builder result_report(presentation::program_info const &program, parse_result const &result) const
Include declared fallback sources in help, without evaluating or opening them.
Definition cli/configuration.hpp:164
CLI::Option * option_file(std::string names="--config")
Register an explicit option-file path; help never opens the file.
Definition cli/configuration.hpp:84
A single declared scalar, with application-selected fallback sources.
Definition common/configuration.hpp:43
configuration_binding & required(bool value=true)
Definition common/configuration.hpp:60
configuration_binding & environment(std::string name)
Definition common/configuration.hpp:50
configuration_binding & without_default()
Require an actual source instead of treating the initialized variable as a fallback.
Definition common/configuration.hpp:66
Parser-independent configuration with CLI/API > file > attribute > environment > default precedence.
Definition common/configuration.hpp:103
configuration_binding & add(std::string id, T initial)
Definition common/configuration.hpp:113
std::function< std::optional< std::string >(std::string const &)> environment_reader
Definition common/configuration.hpp:105
void row(std::vector< formatted_cell > cells, std::source_location where=std::source_location::current())
Emit preformatted cells without discarding their numeric alignment semantics.
Definition display.cpp:448
streaming_table & column(std::string heading, column_width width_spec=width::share(), presentation::table_alignment alignment=presentation::table_alignment::right, column_format format={})
Ordered, uniquely identified metadata. Copies are independent snapshots.
Definition metadata.hpp:306
An immutable owned value that retains its native scalar and optional type.
Definition metadata.hpp:66
std::string text(presentation::data_column_display const &format={}, bool machine=false) const
Definition metadata.hpp:76
Builder for simple terminal reports with headings, status lines, fields, and tables.
Definition presentation.hpp:602
CLI11 option adapters, explicit parse outcomes and semantic application help.
Explicit source bindings and checked configuration resolution.
locale_t value
Definition data_table.cpp:49
presentation::styled_text text
Definition display.cpp:141
Definition qnum.hpp:403
Definition cli.cpp:6
presentation::report_builder result_report(CLI::App const &app, presentation::program_info const &program, parse_result const &result)
Build the selected informational/error document; a run outcome has no document and is rejected.
Definition cli.cpp:190
parse_result parse(CLI::App &app, int argc, char const *const *argv, parse_policy policy)
Parse argc/argv, preserving CLI11's argument boundaries and help precedence.
Definition cli.cpp:116
Definition data_table.cpp:8
std::map< std::string, metadata_value > attribute_map
Definition common/configuration.hpp:26
Executable policy for empty invocations and usage failures.
Definition cli.hpp:39
Owned parse outcome; parsing itself does not print, open files or terminate the process.
Definition cli.hpp:48
action requested
Definition cli.hpp:49
Application identity and explanatory content, independent of any option parser.
Definition program.hpp:32
Identifies the winning source, without retaining discarded candidates.
Definition metadata.hpp:275