Learning Curve Plus Plus (LCPP)
clistore.h
Go to the documentation of this file.
1 /**
2  * @file clistore.h
3  * @author Ozgur Taylan Turan
4  *
5  * A Simple holder for all your applications where you can write easier
6  * command-line interfaces...
7  */
8 
9 #ifndef CLISTORE_H
10 #define CLISTORE_H
11 
12 //-----------------------------------------------------------------------------
13 // CLIStore : This is a command line interface storing singleton.
14 // It has some nice functionality that might be of use while desining apps
15 // for HPC usage.
16 //-----------------------------------------------------------------------------
17 class CLIStore
18 {
19 public:
20  // Define the supported value types
21  using FlagValue = std::variant<int, float, double, std::string, bool, size_t>;
22 
23  // Singleton access
24  static CLIStore& GetInstance()
25  {
26  static CLIStore instance;
27  return instance;
28  }
29  /////////////////////////////////////////////////////////////////////////////
30  // Register a flag using the long name
31  template<typename T>
32  void Register( const std::string& longName, const T& defaultValue )
33  {
34  if (flags_.count(longName))
35  throw std::runtime_error("Flag already registered: " + longName);
36 
37  flags_[longName] = defaultValue; // Store the flag
38  }
39  /////////////////////////////////////////////////////////////////////////////
40  template<typename T>
41  void Register( const std::string& longName,
42  const T& defaultValue,
43  const std::vector<T>& options )
44  {
45  this->Register(longName, defaultValue);
46 
47  std::vector<FlagValue> convertedOptions;
48  convertedOptions.reserve(options.size());
49 
50  for (const T& val : options)
51  convertedOptions.push_back(val); // Implicitly converts to FlagValue
52 
53  options_[longName] = std::move(convertedOptions);
54  }
55  /////////////////////////////////////////////////////////////////////////////
56  // Get flag value by name
57  template<typename T>
58  T Get ( const std::string& name ) const
59  {
60  auto it = flags_.find(name);
61  if (it == flags_.end())
62  throw std::runtime_error("Flag not found: " + name);
63 
64  return std::get<T>(it->second); // Return typed value
65  }
66  /////////////////////////////////////////////////////////////////////////////
67  // Get flag options by name
68  template<typename T>
69  std::vector<T> GetOptions( const std::string& name ) const
70  {
71  auto it = options_.find(name);
72  if (it == options_.end())
73  throw std::runtime_error("Flag not found: " + name);
74 
75  return _ExtractTypedVector<T>(it->second, name);
76  }
77  /////////////////////////////////////////////////////////////////////////////
78  // Set flag value by name
79  template<typename T>
80  void Set ( const std::string& name, const T& value )
81  {
82  auto it = flags_.find(name);
83  if (it == flags_.end())
84  throw std::runtime_error("Flag not found: " + name);
85 
86  it->second = value; // Update the flag
87  }
88  /////////////////////////////////////////////////////////////////////////////
89  // Parse command-line arguments
90  void Parse ( int argc, char** argv )
91  {
92  for (int i = 1; i < argc; ++i)
93  {
94  std::string arg = argv[i];
95 
96  // Handle long flag: --flag
97  if (arg.size() >= 3 && arg.substr(0, 2) == "--")
98  {
99  std::string flag = arg.substr(2);
100 
101  if (flags_.count(flag) == 0)
102  throw std::runtime_error("Error: Unknown flag '--" + flag + "'");
103 
104  std::string value;
105  if (i + 1 < argc && argv[i + 1][0] != '-')
106  value = argv[++i];
107  else
108  {
109  // Value is required unless type is bool
110  if (!std::holds_alternative<bool>(flags_[flag]))
111  throw std::runtime_error("Error: Missing value for flag '--" +
112  flag + "'");
113  value = "true";
114  }
115 
116  Set(flag, ParseValue(flag, value));
117  }
118 
119  // Unexpected argument format
120  else
121  throw std::runtime_error("Error: Invalid argument format '" + arg + "'");
122  }
123  }
124  /////////////////////////////////////////////////////////////////////////////
125  // Print what you are holding
126  void Print ( std::ostream& out = std::cout )
127  {
128  out << std::string(50, '-') << "\n"
129  << std::left
130  << std::setw(25) << "Flag Name"
131  << std::setw(25) << "Flag Value" << std::endl
132  << std::string(50, '-') << "\n";
133 
134  for (const auto &entry : flags_)
135  {
136  const auto &key = entry.first;
137  const auto &value = entry.second;
138 
139  std::string value_str;
140  std::string type_str;
141 
142  std::visit([&](const auto &val)
143  {
144  std::ostringstream oss;
145  oss << val;
146  value_str = oss.str();
147 
148  }, value);
149 
150  out << std::left
151  << std::setw(25) << key
152  << std::setw(25) << value_str
153  << "\n";
154  }
155  out << std::string(50, '-') << "\n";
156  }
157  /////////////////////////////////////////////////////////////////////////////
158  // Sanatize the name
159  std::string Sanitize ( const std::string &input ) const
160  {
161  std::string output = input;
162 
163  // Replace '.' with 'p' (e.g., 0.01 → 0p01)
164  std::replace(output.begin(), output.end(), '.', 'p');
165 
166  // Remove or replace other non-alphanumerics as needed
167  for (char &c : output)
168  if (!std::isalnum(static_cast<unsigned char>(c)))
169  c = '_';
170 
171  return output;
172  }
173  /////////////////////////////////////////////////////////////////////////////
174  // Generate a unique name with the keys you are holding
175  std::string GenName ( ) const
176  {
177  std::vector<std::string> keys;
178  for (const auto &pair : flags_)
179  {
180  keys.push_back(pair.first);
181  }
182  std::sort(keys.begin(), keys.end()); // deterministic order
183 
184  std::ostringstream oss;
185 
186  for (const auto &key : keys)
187  {
188  const auto &value = flags_.at(key);
189 
190  std::string value_str;
191  std::visit([&](const auto &val)
192  {
193  std::ostringstream val_stream;
194 
195  if constexpr (std::is_same_v<decltype(val), bool>)
196  val_stream << (val ? "true" : "false");
197  else
198  val_stream << val;
199 
200  value_str = val_stream.str();
201  }, value);
202 
203  oss << Sanitize(key) << "_" << Sanitize(value_str) << "-";
204  }
205 
206  std::string result = oss.str();
207  if (!result.empty())
208  result.erase(result.size() - 1); // remove last "__"
209 
210  return result;
211  }
212  /////////////////////////////////////////////////////////////////////////////
213  // Generate a unique name with the specified keys
214  std::string GenName(const std::vector<std::string>& include_keys) const
215  {
216  std::vector<std::string> keys;
217  for (const auto &key : include_keys)
218  {
219  if (flags_.find(key) != flags_.end())
220  {
221  keys.push_back(key);
222  }
223  }
224  std::sort(keys.begin(), keys.end()); // deterministic order
225 
226  std::ostringstream oss;
227 
228  for (const auto &key : keys)
229  {
230  const auto &value = flags_.at(key);
231 
232  std::string value_str;
233  std::visit([&](const auto &val)
234  {
235  std::ostringstream val_stream;
236 
237  if constexpr (std::is_same_v<decltype(val), bool>)
238  val_stream << (val ? "true" : "false");
239  else
240  val_stream << val;
241 
242  value_str = val_stream.str();
243  }, value);
244 
245  oss << Sanitize(key) << "_" << Sanitize(value_str) << "-";
246  }
247 
248  std::string result = oss.str();
249  if (!result.empty())
250  result.erase(result.size() - 1); // remove last '+'
251  return result;
252  }
253 
254 private:
255 
256  template<typename T>
257  std::vector<T> _ExtractTypedVector(const std::vector<FlagValue>& input,
258  const std::string& name) const
259  {
260  std::vector<T> output;
261  for (const auto& val : input)
262  {
263  if (!std::holds_alternative<T>(val))
264  throw std::runtime_error("Type mismatch in options for flag: " + name);
265 
266  output.push_back(std::get<T>(val));
267  }
268  return output;
269  }
270  /////////////////////////////////////////////////////////////////////////////
271 
272  CLIStore() = default; // Private constructor
273  // Store flag values
274  std::unordered_map<std::string, FlagValue> flags_;
275  // options for the flags
276  std::unordered_map<std::string, std::vector<FlagValue>> options_;
277 
278  // Convert string value to proper type based on registered default
279  FlagValue ParseValue(const std::string& name, const std::string& valueStr)
280  {
281  const auto& val = flags_.at(name);
282  if (std::holds_alternative<int>(val))
283  return std::stoi(valueStr);
284  if (std::holds_alternative<size_t>(val))
285  return static_cast<size_t>(std::stoi(valueStr));
286  if (std::holds_alternative<float>(val))
287  return std::stof(valueStr);
288  if (std::holds_alternative<double>(val))
289  return std::stod(valueStr);
290  if (std::holds_alternative<std::string>(val))
291  return valueStr;
292  if (std::holds_alternative<bool>(val))
293  return valueStr == "true" || valueStr == "1";
294  throw std::runtime_error("Unsupported flag type for: " + name);
295  }
296 };
297 #endif
298 
299 
300 
301