1 #pragma once
2 
3 #include <unordered_map>
4 
5 #include "common.hpp"
6 #include "components/logger.hpp"
7 #include "errors.hpp"
8 #include "settings.hpp"
9 #include "utils/env.hpp"
10 #include "utils/file.hpp"
11 #include "utils/string.hpp"
12 #if WITH_XRM
13 #include "x11/xresources.hpp"
14 #endif
15 
16 POLYBAR_NS
17 
18 DEFINE_ERROR(value_error);
19 DEFINE_ERROR(key_error);
20 
21 using valuemap_t = std::unordered_map<string, string>;
22 using sectionmap_t = std::map<string, valuemap_t>;
23 using file_list = vector<string>;
24 
25 class config {
26  public:
27   using make_type = const config&;
28   static make_type make(string path = "", string bar = "");
29 
config(const logger & logger,string && path="",string && bar="")30   explicit config(const logger& logger, string&& path = "", string&& bar = "")
31       : m_log(logger), m_file(move(path)), m_barname(move(bar)){};
32 
33   const string& filepath() const;
34   string section() const;
35 
36   /**
37    * \brief Instruct the config to connect to the xresource manager
38    */
39   void use_xrm();
40 
41   void set_sections(sectionmap_t sections);
42 
43   void set_included(file_list included);
44 
45   void warn_deprecated(const string& section, const string& key, string replacement) const;
46 
47   /**
48    * Returns true if a given parameter exists
49    */
has(const string & section,const string & key) const50   bool has(const string& section, const string& key) const {
51     auto it = m_sections.find(section);
52     return it != m_sections.end() && it->second.find(key) != it->second.end();
53   }
54 
55   /**
56    * Set parameter value
57    */
set(const string & section,const string & key,string && value)58   void set(const string& section, const string& key, string&& value) {
59     auto it = m_sections.find(section);
60     if (it == m_sections.end()) {
61       valuemap_t values;
62       values[key] = value;
63       m_sections[section] = move(values);
64     }
65     auto it2 = it->second.find(key);
66     if ((it2 = it->second.find(key)) == it->second.end()) {
67       it2 = it->second.emplace_hint(it2, key, value);
68     } else {
69       it2->second = value;
70     }
71   }
72 
73   /**
74    * Get parameter for the current bar by name
75    */
76   template <typename T = string>
get(const string & key) const77   T get(const string& key) const {
78     return get<T>(section(), key);
79   }
80 
81   /**
82    * Get value of a variable by section and parameter name
83    */
84   template <typename T = string>
get(const string & section,const string & key) const85   T get(const string& section, const string& key) const {
86     auto it = m_sections.find(section);
87     if (it == m_sections.end()) {
88       throw key_error("Missing section \"" + section + "\"");
89     }
90     if (it->second.find(key) == it->second.end()) {
91       throw key_error("Missing parameter \"" + section + "." + key + "\"");
92     }
93     return dereference<T>(section, key, it->second.at(key), convert<T>(string{it->second.at(key)}));
94   }
95 
96   /**
97    * Get value of a variable by section and parameter name
98    * with a default value in case the parameter isn't defined
99    */
100   template <typename T = string>
get(const string & section,const string & key,const T & default_value) const101   T get(const string& section, const string& key, const T& default_value) const {
102     try {
103       string string_value{get<string>(section, key)};
104       T result{convert<T>(string{string_value})};
105       return dereference<T>(move(section), move(key), move(string_value), move(result));
106     } catch (const key_error& err) {
107       return default_value;
108     } catch (const value_error& err) {
109       m_log.err("Invalid value for \"%s.%s\", using default value (reason: %s)", section, key, err.what());
110       return default_value;
111     }
112   }
113 
114   /**
115    * Get list of values for the current bar by name
116    */
117   template <typename T = string>
get_list(const string & key) const118   vector<T> get_list(const string& key) const {
119     return get_list<T>(section(), key);
120   }
121 
122   /**
123    * Get list of values by section and parameter name
124    */
125   template <typename T = string>
get_list(const string & section,const string & key) const126   vector<T> get_list(const string& section, const string& key) const {
127     vector<T> results;
128 
129     while (true) {
130       try {
131         string string_value{get<string>(section, key + "-" + to_string(results.size()))};
132         T value{convert<T>(string{string_value})};
133 
134         if (!string_value.empty()) {
135           results.emplace_back(dereference<T>(section, key, move(string_value), move(value)));
136         } else {
137           results.emplace_back(move(value));
138         }
139       } catch (const key_error& err) {
140         break;
141       }
142     }
143 
144     if (results.empty()) {
145       throw key_error("Missing parameter \"" + section + "." + key + "-0\"");
146     }
147 
148     return results;
149   }
150 
151   /**
152    * Get list of values by section and parameter name
153    * with a default list in case the list isn't defined
154    */
155   template <typename T = string>
get_list(const string & section,const string & key,const vector<T> & default_value) const156   vector<T> get_list(const string& section, const string& key, const vector<T>& default_value) const {
157     vector<T> results;
158 
159     while (true) {
160       try {
161         string string_value{get<string>(section, key + "-" + to_string(results.size()))};
162         T value{convert<T>(string{string_value})};
163 
164         if (!string_value.empty()) {
165           results.emplace_back(dereference<T>(section, key, move(string_value), move(value)));
166         } else {
167           results.emplace_back(move(value));
168         }
169       } catch (const key_error& err) {
170         break;
171       } catch (const value_error& err) {
172         m_log.err("Invalid value in list \"%s.%s\", using list as-is (reason: %s)", section, key, err.what());
173         return default_value;
174       }
175     }
176 
177     if (!results.empty()) {
178       return results;
179       ;
180     }
181 
182     return default_value;
183   }
184 
185   /**
186    * Attempt to load value using the deprecated key name. If successful show a
187    * warning message. If it fails load the value using the new key and given
188    * fallback value
189    */
190   template <typename T = string>
deprecated(const string & section,const string & old,const string & newkey,const T & fallback) const191   T deprecated(const string& section, const string& old, const string& newkey, const T& fallback) const {
192     try {
193       T value{get<T>(section, old)};
194       warn_deprecated(section, old, newkey);
195       return value;
196     } catch (const key_error& err) {
197       return get<T>(section, newkey, fallback);
198     }
199   }
200 
201   /**
202    * \see deprecated<T>
203    */
204   template <typename T = string>
deprecated_list(const string & section,const string & old,const string & newkey,const vector<T> & fallback) const205   T deprecated_list(const string& section, const string& old, const string& newkey, const vector<T>& fallback) const {
206     try {
207       vector<T> value{get_list<T>(section, old)};
208       warn_deprecated(section, old, newkey);
209       return value;
210     } catch (const key_error& err) {
211       return get_list<T>(section, newkey, fallback);
212     }
213   }
214 
215  protected:
216   void copy_inherited();
217 
218   template <typename T>
219   T convert(string&& value) const;
220 
221   /**
222    * Dereference value reference
223    */
224   template <typename T>
dereference(const string & section,const string & key,const string & var,const T & fallback) const225   T dereference(const string& section, const string& key, const string& var, const T& fallback) const {
226     if (var.substr(0, 2) != "${" || var.substr(var.length() - 1) != "}") {
227       return fallback;
228     }
229 
230     auto path = var.substr(2, var.length() - 3);
231     size_t pos;
232 
233     if (path.compare(0, 4, "env:") == 0) {
234       return dereference_env<T>(path.substr(4));
235     } else if (path.compare(0, 5, "xrdb:") == 0) {
236       return dereference_xrdb<T>(path.substr(5));
237     } else if (path.compare(0, 5, "file:") == 0) {
238       return dereference_file<T>(path.substr(5));
239     } else if ((pos = path.find(".")) != string::npos) {
240       return dereference_local<T>(path.substr(0, pos), path.substr(pos + 1), section);
241     } else {
242       throw value_error("Invalid reference defined at \"" + section + "." + key + "\"");
243     }
244   }
245 
246   /**
247    * Dereference local value reference defined using:
248    *  ${root.key}
249    *  ${root.key:fallback}
250    *  ${self.key}
251    *  ${self.key:fallback}
252    *  ${section.key}
253    *  ${section.key:fallback}
254    */
255   template <typename T>
dereference_local(string section,const string & key,const string & current_section) const256   T dereference_local(string section, const string& key, const string& current_section) const {
257     if (section == "BAR") {
258       m_log.warn("${BAR.key} is deprecated. Use ${root.key} instead");
259     }
260 
261     section = string_util::replace(section, "BAR", this->section(), 0, 3);
262     section = string_util::replace(section, "root", this->section(), 0, 4);
263     section = string_util::replace(section, "self", current_section, 0, 4);
264 
265     try {
266       string string_value{get<string>(section, key)};
267       T result{convert<T>(string{string_value})};
268       return dereference<T>(string(section), move(key), move(string_value), move(result));
269     } catch (const key_error& err) {
270       size_t pos;
271       if ((pos = key.find(':')) != string::npos) {
272         string fallback = key.substr(pos + 1);
273         m_log.info("The reference ${%s.%s} does not exist, using defined fallback value \"%s\"", section,
274             key.substr(0, pos), fallback);
275         return convert<T>(move(fallback));
276       }
277       throw value_error("The reference ${" + section + "." + key + "} does not exist (no fallback set)");
278     }
279   }
280 
281   /**
282    * Dereference environment variable reference defined using:
283    *  ${env:key}
284    *  ${env:key:fallback value}
285    */
286   template <typename T>
dereference_env(string var) const287   T dereference_env(string var) const {
288     size_t pos;
289     string env_default;
290     /*
291      * This is needed because with only the string we cannot distinguish
292      * between an empty string as default and not default
293      */
294     bool has_default = false;
295 
296     if ((pos = var.find(':')) != string::npos) {
297       env_default = var.substr(pos + 1);
298       has_default = true;
299       var.erase(pos);
300     }
301 
302     if (env_util::has(var)) {
303       string env_value{env_util::get(var)};
304       m_log.info("Environment var reference ${%s} found (value=%s)", var, env_value);
305       return convert<T>(move(env_value));
306     } else if (has_default) {
307       m_log.info("Environment var ${%s} is undefined, using defined fallback value \"%s\"", var, env_default);
308       return convert<T>(move(env_default));
309     } else {
310       throw value_error(sstream() << "Environment var ${" << var << "} does not exist (no fallback set)");
311     }
312   }
313 
314   /**
315    * Dereference X resource db value defined using:
316    *  ${xrdb:key}
317    *  ${xrdb:key:fallback value}
318    */
319   template <typename T>
dereference_xrdb(string var) const320   T dereference_xrdb(string var) const {
321     size_t pos;
322 #if not WITH_XRM
323     m_log.warn("No built-in support to dereference ${xrdb:%s} references (requires `xcb-util-xrm`)", var);
324     if ((pos = var.find(':')) != string::npos) {
325       return convert<T>(var.substr(pos + 1));
326     }
327     return convert<T>("");
328 #else
329     if (!m_xrm) {
330       throw application_error("xrm is not initialized");
331     }
332 
333     string fallback;
334     bool has_fallback = false;
335     if ((pos = var.find(':')) != string::npos) {
336       fallback = var.substr(pos + 1);
337       has_fallback = true;
338       var.erase(pos);
339     }
340 
341     try {
342       auto value = m_xrm->require<string>(var.c_str());
343       m_log.info("Found matching X resource \"%s\" (value=%s)", var, value);
344       return convert<T>(move(value));
345     } catch (const xresource_error& err) {
346       if (has_fallback) {
347         m_log.info("%s, using defined fallback value \"%s\"", err.what(), fallback);
348         return convert<T>(move(fallback));
349       }
350       throw value_error(sstream() << err.what() << " (no fallback set)");
351     }
352 #endif
353   }
354 
355   /**
356    * Dereference file reference by reading its contents
357    *  ${file:/absolute/file/path}
358    *  ${file:/absolute/file/path:fallback value}
359    */
360   template <typename T>
dereference_file(string var) const361   T dereference_file(string var) const {
362     size_t pos;
363     string fallback;
364     bool has_fallback = false;
365     if ((pos = var.find(':')) != string::npos) {
366       fallback = var.substr(pos + 1);
367       has_fallback = true;
368       var.erase(pos);
369     }
370     var = file_util::expand(var);
371 
372     if (file_util::exists(var)) {
373       m_log.info("File reference \"%s\" found", var);
374       return convert<T>(string_util::trim(file_util::contents(var), '\n'));
375     } else if (has_fallback) {
376       m_log.info("File reference \"%s\" not found, using defined fallback value \"%s\"", var, fallback);
377       return convert<T>(move(fallback));
378     } else {
379       throw value_error(sstream() << "The file \"" << var << "\" does not exist (no fallback set)");
380     }
381   }
382 
383  private:
384   const logger& m_log;
385   string m_file;
386   string m_barname;
387   sectionmap_t m_sections{};
388 
389   /**
390    * Absolute path of all files that were parsed in the process of parsing the
391    * config (Path of the main config file also included)
392    */
393   file_list m_included;
394 #if WITH_XRM
395   unique_ptr<xresource_manager> m_xrm;
396 #endif
397 };
398 
399 POLYBAR_NS_END
400