1 /*
2 * Copyright 2009-2020 The VOTCA Development Team (http://www.votca.org)
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18 // Local VOTCA includes
19 #include "votca/tools/optionshandler.h"
20 #include "votca/tools/propertyiomanipulator.h"
21 #include "votca/tools/tokenizer.h"
22 #include <algorithm>
23 #include <stdexcept>
24 #include <string>
25
26 namespace votca {
27 namespace tools {
28
29 template <typename T>
IsValidCast(const tools::Property & prop)30 static bool IsValidCast(const tools::Property &prop) {
31 try {
32 prop.as<T>();
33 return true;
34 } catch (const std::runtime_error &e) {
35 return false;
36 }
37 }
38
ResolveLinks(Property & prop) const39 void OptionsHandler::ResolveLinks(Property &prop) const {
40
41 if (prop.hasAttribute("link")) {
42 Tokenizer tok(prop.getAttribute<std::string>("link"), " ,");
43 for (std::string path : tok) {
44 std::string relative_path = "subpackages/" + path;
45 std::string file_path = defaults_path_ + relative_path;
46 tools::Property package;
47 package.LoadFromXML(file_path);
48 const tools::Property &options = *(package.begin());
49 for (Property::const_AttributeIterator attr = options.firstAttribute();
50 attr != options.lastAttribute(); ++attr) {
51 if (!prop.hasAttribute(attr->first)) {
52 prop.setAttribute(attr->first, attr->second);
53 }
54 }
55
56 for (const auto &child : options) {
57 prop.add(child);
58 }
59 }
60 }
61
62 for (tools::Property &child : prop) {
63 ResolveLinks(child);
64 }
65 }
66
ProcessUserInput(const Property & user_input,const std::string & calcname) const67 Property OptionsHandler::ProcessUserInput(const Property &user_input,
68 const std::string &calcname) const {
69 Property print = LoadDefaults(calcname);
70
71 CheckUserInput(user_input, print);
72
73 OverwriteDefaultsWithUserInput(user_input.get("options"),
74 print.get("options"));
75 RemoveOptional(print);
76 CheckRequired(print);
77 InjectDefaultsAsValues(print);
78 RecursivelyCheckOptions(print);
79 return print;
80 }
81
CheckUserInput(const Property & user_input,const Property & defaults) const82 void OptionsHandler::CheckUserInput(const Property &user_input,
83 const Property &defaults) const {
84 if (user_input.hasAttribute("unchecked")) {
85 return;
86 } else {
87 for (const auto &child : user_input) {
88 if (defaults.exists(child.name())) {
89 CheckUserInput(child, defaults.get(child.name()));
90 } else {
91 throw std::runtime_error("Votca has no option:" + child.path() + "." +
92 child.name());
93 }
94 }
95 }
96 }
97
CheckRequired(const Property & options) const98 void OptionsHandler::CheckRequired(const Property &options) const {
99 for (const auto &child : options) {
100 CheckRequired(child);
101 }
102 if (options.hasAttribute("default") &&
103 options.getAttribute<std::string>("default") == "REQUIRED" &&
104 !options.hasAttribute("injected")) {
105 throw std::runtime_error("Please specify an input for:" + options.path() +
106 "." + options.name());
107 }
108 }
109
RemoveOptional(Property & options) const110 void OptionsHandler::RemoveOptional(Property &options) const {
111 options.deleteChildren([](const Property &p) {
112 return p.hasAttribute("default") &&
113 p.getAttribute<std::string>("default") == "OPTIONAL" &&
114 !p.hasAttribute("injected");
115 });
116 for (auto &child : options) {
117 RemoveOptional(child);
118 }
119 }
120
CalculatorOptions(const std::string & calcname) const121 Property OptionsHandler::CalculatorOptions(const std::string &calcname) const {
122 Property print = LoadDefaults(calcname);
123 InjectDefaultsAsValues(print);
124 CleanAttributes(print, {"link"});
125 return print;
126 }
127
CleanAttributes(Property & options,const std::vector<std::string> & attributes) const128 void OptionsHandler::CleanAttributes(
129 Property &options, const std::vector<std::string> &attributes) const {
130 for (const auto &attribute : attributes) {
131 if (options.hasAttribute(attribute)) {
132 options.deleteAttribute(attribute);
133 }
134 }
135 for (auto &child : options) {
136 CleanAttributes(child, attributes);
137 }
138 }
139
140 // load the xml description of the calculator (with defaults and test values)
LoadDefaults(const std::string & calculatorname) const141 Property OptionsHandler::LoadDefaults(const std::string &calculatorname) const {
142 Property defaults_all;
143 std::string defaults_file_path =
144 defaults_path_ + "/" + calculatorname + ".xml";
145 defaults_all.LoadFromXML(defaults_file_path);
146 ResolveLinks(defaults_all);
147 return defaults_all;
148 }
149
InjectDefaultsAsValues(Property & defaults) const150 void OptionsHandler::InjectDefaultsAsValues(Property &defaults) const {
151 for (Property &prop : defaults) {
152 if (prop.HasChildren()) {
153 InjectDefaultsAsValues(prop);
154 } else if (prop.hasAttribute("default") && !prop.hasAttribute("injected")) {
155 std::string value = prop.getAttribute<std::string>("default");
156 if (std::none_of(
157 reserved_keywords_.begin(), reserved_keywords_.end(),
158 [value](const std::string &keyword) { return value == keyword; }))
159 prop.value() = value;
160 ;
161 }
162 }
163 }
164
OverwriteDefaultsWithUserInput(const Property & user_input,Property & defaults) const165 void OptionsHandler::OverwriteDefaultsWithUserInput(const Property &user_input,
166 Property &defaults) const {
167 // There are 3 distinct cases
168 // a) normal option that can be copied over
169 // b) a list attribute is discovered
170 // c) an unchecked attriute is found,then the whole set is simply copied
171 // over from the user_options, should be done afterwards, as an unchecked
172 // session is not checked and simply copied over
173
174 if (!defaults.hasAttribute("list")) {
175 defaults.value() = user_input.value();
176 defaults.setAttribute("injected", "true");
177 for (auto &child : defaults) {
178 if (user_input.exists(child.name())) {
179 OverwriteDefaultsWithUserInput(user_input.get(child.name()), child);
180 }
181 }
182 } else {
183 defaults.setAttribute("injected", "true");
184 std::map<std::string, Index> tags;
185 for (const auto &child : defaults) {
186 tags[child.name()]++;
187 }
188
189 for (const auto &tag : tags) {
190 if (tag.second > 1) {
191 throw std::runtime_error(
192 "Developers: Each distinct tag in list should only appear once.");
193 }
194 std::vector<const tools::Property *> inputs =
195 user_input.Select(tag.first);
196
197 // if the input has no elements with that tag, remove all children from
198 // default with that tag as well
199 if (inputs.empty()) {
200 defaults.deleteChildren(
201 [&](const Property &prop) { return prop.name() == tag.first; });
202 } else {
203 // copy the element from defaults if the user_input has the element more
204 // than once
205 Property copy = defaults.get(tag.first);
206 for (Index i = 1; i < Index(inputs.size()); i++) {
207 defaults.add(copy);
208 }
209 std::vector<Property *> newdefault_elements =
210 defaults.Select(tag.first);
211 // for each element in both lists do the overwrite again
212 for (Index i = 0; i < Index(inputs.size()); i++) {
213 OverwriteDefaultsWithUserInput(*inputs[i], *newdefault_elements[i]);
214 }
215 }
216 }
217 }
218
219 if (defaults.hasAttribute("unchecked")) {
220 for (const auto &child : user_input) {
221 defaults.add(child);
222 }
223 }
224 }
225
GetPropertyChoices(const Property & p)226 std::vector<std::string> OptionsHandler::GetPropertyChoices(const Property &p) {
227 if (p.hasAttribute("choices")) {
228 std::string att = p.getAttribute<std::string>("choices");
229 std::size_t start_bracket = att.find('[');
230 if (start_bracket != std::string::npos) {
231 std::size_t end_bracket = att.find(']');
232 att = att.substr(start_bracket + 1, end_bracket - start_bracket - 1);
233 }
234 return Tokenizer{att, " ,"}.ToVector();
235 } else {
236 return {};
237 }
238 }
239
RecursivelyCheckOptions(const Property & p) const240 void OptionsHandler::RecursivelyCheckOptions(const Property &p) const {
241
242 for (const Property &prop : p) {
243 if (prop.HasChildren()) {
244 RecursivelyCheckOptions(prop);
245 } else {
246 std::vector<std::string> choices = GetPropertyChoices(prop);
247 if (choices.empty()) {
248 continue;
249 }
250 if (!IsValidOption(prop, choices)) {
251 std::ostringstream oss;
252 oss << "\nThe input value for \"" << prop.name() << "\"";
253 if (choices.size() == 1) {
254 oss << " should be a \"" << choices.front() << "\"";
255 } else {
256 oss << " should be one of the following values: ";
257 for (const std::string &c : choices) {
258 oss << "\"" << c << "\""
259 << " ";
260 }
261 }
262 oss << " But \"" << prop.value()
263 << "\" cannot be converted into one.\n";
264 throw std::runtime_error(oss.str());
265 }
266 }
267 }
268 }
269
IsValidOption(const Property & prop,const std::vector<std::string> & choices) const270 bool OptionsHandler::IsValidOption(
271 const Property &prop, const std::vector<std::string> &choices) const {
272
273 const std::string &head = choices.front();
274 std::ostringstream oss;
275 bool is_valid = true;
276 if (head == "bool") {
277 is_valid = IsValidCast<bool>(prop);
278 } else if (head == "float") {
279 is_valid = IsValidCast<double>(prop);
280 } else if (head == "float+") {
281 is_valid = IsValidCast<double>(prop) && (prop.as<double>() >= 0.0);
282 } else if (head == "int") {
283 is_valid = IsValidCast<Index>(prop);
284 } else if (head == "int+") {
285 is_valid = IsValidCast<Index>(prop) && (prop.as<Index>() >= 0);
286 } else {
287 std::string value = prop.as<std::string>();
288 std::string att = prop.getAttribute<std::string>("choices");
289 std::size_t start_bracket = att.find('[');
290 if (start_bracket == std::string::npos) {
291 // There is a single choice out of multiple default valid choices
292 auto it = std::find(std::cbegin(choices), std::cend(choices), value);
293 is_valid = (it != std::cend(choices));
294 } else {
295 // there are multiple valid choices
296 Tokenizer tok{value, " ,"};
297 for (const std::string &word : tok) {
298 auto it = std::find(std::cbegin(choices), std::cend(choices), word);
299 if (it == std::cend(choices)) {
300 is_valid = false;
301 break;
302 }
303 }
304 }
305 }
306 if (std::find(additional_choices_.begin(), additional_choices_.end(),
307 prop.as<std::string>()) != additional_choices_.end()) {
308 is_valid = true;
309 }
310 return is_valid;
311 }
312
313 } // namespace tools
314 } // namespace votca
315