goptions
Loading...
Searching...
No Matches
goption.cc
Go to the documentation of this file.
1
14
15#include "goption.h"
16#include "goptionsConventions.h"
17#include "gutilities.h"
18
19// gemc
20#include "gutsConventions.h"
21
22#include <iostream>
23#include <algorithm>
24#include <functional>
25
26using std::cerr;
27using std::endl;
28using std::cout;
29using std::left;
30using std::string;
31using std::vector;
32
33// See goption.h for API docs.
34/*
35 * Implementation notes:
36 * - Scalar values are normalized to preserve legacy comma-delimited payloads.
37 * - For structured options, the behavior differs between cumulative and non-cumulative schemas.
38 */
39void GOption::set_scalar_value(const string& v) {
40 // Legacy normalization: remove commas so payloads like "a,b,c" remain shell-friendly.
41 string value_to_set = gutilities::replaceCharInStringWithChars(v, ",", "");
42
43 // Scalar options are stored as a single-entry map: { <name>: <scalar> }.
44 auto key = value.begin()->first.as<string>();
45 value[key] = value_to_set;
46}
47
48// See goption.h for API docs.
49/*
50 * Implementation notes:
51 * - Cumulative options store a user-provided sequence of maps and then back-fill optional keys
52 * from the schema defaults.
53 * - Non-cumulative structured options update existing key/value pairs in-place (by matching keys).
54 */
55void GOption::set_value(const YAML::Node& v) {
56 if (isCumulative) {
57 // Validate that each user-provided entry includes all mandatory keys.
58 for (const auto& element : v) {
59 if (!does_the_option_set_all_necessary_values(element)) {
60 cerr << guts::FATALERRORL << "Trying to set " << guts::YELLOWHHL << name << guts::RSTHHR
61 << " but missing mandatory values." << endl;
62 cerr << " Use the option: " << guts::YELLOWHHL << " help " << name
63 << " " << guts::RSTHHR << " for details." << endl << endl;
65 }
66 }
67
68 // Store the full sequence exactly as provided by the user.
69 value[name] = v;
70
71 // Back-fill optional keys from the schema default sequence.
72 // The default schema is stored as a sequence of single-entry maps.
73 auto default_value_node = defaultValue.begin()->second;
74
75 for (const auto& map_element_in_default_value : default_value_node) {
76 for (auto default_value_iterator = map_element_in_default_value.begin();
77 default_value_iterator != map_element_in_default_value.end(); ++default_value_iterator) {
78 auto default_key = default_value_iterator->first.as<string>();
79 auto default_value = default_value_iterator->second;
80
81 // For each user entry, ensure default_key exists; if not, assign schema default.
82 for (auto map_element_in_value : value[name]) {
83 bool key_found = false;
84
85 for (auto value_iterator = map_element_in_value.begin();
86 value_iterator != map_element_in_value.end(); ++value_iterator) {
87 auto value_key = value_iterator->first.as<string>();
88 if (default_key == value_key) {
89 key_found = true;
90 break;
91 }
92 }
93
94 if (!key_found) {
95 map_element_in_value[default_key] = default_value;
96 }
97 }
98 }
99 }
100 }
101 else {
102 // Non-cumulative structured update:
103 // Iterate over desired values and update matching keys in the existing stored structure.
104 const auto update_existing_value = [this](const YAML::Node& desired_key, const YAML::Node& desired_value) {
105 for (auto existing_map : value[name]) {
106 for (auto existing_map_iterator = existing_map.begin();
107 existing_map_iterator != existing_map.end(); ++existing_map_iterator) {
108 auto first_key = existing_map_iterator->first.as<string>();
109 auto second_key = desired_key.as<string>();
110
111 // Only update entries whose key matches the requested update key.
112 if (first_key == second_key) {
113 existing_map[existing_map_iterator->first] = desired_value;
114 }
115 }
116 }
117 };
118
119 if (v.IsMap()) {
120 for (auto desired_value_iterator = v.begin();
121 desired_value_iterator != v.end(); ++desired_value_iterator) {
122 update_existing_value(desired_value_iterator->first, desired_value_iterator->second);
123 }
124 }
125 else {
126 for (const auto& map_element_in_desired_value : v) {
127 for (auto desired_value_iterator = map_element_in_desired_value.begin();
128 desired_value_iterator != map_element_in_desired_value.end(); ++desired_value_iterator) {
129 update_existing_value(desired_value_iterator->first, desired_value_iterator->second);
130 }
131 }
132 }
133 }
134}
135
136// See goption.h for API docs.
137/*
138 * Implementation notes:
139 * - The input is expected to be one element of a cumulative sequence (typically a map).
140 * - We only check keys; type/shape constraints are not enforced here.
141 */
142bool GOption::does_the_option_set_all_necessary_values(const YAML::Node& v) {
143 for (const auto& key : mandatory_keys) {
144 if (!v[key] || v[key].IsNull()) return false;
145 }
146 return true;
147}
148
149// See goption.h for API docs.
150/*
151 * Implementation notes:
152 * - Writes comment lines for nulls so the saved YAML explains which values were not provided.
153 * - Produces a cleaned copy of the node (nulls replaced) to keep output valid YAML scalars.
154 */
155void GOption::saveOption(std::ofstream* yamlConf) const {
156 std::vector<std::string> missing; // paths of null values
157
158 // --------------------------------------------------------------------
159 // recursive lambda: returns a *new* node with nulls → "not provided"
160 // --------------------------------------------------------------------
161 std::function<YAML::Node(YAML::Node, std::string)> clean =
162 [&](YAML::Node n, const std::string& path) -> YAML::Node {
163 if (n.IsNull()) {
164 missing.push_back(path.empty() ? name : path);
165 return YAML::Node("not provided"); // null replaced
166 }
167
168 if (n.IsMap()) {
169 YAML::Node res(YAML::NodeType::Map);
170 for (auto it : n) {
171 const std::string key = it.first.as<std::string>();
172 res[it.first] = clean(it.second, path.empty() ? key : path + "." + key);
173 }
174 return res;
175 }
176
177 if (n.IsSequence()) {
178 YAML::Node res(YAML::NodeType::Sequence);
179 for (std::size_t i = 0; i < n.size(); ++i) {
180 res.push_back(clean(n[i], path + "[" + std::to_string(i) + "]"));
181 }
182 return res;
183 }
184
185 return n; // scalar, already OK
186 };
187
188 YAML::Node out = clean(value, ""); // fully cleaned copy
189
190 // --------------------------------------------------------------------
191 // write one comment line per missing entry
192 // --------------------------------------------------------------------
193 for (const auto& p : missing) {
194 *yamlConf << "# " << p << " not provided\n";
195 }
196
197 // write the YAML itself (block style)
198 out.SetStyle(YAML::EmitterStyle::Block);
199 *yamlConf << out << '\n';
200}
201
202
203// See goption.h for API docs.
204/*
205 * Implementation notes:
206 * - Summary help is a single aligned line.
207 * - Detailed help includes schema defaults + extended multi-line help payload.
208 */
209void GOption::printHelp(bool detailed) const {
210 if (name == goptions::GVERSION_STRING) return;
211
212 long int fill_width = string(goptions::HELPFILLSPACE).size() + 1;
213 cout.fill('.');
214
215 string helpString = "-" + name + guts::RST;
216 bool is_sequence = defaultValue.begin()->second.IsSequence();
217 helpString += is_sequence ? "=<sequence>" : "=<value>";
218 helpString += " ";
219
220 cout << guts::KGRN << " " << left;
221 cout.width(fill_width);
222
223 if (detailed) {
224 cout << helpString << ": " << description << endl << endl;
225 cout << detailedHelp() << endl;
226 }
227 else {
228 cout << helpString << ": " << description << endl;
229 }
230}
231
232// See goption.h for API docs.
233/*
234 * Implementation notes:
235 * - If the default schema is a sequence, print each key with its per-key description and default value.
236 * - Then append the free-form help text, preserving user formatting.
237 */
238string GOption::detailedHelp() const {
239 string newHelp;
240 YAML::Node yvalues = defaultValue.begin()->second;
241
242 if (yvalues.IsSequence()) {
243 newHelp += "\n";
244
245 for (unsigned i = 0; i < yvalues.size(); i++) {
246 YAML::Node this_node = yvalues[i];
247
248 for (auto it = this_node.begin(); it != this_node.end(); ++it) {
249 const std::string defaultDescription = gvar_required[i]
250 ? "required"
251 : (it->second.IsNull() ? "not set" : YAML::Dump(it->second));
252 cout << guts::TGREENPOINTITEM << " " << guts::KGRN << it->first.as<string>() << guts::RST
253 << ": " << gvar_descs[i] << "Default value: " << defaultDescription << endl;
254 }
255 }
256 }
257
258 newHelp += "\n";
259 vector<string> help_lines = gutilities::getStringVectorFromStringWithDelimiter(help, "\n");
260 for (const auto& line : help_lines) {
261 newHelp += guts::GTAB + line + "\n";
262 }
263
264 return newHelp;
265}
266
267// See goption.h for API docs.
268/*
269 * Implementation notes:
270 * - Dot-notation updates apply to existing structured storage.
271 * - If the stored node is a sequence, all map elements containing subkey are updated.
272 * - If the stored node is a map, only that entry is updated.
273 */
274void GOption::set_sub_option_value(const string& subkey, const string& subvalue) {
275 YAML::Node option_node = value.begin()->second;
276
277 if (option_node.IsSequence()) {
278 bool updated = false;
279
280 for (auto it = option_node.begin(); it != option_node.end(); ++it) {
281 // Only update entries that are maps and already contain subkey.
282 if ((*it).IsMap() && (*it)[subkey]) {
283 (*it)[subkey] = YAML::Load(subvalue);
284 updated = true;
285 }
286 }
287
288 if (!updated) {
289 cerr << "Sub-option key '" << subkey << "' not found in option '" << name << "'." << endl;
291 }
292 }
293 else if (option_node.IsMap()) {
294 if (option_node[subkey]) {
295 option_node[subkey] = YAML::Load(subvalue);
296 }
297 else {
298 cerr << "Sub-option key '" << subkey << "' not found in option '" << name << "'." << endl;
300 }
301 }
302 else {
303 cerr << "Option '" << name << "' is not structured to accept sub–options." << endl;
305 }
306}
void set_sub_option_value(const std::string &subkey, const std::string &subvalue)
Updates a structured sub-option using dot-notation semantics.
Definition goption.cc:274
Definitions of GVariable : and GOption : used by GOptions : .
Conventions, constants, and error codes for the GOptions : / GOption : subsystem.
constexpr int EC__NOOPTIONFOUND
Option/switch/key not found, or invalid command-line token.
constexpr int EC__MANDATORY_NOT_FILLED
Mandatory structured option key missing.
constexpr char GVERSION_STRING[]
Reserved option tag used to store version information.
constexpr char HELPFILLSPACE[]
Padding used when printing option/switch help.
vector< string > getStringVectorFromStringWithDelimiter(const string &input, const string &x)
string replaceCharInStringWithChars(const std::string &input, const std::string &toReplace, const std::string &replacement)
constexpr char TGREENPOINTITEM[]
constexpr char YELLOWHHL[]
constexpr char RST[]
constexpr char FATALERRORL[]
constexpr char RSTHHR[]
constexpr char KGRN[]
constexpr char GTAB[]