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 #include "ConfigParser.hpp"
22 #include "../utils/iniparser/iniparser.hpp"
23
24 #include <algorithm>
25 #include <fstream>
26
27 namespace libdnf {
28
substitute(std::string & text,const std::map<std::string,std::string> & substitutions)29 void ConfigParser::substitute(std::string & text,
30 const std::map<std::string, std::string> & substitutions)
31 {
32 auto start = text.find_first_of("$");
33 while (start != text.npos)
34 {
35 auto variable = start + 1;
36 if (variable >= text.length())
37 break;
38 bool bracket;
39 if (text[variable] == '{') {
40 bracket = true;
41 if (++variable >= text.length())
42 break;
43 } else
44 bracket = false;
45 auto it = std::find_if_not(text.begin()+variable, text.end(),
46 [](char c){return std::isalnum(c) || c=='_';});
47 if (bracket && it == text.end())
48 break;
49 auto pastVariable = std::distance(text.begin(), it);
50 if (bracket && *it != '}') {
51 start = text.find_first_of("$", pastVariable);
52 continue;
53 }
54 auto subst = substitutions.find(text.substr(variable, pastVariable - variable));
55 if (subst != substitutions.end()) {
56 if (bracket)
57 ++pastVariable;
58 text.replace(start, pastVariable - start, subst->second);
59 start = text.find_first_of("$", start + subst->second.length());
60 } else {
61 start = text.find_first_of("$", pastVariable);
62 }
63 }
64 }
65
read(ConfigParser & cfgParser,IniParser & parser)66 static void read(ConfigParser & cfgParser, IniParser & parser)
67 {
68 IniParser::ItemType readedType;
69 while ((readedType = parser.next()) != IniParser::ItemType::END_OF_INPUT) {
70 auto section = parser.getSection();
71 if (readedType == IniParser::ItemType::SECTION) {
72 cfgParser.addSection(std::move(section), std::move(parser.getRawItem()));
73 }
74 else if (readedType == IniParser::ItemType::KEY_VAL) {
75 cfgParser.setValue(section, std::move(parser.getKey()), std::move(parser.getValue()), std::move(parser.getRawItem()));
76 }
77 else if (readedType == IniParser::ItemType::COMMENT_LINE || readedType == IniParser::ItemType::EMPTY_LINE) {
78 if (section.empty())
79 cfgParser.getHeader() += parser.getRawItem();
80 else
81 cfgParser.addCommentLine(section, std::move(parser.getRawItem()));
82 }
83 }
84 }
85
read(const std::string & filePath)86 void ConfigParser::read(const std::string & filePath)
87 {
88 try {
89 IniParser parser(filePath);
90 ::libdnf::read(*this, parser);
91 } catch (const IniParser::CantOpenFile & e) {
92 throw CantOpenFile(e.what());
93 } catch (const IniParser::Exception & e) {
94 throw ParsingError(e.what() + std::string(" at line ") + std::to_string(e.getLineNumber()));
95 }
96 }
97
read(std::unique_ptr<std::istream> && inputStream)98 void ConfigParser::read(std::unique_ptr<std::istream> && inputStream)
99 {
100 try {
101 IniParser parser(std::move(inputStream));
102 ::libdnf::read(*this, parser);
103 } catch (const IniParser::CantOpenFile & e) {
104 throw CantOpenFile(e.what());
105 } catch (const IniParser::Exception & e) {
106 throw ParsingError(e.what() + std::string(" at line ") + std::to_string(e.getLineNumber()));
107 }
108 }
109
createRawItem(const std::string & value,const std::string & oldRawItem)110 static std::string createRawItem(const std::string & value, const std::string & oldRawItem)
111 {
112 auto eqlPos = oldRawItem.find('=');
113 if (eqlPos == oldRawItem.npos)
114 return "";
115 auto valuepos = oldRawItem.find_first_not_of(" \t", eqlPos + 1);
116 auto keyAndDelimLength = valuepos != oldRawItem.npos ? valuepos : oldRawItem.length();
117 return oldRawItem.substr(0, keyAndDelimLength) + value + '\n';
118 }
119
setValue(const std::string & section,const std::string & key,const std::string & value)120 void ConfigParser::setValue(const std::string & section, const std::string & key, const std::string & value)
121 {
122 auto rawIter = rawItems.find(section + ']' + key);
123 auto raw = createRawItem(value, rawIter != rawItems.end() ? rawIter->second : "");
124 setValue(section, key, value, raw);
125 }
126
setValue(const std::string & section,std::string && key,std::string && value)127 void ConfigParser::setValue(const std::string & section, std::string && key, std::string && value)
128 {
129 auto rawIter = rawItems.find(section + ']' + key);
130 auto raw = createRawItem(value, rawIter != rawItems.end() ? rawIter->second : "");
131 setValue(section, std::move(key), std::move(value), std::move(raw));
132 }
133
134 const std::string &
getValue(const std::string & section,const std::string & key) const135 ConfigParser::getValue(const std::string & section, const std::string & key) const
136 {
137 auto sect = data.find(section);
138 if (sect == data.end())
139 throw MissingSection("OptionReader::getValue(): Missing section " + section);
140 auto keyVal = sect->second.find(key);
141 if (keyVal == sect->second.end())
142 throw MissingOption("OptionReader::getValue(): Missing option " + key +
143 " in section " + section);
144 return keyVal->second;
145 }
146
147 std::string
getSubstitutedValue(const std::string & section,const std::string & key) const148 ConfigParser::getSubstitutedValue(const std::string & section, const std::string & key) const
149 {
150 auto ret = getValue(section, key);
151 substitute(ret, substitutions);
152 return ret;
153 }
154
writeKeyVals(std::ostream & out,const std::string & section,const ConfigParser::Container::mapped_type & keyValMap,const std::map<std::string,std::string> & rawItems)155 static void writeKeyVals(std::ostream & out, const std::string & section, const ConfigParser::Container::mapped_type & keyValMap, const std::map<std::string, std::string> & rawItems)
156 {
157 for (const auto & keyVal : keyValMap) {
158 auto first = keyVal.first[0];
159 if (first == '#' || first == ';')
160 out << keyVal.second;
161 else {
162 auto rawItem = rawItems.find(section + ']' + keyVal.first);
163 if (rawItem != rawItems.end())
164 out << rawItem->second;
165 else {
166 out << keyVal.first << "=";
167 for (const auto chr : keyVal.second) {
168 out << chr;
169 if (chr == '\n')
170 out << " ";
171 }
172 out << "\n";
173 }
174 }
175 }
176 }
177
writeSection(std::ostream & out,const std::string & section,const ConfigParser::Container::mapped_type & keyValMap,const std::map<std::string,std::string> & rawItems)178 static void writeSection(std::ostream & out, const std::string & section, const ConfigParser::Container::mapped_type & keyValMap, const std::map<std::string, std::string> & rawItems)
179 {
180 auto rawItem = rawItems.find(section);
181 if (rawItem != rawItems.end())
182 out << rawItem->second;
183 else
184 out << "[" << section << "]" << "\n";
185 writeKeyVals(out, section, keyValMap, rawItems);
186 }
187
write(const std::string & filePath,bool append) const188 void ConfigParser::write(const std::string & filePath, bool append) const
189 {
190 std::ofstream ofs;
191 ofs.exceptions(std::ofstream::failbit | std::ofstream::badbit);
192 ofs.open(filePath, append ? std::ofstream::app : std::ofstream::trunc);
193 write(ofs);
194 }
195
write(const std::string & filePath,bool append,const std::string & section) const196 void ConfigParser::write(const std::string & filePath, bool append, const std::string & section) const
197 {
198 auto sit = data.find(section);
199 if (sit == data.end())
200 throw MissingSection("ConfigParser::write(): Missing section " + section);
201 std::ofstream ofs;
202 ofs.exceptions(std::ofstream::failbit | std::ofstream::badbit);
203 ofs.open(filePath, append ? std::ofstream::app : std::ofstream::trunc);
204 writeSection(ofs, sit->first, sit->second, rawItems);
205 }
206
write(std::ostream & outputStream) const207 void ConfigParser::write(std::ostream & outputStream) const
208 {
209 outputStream << header;
210 for (const auto & section : data) {
211 writeSection(outputStream, section.first, section.second, rawItems);
212 }
213 }
214
write(std::ostream & outputStream,const std::string & section) const215 void ConfigParser::write(std::ostream & outputStream, const std::string & section) const
216 {
217 auto sit = data.find(section);
218 if (sit == data.end())
219 throw MissingSection("ConfigParser::write(): Missing section " + section);
220 writeSection(outputStream, sit->first, sit->second, rawItems);
221 }
222
223 }
224