1 /*
2  * Copyright (C) 2018 Red Hat, Inc.
3  *
4  * Licensed under the GNU Lesser General Public License Version 2.1
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 
21 #ifndef LIBDNF_CONFIG_PARSER_HPP
22 #define LIBDNF_CONFIG_PARSER_HPP
23 
24 #ifdef LIBDNF_UNSTABLE_API
25 
26 #include "../utils/PreserveOrderMap.hpp"
27 
28 #include <istream>
29 #include <ostream>
30 #include <map>
31 #include <memory>
32 #include <stdexcept>
33 #include <string>
34 #include <utility>
35 
36 namespace libdnf {
37 
38 /**
39 * @class ConfigParser
40 *
41 * @brief Class for parsing dnf/yum .ini configuration files.
42 *
43 * ConfigParser is used for parsing files. The class adds support for substitutions.
44 * User can get both substituded and original parsed values.
45 * The parsed items are stored into the PreserveOrderMap.
46 * IniParser preserve order of items. Comments and empty lines are kept.
47 */
48 struct ConfigParser {
49 public:
50     typedef PreserveOrderMap<std::string, PreserveOrderMap<std::string, std::string>> Container;
51 
52     struct Exception : public std::runtime_error {
Exceptionlibdnf::ConfigParser::Exception53         Exception(const std::string & what) : runtime_error(what) {}
54     };
55     struct CantOpenFile : public Exception {
CantOpenFilelibdnf::ConfigParser::CantOpenFile56         CantOpenFile(const std::string & what) : Exception(what) {}
57     };
58     struct ParsingError : public Exception {
ParsingErrorlibdnf::ConfigParser::ParsingError59         ParsingError(const std::string & what) : Exception(what) {}
60     };
61     struct MissingSection : public Exception {
MissingSectionlibdnf::ConfigParser::MissingSection62         MissingSection(const std::string & what) : Exception(what) {}
63     };
64     struct MissingOption : public Exception {
MissingOptionlibdnf::ConfigParser::MissingOption65         MissingOption(const std::string & what) : Exception(what) {}
66     };
67 
68     /**
69     * @brief Substitute values in text according to the substitutions map
70     *
71     * @param text The text for substitution
72     * @param substitutions Substitution map
73     */
74     static void substitute(std::string & text,
75         const std::map<std::string, std::string> & substitutions);
76     void setSubstitutions(const std::map<std::string, std::string> & substitutions);
77     void setSubstitutions(std::map<std::string, std::string> && substitutions);
78     const std::map<std::string, std::string> & getSubstitutions() const;
79     /**
80     * @brief Reads/parse one INI file
81     *
82     * Can be called repeately for reading/merge more INI files.
83     *
84     * @param filePath Name (with path) of file to read
85     */
86     void read(const std::string & filePath);
87     /**
88     * @brief Reads/parse from istream
89     *
90     * Can be called repeately for reading/merge more istreams.
91     *
92     * @param inputStream Stream to read
93     */
94     void read(std::unique_ptr<std::istream> && inputStream);
95     /**
96     * @brief Writes all data (all sections) to INI file
97     *
98     * @param filePath Name (with path) of file to write
99     * @param append If true, existent file will be appended, otherwise overwritten
100     */
101     void write(const std::string & filePath, bool append) const;
102     /**
103     * @brief Writes one section data to INI file
104     *
105     * @param filePath Name (with path) of file to write
106     * @param append If true, existent file will be appended, otherwise overwritten
107     * @param section Section to write
108     */
109     void write(const std::string & filePath, bool append, const std::string & section) const;
110     /**
111     * @brief Writes one section data to stream
112     *
113     * @param outputStream Stream to write
114     * @param section Section to write
115     */
116     void write(std::ostream & outputStream, const std::string & section) const;
117     /**
118     * @brief Writes all data (all sections) to stream
119     *
120     * @param outputStream Stream to write
121     */
122     void write(std::ostream & outputStream) const;
123     bool addSection(const std::string & section, const std::string & rawLine);
124     bool addSection(const std::string & section);
125     bool addSection(std::string && section, std::string && rawLine);
126     bool addSection(std::string && section);
127     bool hasSection(const std::string & section) const noexcept;
128     bool hasOption(const std::string & section, const std::string & key) const noexcept;
129     void setValue(const std::string & section, const std::string & key, const std::string & value, const std::string & rawItem);
130     void setValue(const std::string & section, const std::string & key, const std::string & value);
131     void setValue(const std::string & section, std::string && key, std::string && value, std::string && rawItem);
132     void setValue(const std::string & section, std::string && key, std::string && value);
133     bool removeSection(const std::string & section);
134     bool removeOption(const std::string & section, const std::string & key);
135     void addCommentLine(const std::string & section, const std::string & comment);
136     void addCommentLine(const std::string & section, std::string && comment);
137     const std::string & getValue(const std::string & section, const std::string & key) const;
138     std::string getSubstitutedValue(const std::string & section, const std::string & key) const;
139     const std::string & getHeader() const noexcept;
140     std::string & getHeader() noexcept;
141     const Container & getData() const noexcept;
142     Container & getData() noexcept;
143 
144 private:
145     std::map<std::string, std::string> substitutions;
146     Container data;
147     int itemNumber{0};
148     std::string header;
149     std::map<std::string, std::string> rawItems;
150 };
151 
setSubstitutions(const std::map<std::string,std::string> & substitutions)152 inline void ConfigParser::setSubstitutions(const std::map<std::string, std::string> & substitutions)
153 {
154     this->substitutions = substitutions;
155 }
156 
setSubstitutions(std::map<std::string,std::string> && substitutions)157 inline void ConfigParser::setSubstitutions(std::map<std::string, std::string> && substitutions)
158 {
159     this->substitutions = std::move(substitutions);
160 }
161 
getSubstitutions() const162 inline const std::map<std::string, std::string> & ConfigParser::getSubstitutions() const
163 {
164     return substitutions;
165 }
166 
addSection(const std::string & section,const std::string & rawLine)167 inline bool ConfigParser::addSection(const std::string & section, const std::string & rawLine)
168 {
169     if (data.find(section) != data.end())
170         return false;
171     if (!rawLine.empty())
172         rawItems[section] = rawLine;
173     data[section] = {};
174     return true;
175 }
176 
addSection(const std::string & section)177 inline bool ConfigParser::addSection(const std::string & section)
178 {
179     return addSection(section, "");
180 }
181 
addSection(std::string && section,std::string && rawLine)182 inline bool ConfigParser::addSection(std::string && section, std::string && rawLine)
183 {
184     if (data.find(section) != data.end())
185         return false;
186     if (!rawLine.empty())
187         rawItems[section] = std::move(rawLine);
188     data[std::move(section)] = {};
189     return true;
190 }
191 
addSection(std::string && section)192 inline bool ConfigParser::addSection(std::string && section)
193 {
194     return addSection(std::move(section), "");
195 }
196 
hasSection(const std::string & section) const197 inline bool ConfigParser::hasSection(const std::string & section) const noexcept
198 {
199     return data.find(section) != data.end();
200 }
201 
hasOption(const std::string & section,const std::string & key) const202 inline bool ConfigParser::hasOption(const std::string & section, const std::string & key) const noexcept
203 {
204     auto sectionIter = data.find(section);
205     return sectionIter != data.end() && sectionIter->second.find(key) != sectionIter->second.end();
206 }
207 
setValue(const std::string & section,const std::string & key,const std::string & value,const std::string & rawItem)208 inline void ConfigParser::setValue(const std::string & section, const std::string & key, const std::string & value, const std::string & rawItem)
209 {
210     auto sectionIter = data.find(section);
211     if (sectionIter == data.end())
212         throw MissingSection(section);
213     if (rawItem.empty())
214         rawItems.erase(section + ']' + key);
215     else
216         rawItems[section + ']' + key] = rawItem;
217     sectionIter->second[key] = value;
218 }
219 
setValue(const std::string & section,std::string && key,std::string && value,std::string && rawItem)220 inline void ConfigParser::setValue(const std::string & section, std::string && key, std::string && value, std::string && rawItem)
221 {
222     auto sectionIter = data.find(section);
223     if (sectionIter == data.end())
224         throw MissingSection(section);
225     if (rawItem.empty())
226         rawItems.erase(section + ']' + key);
227     else
228         rawItems[section + ']' + key] = std::move(rawItem);
229     sectionIter->second[std::move(key)] = std::move(value);
230 }
231 
removeSection(const std::string & section)232 inline bool ConfigParser::removeSection(const std::string & section)
233 {
234     auto removed = data.erase(section) > 0;
235     if (removed)
236         rawItems.erase(section);
237     return removed;
238 }
239 
removeOption(const std::string & section,const std::string & key)240 inline bool ConfigParser::removeOption(const std::string & section, const std::string & key)
241 {
242     auto sectionIter = data.find(section);
243     if (sectionIter == data.end())
244         return false;
245     auto removed = sectionIter->second.erase(key) > 0;
246     if (removed)
247         rawItems.erase(section + ']' + key);
248     return removed;
249 }
250 
addCommentLine(const std::string & section,const std::string & comment)251 inline void ConfigParser::addCommentLine(const std::string & section, const std::string & comment)
252 {
253     auto sectionIter = data.find(section);
254     if (sectionIter == data.end())
255         throw MissingSection(section);
256     sectionIter->second["#"+std::to_string(++itemNumber)] = comment;
257 }
258 
addCommentLine(const std::string & section,std::string && comment)259 inline void ConfigParser::addCommentLine(const std::string & section, std::string && comment)
260 {
261     auto sectionIter = data.find(section);
262     if (sectionIter == data.end())
263         throw MissingSection(section);
264     sectionIter->second["#"+std::to_string(++itemNumber)] = std::move(comment);
265 }
266 
getHeader() const267 inline const std::string & ConfigParser::getHeader() const noexcept
268 {
269     return header;
270 }
271 
getHeader()272 inline std::string & ConfigParser::getHeader() noexcept
273 {
274     return header;
275 }
276 
getData() const277 inline const ConfigParser::Container & ConfigParser::getData() const noexcept
278 {
279     return data;
280 }
281 
getData()282 inline ConfigParser::Container & ConfigParser::getData() noexcept
283 {
284     return data;
285 }
286 
287 }
288 
289 #endif
290 
291 #endif
292