1 #pragma once
2 
3 #include <set>
4 
5 #include "common.hpp"
6 #include "components/config.hpp"
7 #include "components/logger.hpp"
8 #include "errors.hpp"
9 
10 POLYBAR_NS
11 
12 DEFINE_ERROR(parser_error);
13 
14 /**
15  * \brief Exception object for syntax errors
16  *
17  * Contains filepath and line number where syntax error was found
18  */
19 class syntax_error : public parser_error {
20  public:
21   /**
22    * Default values are used when the thrower doesn't know the position.
23    * parse_line has to catch, set the proper values and rethrow
24    */
syntax_error(string msg,const string & file="",int line_no=-1)25   explicit syntax_error(string msg, const string& file = "", int line_no = -1)
26       : parser_error(file + ":" + to_string(line_no) + ": " + msg), msg(move(msg)) {}
27 
get_msg()28   const string& get_msg() {
29     return msg;
30   };
31 
32  private:
33   string msg;
34 };
35 
36 class invalid_name_error : public syntax_error {
37  public:
38   /**
39    * type is either Header or Key
40    */
invalid_name_error(const string & type,const string & name)41   invalid_name_error(const string& type, const string& name)
42       : syntax_error(type + " name '" + name + "' is empty or contains forbidden characters.") {}
43 };
44 
45 /**
46  * \brief All different types a line in a config can be
47  */
48 enum class line_type { KEY, HEADER, COMMENT, EMPTY, UNKNOWN };
49 
50 /**
51  * \brief Storage for a single config line
52  *
53  * More sanitized than the actual string of the comment line, with information
54  * about line type and structure
55  */
56 struct line_t {
57   /**
58    * Whether or not this struct represents a "useful" line, a line that has
59    * any semantic significance (key-value or header line)
60    * If false all other fields are not set.
61    * Set this to false, if you want to return a line that has no effect
62    * (for example when you parse a comment line)
63    */
64   bool useful;
65 
66   /**
67    * Index of the config_parser::files vector where this line is from
68    */
69   int file_index;
70   int line_no;
71 
72   /**
73    * We access header, if is_header == true otherwise we access key, value
74    */
75   bool is_header;
76 
77   /**
78    * Only set for header lines
79    */
80   string header;
81 
82   /**
83    * Only set for key-value lines
84    */
85   string key, value;
86 };
87 
88 class config_parser {
89  public:
90   config_parser(const logger& logger, string&& file, string&& bar);
91 
92   /**
93    * \brief Performs the parsing of the main config file m_file
94    *
95    * \returns config class instance populated with the parsed config
96    *
97    * \throws syntax_error If there was any kind of syntax error
98    * \throws parser_error If aynthing else went wrong
99    */
100   config::make_type parse();
101 
102  protected:
103   /**
104    * \brief Converts the `lines` vector to a proper sectionmap
105    */
106   sectionmap_t create_sectionmap();
107 
108   /**
109    * \brief Parses the given file, extracts key-value pairs and section
110    *        headers and adds them onto the `lines` vector
111    *
112    * This method directly resolves `include-file` directives and checks for
113    * cyclic dependencies
114    *
115    * `file` is expected to be an already resolved absolute path
116    */
117   void parse_file(const string& file, file_list path);
118 
119   /**
120    * \brief Parses the given line string to create a line_t struct
121    *
122    * We use the INI file syntax (https://en.wikipedia.org/wiki/INI_file)
123    * Whitespaces (tested with isspace()) at the beginning and end of a line are ignored
124    * Keys and section names can contain any character except for the following:
125    * - spaces
126    * - equal sign (=)
127    * - semicolon (;)
128    * - pound sign (#)
129    * - Any kind of parentheses ([](){})
130    * - colon (:)
131    * - period (.)
132    * - dollar sign ($)
133    * - backslash (\)
134    * - percent sign (%)
135    * - single and double quotes ('")
136    * So basically any character that has any kind of special meaning is prohibited.
137    *
138    * Comment lines have to start with a semicolon (;) or a pound sign (#),
139    * you cannot put a comment after another type of line.
140    *
141    * key and section names are case-sensitive.
142    *
143    * Keys are specified as `key = value`, spaces around the equal sign, as
144    * well as double quotes around the value are ignored
145    *
146    * sections are defined as [section], everything inside the square brackets is part of the name
147    *
148    * \throws syntax_error if the line isn't well formed. The syntax error
149    *         does not contain the filename or line numbers because parse_line
150    *         doesn't know about those. Whoever calls parse_line needs to
151    *         catch those exceptions and set the file path and line number
152    */
153   line_t parse_line(const string& line);
154 
155   /**
156    * \brief Determines the type of a line read from a config file
157    *
158    * Expects that line is trimmed
159    * This mainly looks at the first character and doesn't check if the line is
160    * actually syntactically correct.
161    * HEADER ('['), COMMENT (';' or '#') and EMPTY (None) are uniquely
162    * identified by their first character (or lack thereof). Any line that
163    * is none of the above and contains an equal sign, is treated as KEY.
164    * All others are UNKNOWN
165    */
166   static line_type get_line_type(const string& line);
167 
168   /**
169    * \brief Parse a line containing a section header and returns the header name
170    *
171    * Only assumes that the line starts with '[' and is trimmed
172    *
173    * \throws syntax_error if the line doesn't end with ']' or the header name
174    *         contains forbidden characters
175    */
176   string parse_header(const string& line);
177 
178   /**
179    * \brief Parses a line containing a key-value pair and returns the key name
180    *        and the value string inside an std::pair
181    *
182    * Only assumes that the line contains '=' at least once and is trimmed
183    *
184    * \throws syntax_error if the key contains forbidden characters
185    */
186   std::pair<string, string> parse_key(const string& line);
187 
188   /**
189    * \brief Name of all the files the config includes values from
190    *
191    * The line_t struct uses indices to this vector to map lines to their
192    * original files. This allows us to point the user to the exact location
193    * of errors
194    */
195   file_list m_files;
196 
197  private:
198   /**
199    * \brief Checks if the given name doesn't contain any spaces or characters
200    *        in config_parser::m_forbidden_chars
201    */
202   bool is_valid_name(const string& name);
203 
204   /**
205    * \brief Whether or not an xresource manager should be used
206    *
207    * Is set to true if any ${xrdb...} references are found
208    */
209   bool use_xrm{false};
210 
211   const logger& m_log;
212 
213   /**
214    * \brief Absolute path to the main config file
215    */
216   string m_config;
217 
218   /**
219    * Is used to resolve ${root...} references
220    */
221   string m_barname;
222 
223   /**
224    * \brief List of all the lines in the config (with included files)
225    *
226    * The order here matters, as we have not yet associated key-value pairs
227    * with sections
228    */
229   vector<line_t> m_lines;
230 
231   /**
232    * \brief None of these characters can be used in the key and section names
233    */
234   const string m_forbidden_chars{"\"'=;#[](){}:.$\\%"};
235 
236   /**
237    * \brief List of names that cannot be used as section names
238    *
239    * These strings have a special meaning inside references and so the
240    * section [self] could never be referenced.
241    *
242    * Note: BAR is deprecated
243    */
244   const std::set<string> m_reserved_section_names = {"self", "BAR", "root"};
245 };
246 
247 POLYBAR_NS_END
248