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