qxLib
layered_configs_manager.inl
Go to the documentation of this file.
1 /**
2 
3  @file layered_configs_manager.inl
4  @author Khrapov
5  @date 12.05.2026
6  @copyright (c) Nick Khrapov, 2026. All right reserved.
7 
8 **/
9 
10 namespace qx
11 {
12 
13 namespace details
14 {
15 
16 template<class T>
18 {
19  using type = T;
20 };
21 
22 template<>
23 struct layered_config_variable_type<cstring_view>
24 {
25  using type = cstring;
26 };
27 
28 } // namespace details
29 
30 inline void layered_configs_manager::set_app_description(cstring sAppDescription) noexcept
31 {
32  m_sAppDescription = std::move(sAppDescription);
33 }
34 
35 inline void layered_configs_manager::set_args(int argc, char* argv[]) noexcept
36 {
37  if (argc <= 0 || argv == nullptr)
38  {
39  m_Args = {};
40  return;
41  }
42 
43  m_Args = std::span(argv, static_cast<size_t>(argc));
44 }
45 
46 inline bool layered_configs_manager::show_help() const noexcept
47 {
48  if (!contains_if(
49  m_Args,
50  [](const char* const pszValue)
51  {
52  cstring_view svValue(pszValue);
53  return svValue == "--help" || svValue == "-h";
54  }))
55  {
56  return false;
57  }
58 
59  struct data_with_runtime_name : details::layered_config_variable_data
60  {
61  cstring_view svRuntimeName;
62  };
63 
64  std::map<cstring_view, std::vector<data_with_runtime_name>> groups;
65  for (const auto& [svRuntimeName, data] : m_VariableData)
66  {
67  groups[data.svGroupName].emplace_back(data, svRuntimeName);
68  }
69 
70  for (std::vector<data_with_runtime_name>& lines : groups | std::views::values)
71  {
72  sort(
73  lines,
74  [](const data_with_runtime_name& left, const data_with_runtime_name& right)
75  {
76  return left.bRequired > right.bRequired && left.svFullCommandLineName > right.svFullCommandLineName;
77  });
78  }
79 
80  cstring sHelp;
81  if (!groups.empty())
82  {
83  constexpr cstring_view svRequired = "required ";
84 
85  size_t nMaxFullCommandLineNameLength = 0;
86  size_t nMaxShortCommandLineNameLength = 0;
87  size_t nMaxTypeNameLength = 0;
88  size_t nMaxRequiredNameLength = 0;
89  for (const std::vector<data_with_runtime_name>& lines : groups | std::views::values)
90  {
91  for (const data_with_runtime_name& line : lines)
92  {
93  nMaxFullCommandLineNameLength =
94  std::max(nMaxFullCommandLineNameLength, line.svFullCommandLineName.size());
95  nMaxShortCommandLineNameLength =
96  std::max(nMaxShortCommandLineNameLength, line.svShortCommandLineName.size());
97  nMaxTypeNameLength = std::max(nMaxTypeNameLength, line.svTypeName.size());
98  nMaxRequiredNameLength = std::max(
99  nMaxRequiredNameLength,
100  line.bRequired ? svRequired.size() : line.pTToString(m_Variables.at(line.svRuntimeName)).size());
101  }
102  }
103 
104  sHelp.append_format("\n\nUsage: {} [options]\n", m_Args[0]);
105  sHelp += "Using formats: [--key], [-k], [--key=value], [-k=value], [--key value], [-k value]\n\n";
106 
107  if (!m_sAppDescription.empty())
108  sHelp.append_format("{}\n\n", m_sAppDescription);
109 
110  for (const auto& [svGroupName, lines] : groups)
111  {
112  if (!svGroupName.empty())
113  sHelp.append_format("{}:\n", svGroupName);
114  else
115  sHelp += "General options:\n";
116 
117  sHelp +=
118  "------------------------------------------------------------------------------------------------------"
119  "------------------\n";
120 
121  for (const data_with_runtime_name& line : lines)
122  {
123  sHelp.append_format(
124  "{:<{}} {:<{}} {:<{}} {:<{}}{}\n",
125  line.svFullCommandLineName,
126  nMaxFullCommandLineNameLength,
127  line.svShortCommandLineName,
128  nMaxShortCommandLineNameLength,
129  line.svTypeName,
130  nMaxTypeNameLength,
131  line.bRequired ? svRequired
132  : cstring_view(line.pTToString(m_Variables.at(line.svRuntimeName)) + " "),
133  nMaxRequiredNameLength,
134  line.svDescription);
135  }
136 
137  sHelp += "\n";
138  }
139  }
140  else
141  {
142  sHelp += "This app doesn't have any command line arguments.\n\n";
143  if (!m_sAppDescription.empty())
144  sHelp.append_format("{}\n\n", m_sAppDescription);
145  }
146 
147  QX_LOG(qx::verbosity::important, qx::to_string(sHelp));
148 
149  return true;
150 }
151 
152 inline bool layered_configs_manager::parse() noexcept
153 {
154  std::unordered_map<cstring_view, std::any> variables;
155 
156  // read default variables
157  for (const auto& [svRuntimeName, value] : m_DefaultVariables)
158  if (!m_Variables.contains(svRuntimeName))
159  variables[svRuntimeName] = value;
160 
161  // read envs
162  for (const auto& [svRuntimeName, data] : m_VariableData)
163  {
164  if (const char* pszEnvValue = std::getenv(cstring(data.svEnvName).c_str()))
165  {
166  std::optional<std::any> optValue = data.pStringToT(pszEnvValue);
167  if (!optValue)
168  continue;
169 
170  variables[svRuntimeName] = std::move(*optValue);
171  }
172  }
173 
174  // read command lines args
175  for (size_t i = 1; i < m_Args.size(); ++i)
176  {
177  const cstring_view svArgument = m_Args[i];
178 
179  bool bShortKey = false;
180  cstring_view svKey;
181  cstring_view svValue;
182 
183  svKey = cstring_view(svArgument.begin(), svArgument.end());
184  if (svArgument.starts_with("--"))
185  {
186  bShortKey = false;
187  }
188  else if (svArgument.starts_with("-"))
189  {
190  bShortKey = true;
191  }
192  else
193  {
194  QX_LOG(
195  qx::verbosity::warning,
196  "Invalid command argument format: {}. "
197  "Use \"--key=value\", \"--key value\", \"-k value\", \"--key\", \"-k\" instead.",
198  qx::to_string(svArgument));
199  continue;
200  }
201 
202  // --key=value / -k=value
203  if (const size_t nEqualPos = svKey.find('='); nEqualPos != cstring_view::npos)
204  {
205  svValue = cstring_view(svKey.begin() + nEqualPos + 1, svKey.end());
206  svKey = cstring_view(svKey.begin(), svKey.begin() + nEqualPos);
207  }
208  // --key value / -k value
209  else if (i + 1 < m_Args.size())
210  {
211  const cstring_view svNextArgument = m_Args[i + 1];
212 
213  if (!svNextArgument.starts_with("-"))
214  {
215  svValue = svNextArgument;
216  ++i;
217  }
218  else
219  {
220  // --key / -k
221  svValue = "true";
222  }
223  }
224  // --key / -k
225  else
226  {
227  svValue = "true";
228  }
229 
230  if (svKey.empty())
231  {
232  QX_LOG(qx::verbosity::warning, "Empty command argument key: {}.", qx::to_string(svArgument));
233  continue;
234  }
235 
236  if (svValue.empty())
237  {
238  QX_LOG(qx::verbosity::warning, "Empty command argument value: {}.", qx::to_string(svArgument));
239  continue;
240  }
241 
242  for (const auto& [svRuntimeName, data] : m_VariableData)
243  {
244  const bool bNameMatches =
245  bShortKey && svKey == data.svShortCommandLineName || !bShortKey && svKey == data.svFullCommandLineName;
246  if (!bNameMatches)
247  continue;
248 
249  std::optional<std::any> optValue = data.pStringToT(svValue);
250  if (!optValue)
251  continue;
252 
253  variables[svRuntimeName] = std::move(*optValue);
254  break;
255  }
256  }
257 
258  bool bAllRequiredVariablesPresent = true;
259  for (const auto& [svRuntimeName, data] : m_VariableData)
260  {
261  if (data.bRequired && !variables.contains(svRuntimeName))
262  {
263  QX_LOG(qx::verbosity::error, "Required variable missing: {}.", qx::to_string(data.svFullCommandLineName));
264  bAllRequiredVariablesPresent = false;
265  }
266  }
267 
268  // override default and previous values
269  for (const auto& [svRuntimeName, value] : variables)
270  m_Variables[svRuntimeName] = value;
271 
272  return bAllRequiredVariablesPresent;
273 }
274 
275 template<class T>
276 inline std::optional<T> layered_configs_manager::get(cstring_view svRuntimeName) const noexcept
277 {
278  auto it = m_Variables.find(svRuntimeName);
279  if (it == m_Variables.end())
280  {
281  it = m_DefaultVariables.find(svRuntimeName);
282  if (it == m_DefaultVariables.end())
283  return std::nullopt;
284  }
285 
286  const auto* pValue = std::any_cast<typename details::layered_config_variable_type<T>::type>(&it->second);
287  if (!pValue)
288  return std::nullopt;
289 
290  return *pValue;
291 }
292 
293 template<class T>
294 inline bool layered_configs_manager::set(cstring_view svRuntimeName, T value) noexcept
295 {
296  // set a value only if types match
297  const auto it = m_DefaultVariables.find(svRuntimeName);
298  if (it == m_DefaultVariables.end())
299  return false;
300 
301  if (!std::any_cast<typename details::layered_config_variable_type<T>::type>(&it->second))
302  return false;
303 
304  m_Variables[svRuntimeName] = typename details::layered_config_variable_type<T>::type(std::move(value));
305  return true;
306 }
307 
308 inline void layered_configs_manager::reset() noexcept
309 {
310  m_Variables.clear();
311 }
312 
313 template<class T>
314 void layered_configs_manager::add_variable(
315  cstring_view svRuntimeName,
317  T defaultValue) noexcept
318 {
319  m_VariableData[svRuntimeName] = data;
320  m_DefaultVariables[svRuntimeName] = typename details::layered_config_variable_type<T>::type(defaultValue);
321 }
322 
323 } // namespace qx
requires format_acceptable_args_c< char_t, args_t... > void append_format(const format_string_type< std::type_identity_t< args_t >... > sFormat, args_t &&... args) noexcept
Append the formatted string to the current one.
Definition: string.inl:146
const_pointer c_str() const noexcept
Get pointer to string zero terminated.
Definition: string.inl:273
void reset() noexcept
Clear all variables and command line arguments. You should call parse() after that to set variables a...
std::optional< T > get(cstring_view svRuntimeName) const noexcept
Get variable value by runtime name.
void set_app_description(cstring sAppDescription) noexcept
Set app description. It will be shown in help message.
bool parse() noexcept
Parse command line arguments, environment variables, and default values.
bool set(cstring_view svRuntimeName, T value) noexcept
Set variable value by runtime name. Only set if the variable type matches.
bool show_help() const noexcept
Show help message if "--help" or "-h" argument is present in command line arguments.
void set_args(int argc, char *argv[]) noexcept
Set command line arguments.
bool contains_if(fwd_it_t itBegin, fwd_it_t itEnd, const predicate_t &predicate)
Check if at least one of range's elements satisfies a predicate.
Definition: contains.h:55
#define QX_LOG(eVerbosity,...)
Log message.
Definition: logger.h:38
void sort(random_it_t begin, random_it_t end, compare_t compare=compare_t())
Sort by the most suitable algorithm.
Definition: sort.inl:522
void to_string(string &out, cstring_view stringView, const std::locale &locale=std::locale())
Convert a char string to common string type.