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 © 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  m_Args = std::span(argv, argc);
38 }
39 
40 inline bool layered_configs_manager::show_help() const noexcept
41 {
42  if (!contains_if(
43  m_Args,
44  [](const char* const pszValue)
45  {
46  cstring_view svValue(pszValue);
47  return svValue == "--help" || svValue == "-h";
48  }))
49  {
50  return false;
51  }
52 
53  struct data_with_runtime_name : details::layered_config_variable_data
54  {
55  cstring_view svRuntimeName;
56  };
57 
58  std::map<cstring_view, std::vector<data_with_runtime_name>> groups;
59  for (const auto& [svRuntimeName, data] : m_VariableData)
60  {
61  groups[data.svGroupName].push_back(data_with_runtime_name { { data }, svRuntimeName });
62  }
63 
64  for (std::vector<data_with_runtime_name>& lines : groups | std::views::values)
65  {
66  sort(
67  lines,
68  [](const data_with_runtime_name& left, const data_with_runtime_name& right)
69  {
70  return left.bRequired > right.bRequired && left.svFullCommandLineName > right.svFullCommandLineName;
71  });
72  }
73 
74  cstring sHelp;
75  if (!groups.empty())
76  {
77  constexpr cstring_view svRequired = "required ";
78 
79  size_t nMaxFullCommandLineNameLength = 0;
80  size_t nMaxShortCommandLineNameLength = 0;
81  size_t nMaxTypeNameLength = 0;
82  size_t nMaxRequiredNameLength = 0;
83  for (const std::vector<data_with_runtime_name>& lines : groups | std::views::values)
84  {
85  for (const data_with_runtime_name& line : lines)
86  {
87  nMaxFullCommandLineNameLength =
88  std::max(nMaxFullCommandLineNameLength, line.svFullCommandLineName.size());
89  nMaxShortCommandLineNameLength =
90  std::max(nMaxShortCommandLineNameLength, line.svShortCommandLineName.size());
91  nMaxTypeNameLength = std::max(nMaxTypeNameLength, line.svTypeName.size());
92  nMaxRequiredNameLength = std::max(
93  nMaxRequiredNameLength,
94  line.bRequired ? svRequired.size() : line.pTToString(m_Variables.at(line.svRuntimeName)).size());
95  }
96  }
97 
98  sHelp.append_format("\n\nUsage: {} [options]\n", m_Args[0]);
99  sHelp += "Using formats: [--key], [-k], [--key=value], [-k=value], [--key value], [-k value]\n\n";
100 
101  if (!m_sAppDescription.empty())
102  sHelp.append_format("{}\n\n", m_sAppDescription);
103 
104  for (const auto& [svGroupName, lines] : groups)
105  {
106  if (!svGroupName.empty())
107  sHelp.append_format("{}:\n", svGroupName);
108  else
109  sHelp += "General options:\n";
110 
111  sHelp +=
112  "------------------------------------------------------------------------------------------------------"
113  "------------------\n";
114 
115  for (const data_with_runtime_name& line : lines)
116  {
117  sHelp.append_format(
118  "{:<{}} {:<{}} {:<{}} {:<{}}{}\n",
119  line.svFullCommandLineName,
120  nMaxFullCommandLineNameLength,
121  line.svShortCommandLineName,
122  nMaxShortCommandLineNameLength,
123  line.svTypeName,
124  nMaxTypeNameLength,
125  line.bRequired ? svRequired
126  : cstring_view(line.pTToString(m_Variables.at(line.svRuntimeName)) + " "),
127  nMaxRequiredNameLength,
128  line.svDescription);
129  }
130 
131  sHelp += "\n";
132  }
133  }
134  else
135  {
136  sHelp += "This app doesn't have any command line arguments.\n\n";
137  if (!m_sAppDescription.empty())
138  sHelp.append_format("{}\n\n", m_sAppDescription);
139  }
140 
141  QX_LOG(qx::verbosity::important, qx::to_string(sHelp));
142 
143  return true;
144 }
145 
146 inline bool layered_configs_manager::parse() noexcept
147 {
148  std::unordered_map<cstring_view, std::any> variables;
149 
150  // read default variables
151  for (const auto& [svRuntimeName, value] : m_DefaultVariables)
152  if (!m_Variables.contains(svRuntimeName))
153  variables[svRuntimeName] = value;
154 
155  // read envs
156  for (const auto& [svRuntimeName, data] : m_VariableData)
157  {
158  if (const char* pszEnvValue = std::getenv(cstring(data.svEnvName).c_str()))
159  {
160  std::optional<std::any> optValue = data.pStringToT(pszEnvValue);
161  if (!optValue)
162  continue;
163 
164  variables[svRuntimeName] = std::move(*optValue);
165  }
166  }
167 
168  // read command lines args
169  for (size_t i = 1; i < m_Args.size(); ++i)
170  {
171  const cstring_view svArgument = m_Args[i];
172 
173  bool bShortKey = false;
174  cstring_view svKey;
175  cstring_view svValue;
176 
177  svKey = cstring_view(svArgument.begin(), svArgument.end());
178  if (svArgument.starts_with("--"))
179  {
180  bShortKey = false;
181  }
182  else if (svArgument.starts_with("-"))
183  {
184  bShortKey = true;
185  }
186  else
187  {
188  QX_LOG(
189  qx::verbosity::warning,
190  "Invalid command argument format: {}. "
191  "Use \"--key=value\", \"--key value\", \"-k value\", \"--key\", \"-k\" instead.",
192  qx::to_string(svArgument));
193  continue;
194  }
195 
196  // --key=value / -k=value
197  if (const size_t nEqualPos = svKey.find('='); nEqualPos != cstring_view::npos)
198  {
199  svValue = cstring_view(svKey.begin() + nEqualPos + 1, svKey.end());
200  svKey = cstring_view(svKey.begin(), svKey.begin() + nEqualPos);
201  }
202  // --key value / -k value
203  else if (i + 1 < m_Args.size())
204  {
205  const cstring_view svNextArgument = m_Args[i + 1];
206 
207  if (!svNextArgument.starts_with("-"))
208  {
209  svValue = svNextArgument;
210  ++i;
211  }
212  else
213  {
214  // --key / -k
215  svValue = "true";
216  }
217  }
218  // --key / -k
219  else
220  {
221  svValue = "true";
222  }
223 
224  if (svKey.empty())
225  {
226  QX_LOG(qx::verbosity::warning, "Empty command argument key: {}.", qx::to_string(svArgument));
227  continue;
228  }
229 
230  if (svValue.empty())
231  {
232  QX_LOG(qx::verbosity::warning, "Empty command argument value: {}.", qx::to_string(svArgument));
233  continue;
234  }
235 
236  for (const auto& [svRuntimeName, data] : m_VariableData)
237  {
238  const bool bNameMatches =
239  bShortKey && svKey == data.svShortCommandLineName || !bShortKey && svKey == data.svFullCommandLineName;
240  if (!bNameMatches)
241  continue;
242 
243  std::optional<std::any> optValue = data.pStringToT(svValue);
244  if (!optValue)
245  continue;
246 
247  variables[svRuntimeName] = std::move(*optValue);
248  break;
249  }
250  }
251 
252  bool bAllRequiredVariablesPresent = true;
253  for (const auto& [svRuntimeName, data] : m_VariableData)
254  {
255  if (data.bRequired && !variables.contains(svRuntimeName))
256  {
257  QX_LOG(qx::verbosity::error, "Required variable missing: {}.", qx::to_string(data.svFullCommandLineName));
258  bAllRequiredVariablesPresent = false;
259  }
260  }
261 
262  // override default and previous values
263  for (const auto& [svRuntimeName, value] : variables)
264  m_Variables[svRuntimeName] = value;
265 
266  return bAllRequiredVariablesPresent;
267 }
268 
269 template<class T>
270 inline std::optional<T> layered_configs_manager::get(cstring_view svRuntimeName) const noexcept
271 {
272  auto it = m_Variables.find(svRuntimeName);
273  if (it == m_Variables.end())
274  {
275  it = m_DefaultVariables.find(svRuntimeName);
276  if (it == m_DefaultVariables.end())
277  return std::nullopt;
278  }
279 
280  const auto* pValue = std::any_cast<typename details::layered_config_variable_type<T>::type>(&it->second);
281  if (!pValue)
282  return std::nullopt;
283 
284  return *pValue;
285 }
286 
287 template<class T>
288 inline bool layered_configs_manager::set(cstring_view svRuntimeName, T value) noexcept
289 {
290  // set a value only if types match
291  const auto it = m_DefaultVariables.find(svRuntimeName);
292  if (it == m_DefaultVariables.end())
293  return false;
294 
295  if (!std::any_cast<typename details::layered_config_variable_type<T>::type>(&it->second))
296  return false;
297 
298  m_Variables[svRuntimeName] = typename details::layered_config_variable_type<T>::type(std::move(value));
299  return true;
300 }
301 
302 inline void layered_configs_manager::reset() noexcept
303 {
304  m_Variables.clear();
305 }
306 
307 template<class T>
308 void layered_configs_manager::add_variable(
309  cstring_view svRuntimeName,
311  T defaultValue) noexcept
312 {
313  m_VariableData[svRuntimeName] = data;
314  m_DefaultVariables[svRuntimeName] = typename details::layered_config_variable_type<T>::type(defaultValue);
315 }
316 
317 } // 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:262
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:40
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.