1 #pragma once
2 
3 #include <wayfire/config/types.hpp>
4 #include <wayfire/config/option.hpp>
5 #include <wayfire/util/log.hpp>
6 #include <vector>
7 #include <map>
8 #include <cassert>
9 
10 namespace wf
11 {
12 namespace config
13 {
14 template<class... Args>
15 using compound_list_t =
16     std::vector<std::tuple<std::string, Args...>>;
17 
18 /**
19  * A base class containing information about an entry in a tuple.
20  */
21 class compound_option_entry_base_t
22 {
23   public:
24 
25     virtual ~compound_option_entry_base_t() = default;
26 
27     /** @return The prefix of the tuple entry. */
get_prefix() const28     virtual std::string get_prefix() const
29     {
30         return prefix;
31     }
32 
33     /**
34      * Try to parse the given value.
35      *
36      * @param update Whether to override the stored value.
37      */
38     virtual bool is_parsable(const std::string&) const = 0;
39 
40     /** Clone this entry */
41     virtual compound_option_entry_base_t *clone() const = 0;
42 
43   protected:
44     compound_option_entry_base_t() = default;
45     std::string prefix;
46 };
47 
48 template<class Type>
49 class compound_option_entry_t : public compound_option_entry_base_t
50 {
51   public:
compound_option_entry_t(const std::string & prefix)52     compound_option_entry_t(const std::string& prefix)
53     {
54         this->prefix = prefix;
55     }
56 
clone() const57     compound_option_entry_base_t *clone() const override
58     {
59         return new compound_option_entry_t<Type>(this->get_prefix());
60     }
61 
62     /**
63      * Try to parse the given value.
64      *
65      * @param update Whether to override the stored value.
66      */
is_parsable(const std::string & str) const67     bool is_parsable(const std::string& str) const override
68     {
69         return option_type::from_string<Type>(str).has_value();
70     }
71 };
72 
73 /**
74  * Compound options are a special class of options which can hold multiple
75  * string-tagged tuples. They are constructed from multiple untyped options
76  * in the config file.
77  */
78 
79 class compound_option_t : public option_base_t
80 {
81   public:
82     using entries_t = std::vector<std::unique_ptr<compound_option_entry_base_t>>;
83     /**
84      * Construct a new compound option, with the types given in the template
85      * arguments.
86      *
87      * @param name The name of the option.
88      * @param prefixes The prefixes used for grouping in the config file.
89      *   Example: Consider a compound option with type <int, double> and two
90      *   prefixes {"prefix1_", "prefix2_"}. In the config file, the options are:
91      *
92      *   prefix1_key1 = v11
93      *   prefix2_key1 = v21
94      *   prefix1_key2 = v12
95      *   prefix2_key2 = v22
96      *
97      *   Options are grouped by suffixes (key1 and key2), and the tuples then
98      *   are formed by taking the values of the options with each prefix.
99      *   So, the tuples contained in the compound option in the end are:
100      *
101      *   (key1, v11, v21)
102      *   (key2, v12, v22)
103      */
104     compound_option_t(const std::string& name, entries_t&& entries);
105 
106     /**
107      * Parse the compound option with the given types.
108      *
109      * Throws an exception in case of wrong template types.
110      */
111     template<class... Args>
get_value() const112     compound_list_t<Args...> get_value() const
113     {
114         compound_list_t<Args...> result;
115         result.resize(value.size());
116         build_recursive<0, Args...>(result);
117         return result;
118     }
119 
120     /**
121      * Set the value of the option.
122      *
123      * Throws an exception in case of wrong template types.
124      */
125     template<class... Args>
set_value(const compound_list_t<Args...> & value)126     void set_value(const compound_list_t<Args...>& value)
127     {
128         assert(sizeof...(Args) == this->entries.size());
129         this->value.assign(value.size(), {});
130         push_recursive<0>(value);
131         notify_updated();
132     }
133 
134     using stored_type_t = std::vector<std::vector<std::string>>;
135     /**
136      * Get the string data stored in the compound option.
137      */
138     stored_type_t get_value_untyped();
139 
140     /**
141      * Set the data contained in the option, from a vector containing
142      * strings which describe the individual elements.
143      *
144      * @return True if the operation was successful.
145      */
146     bool set_value_untyped(stored_type_t value);
147 
148     /**
149      * Get the type information about entries in the option.
150      */
151     const entries_t& get_entries() const;
152 
153   private:
154     /**
155      * Current value stored in the option.
156      * The first element is the name of the tuple, followed by the string values
157      * of each element.
158      */
159     stored_type_t value;
160 
161     /** Entry types with which the option was created. */
162     entries_t entries;
163 
164     /**
165      * Set the n-th element in the result tuples by reading from the stored
166      * values in this option.
167      */
168     template<size_t n, class... Args>
build_recursive(compound_list_t<Args...> & result) const169     void build_recursive(compound_list_t<Args...>& result) const
170     {
171         for (size_t i = 0; i < result.size(); i++)
172         {
173             using type_t = typename std::tuple_element<n,
174                 std::tuple<std::string, Args...>>::type;
175 
176             std::get<n>(result[i]) = option_type::from_string<type_t>(
177                 this->value[i][n]).value();
178         }
179 
180         // Recursively build the (N+1)'th entries
181         if constexpr (n < sizeof...(Args))
182         {
183             build_recursive<n + 1>(result);
184         }
185     }
186 
187     template<size_t n, class... Args>
push_recursive(const compound_list_t<Args...> & new_value)188     void push_recursive(const compound_list_t<Args...>& new_value)
189     {
190         for (size_t i = 0; i < new_value.size(); i++)
191         {
192             using type_t = typename std::tuple_element<n,
193                 std::tuple<std::string, Args...>>::type;
194 
195             this->value[i].push_back(option_type::to_string<type_t>(
196                 std::get<n>(new_value[i])));
197         }
198 
199         // Recursively build the (N+1)'th entries
200         if constexpr (n < sizeof...(Args))
201         {
202             push_recursive<n + 1>(new_value);
203         }
204     }
205 
206   public: // Implementation of option_base_t
207     std::shared_ptr<option_base_t> clone_option() const override;
208     bool set_value_str(const std::string&) override;
209     void reset_to_default() override;
210     bool set_default_value_str(const std::string&) override;
211     std::string get_value_str() const override;
212     std::string get_default_value_str() const override;
213 };
214 }
215 }
216