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