Line data Source code
1 : /****************************************************************************/
2 : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3 : // Copyright (C) 2001-2024 German Aerospace Center (DLR) and others.
4 : // This program and the accompanying materials are made available under the
5 : // terms of the Eclipse Public License 2.0 which is available at
6 : // https://www.eclipse.org/legal/epl-2.0/
7 : // This Source Code may also be made available under the following Secondary
8 : // Licenses when the conditions for such availability set forth in the Eclipse
9 : // Public License 2.0 are satisfied: GNU General Public License, version 2
10 : // or later which is available at
11 : // https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12 : // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13 : /****************************************************************************/
14 : /// @file OptionsCont.cpp
15 : /// @author Daniel Krajzewicz
16 : /// @author Jakob Erdmann
17 : /// @author Michael Behrisch
18 : /// @author Walter Bamberger
19 : /// @date Mon, 17 Dec 2001
20 : ///
21 : // A storage for options (typed value containers)
22 : /****************************************************************************/
23 : #include <config.h>
24 :
25 : #include <map>
26 : #include <string>
27 : #include <exception>
28 : #include <algorithm>
29 : #include <vector>
30 : #include <iostream>
31 : #include <cstdlib>
32 : #include <ctime>
33 : #include <cstring>
34 : #include <cerrno>
35 : #include <iterator>
36 : #include <sstream>
37 : #include <utils/common/UtilExceptions.h>
38 : #include <utils/common/FileHelpers.h>
39 : #include <utils/common/MsgHandler.h>
40 : #include <utils/common/StringTokenizer.h>
41 : #include <utils/common/StringUtils.h>
42 : #include <utils/xml/SUMOSAXAttributes.h>
43 : #include "Option.h"
44 : #include "OptionsIO.h"
45 : #include "OptionsCont.h"
46 :
47 :
48 : // ===========================================================================
49 : // static member definitions
50 : // ===========================================================================
51 :
52 : OptionsCont OptionsCont::myOptions;
53 : OptionsCont OptionsCont::EMPTY_OPTIONS;
54 :
55 : // ===========================================================================
56 : // method definitions
57 : // ===========================================================================
58 :
59 : OptionsCont&
60 1550772218 : OptionsCont::getOptions() {
61 1550772218 : return myOptions;
62 : }
63 :
64 :
65 116856 : OptionsCont::OptionsCont() {
66 116856 : myCopyrightNotices.push_back(TL("Copyright (C) 2001-2024 German Aerospace Center (DLR) and others; https://sumo.dlr.de"));
67 116856 : }
68 :
69 :
70 117020 : OptionsCont::~OptionsCont() {
71 117020 : clear();
72 234040 : }
73 :
74 :
75 : void
76 26312911 : OptionsCont::doRegister(const std::string& name, Option* o) {
77 : // first check that option isn't null
78 26312911 : if (o == nullptr) {
79 0 : throw ProcessError("Option cannot be null");
80 : }
81 : // now check that there isn't another addresse (or synonym) related with the option
82 26312911 : if (myValues.find(name) != myValues.end()) {
83 0 : throw ProcessError(name + " is an already used option name.");
84 : }
85 : // check if previously was inserted in addresses (to avoid synonyms in addresses)
86 : bool isSynonym = false;
87 5271154762 : for (const auto& addresse : myAddresses) {
88 5244841851 : if (addresse.second == o) {
89 : isSynonym = true;
90 : }
91 : }
92 26312911 : if (!isSynonym) {
93 42395190 : myAddresses.push_back(std::make_pair(name, o));
94 : }
95 : // insert in values
96 26312911 : myValues[name] = o;
97 26312911 : }
98 :
99 :
100 : void
101 1448013 : OptionsCont::doRegister(const std::string& name1, char abbr, Option* o) {
102 1448013 : doRegister(name1, o);
103 1448013 : doRegister(convertChar(abbr), o);
104 1448013 : }
105 :
106 :
107 : void
108 3667303 : OptionsCont::addSynonyme(const std::string& name1, const std::string& name2, bool isDeprecated) {
109 : auto i1 = myValues.find(name1);
110 : auto i2 = myValues.find(name2);
111 3667303 : if (i1 == myValues.end() && i2 == myValues.end()) {
112 0 : throw ProcessError("Neither the option '" + name1 + "' nor the option '" + name2 + "' is known yet");
113 : }
114 3667303 : if (i1 != myValues.end() && i2 != myValues.end()) {
115 0 : if ((*i1).second == (*i2).second) {
116 : return;
117 : }
118 0 : throw ProcessError("Both options '" + name1 + "' and '" + name2 + "' do exist and differ.");
119 : }
120 3667303 : if (i1 == myValues.end() && i2 != myValues.end()) {
121 124853 : doRegister(name1, (*i2).second);
122 124853 : if (isDeprecated) {
123 0 : myDeprecatedSynonymes[name1] = false;
124 : }
125 : }
126 3667303 : if (i1 != myValues.end() && i2 == myValues.end()) {
127 3542450 : doRegister(name2, (*i1).second);
128 3542450 : if (isDeprecated) {
129 1946883 : myDeprecatedSynonymes[name2] = false;
130 : }
131 : }
132 : }
133 :
134 :
135 : void
136 104288 : OptionsCont::addXMLDefault(const std::string& name, const std::string& xmlRoot) {
137 104288 : myXMLDefaults[xmlRoot] = name;
138 104288 : }
139 :
140 :
141 : bool
142 331261047 : OptionsCont::exists(const std::string& name) const {
143 331261047 : return myValues.count(name) > 0;
144 : }
145 :
146 :
147 : bool
148 1649496451 : OptionsCont::isSet(const std::string& name, bool failOnNonExistant) const {
149 : auto i = myValues.find(name);
150 1649496451 : if (i == myValues.end()) {
151 200 : if (failOnNonExistant) {
152 0 : throw ProcessError(TLF("Internal request for unknown option '%'!", name));
153 : } else {
154 : return false;
155 : }
156 : }
157 1649496251 : return (*i).second->isSet();
158 : }
159 :
160 :
161 : bool
162 2219400 : OptionsCont::isDefault(const std::string& name) const {
163 : auto i = myValues.find(name);
164 2219400 : if (i == myValues.end()) {
165 : return false;
166 : }
167 2218954 : return (*i).second->isDefault();
168 : }
169 :
170 :
171 : Option*
172 328102268 : OptionsCont::getSecure(const std::string& name) const {
173 : const auto& valuesFinder = myValues.find(name);
174 328102268 : if (valuesFinder == myValues.end()) {
175 168 : throw ProcessError(TLF("No option with the name '%' exists.", name));
176 : }
177 : const auto& synonymFinder = myDeprecatedSynonymes.find(name);
178 328102212 : if ((synonymFinder != myDeprecatedSynonymes.end()) && !synonymFinder->second) {
179 : std::string defaultName;
180 7018 : for (const auto& subtopicEntry : mySubTopicEntries) {
181 107540 : for (const auto& value : subtopicEntry.second) {
182 : const auto l = myValues.find(value);
183 101723 : if ((l != myValues.end()) && (l->second == valuesFinder->second)) {
184 : defaultName = value;
185 : break;
186 : }
187 : }
188 7018 : if (defaultName != "") {
189 : break;
190 : }
191 : }
192 3603 : WRITE_WARNINGF(TL("Please note that '%' is deprecated.\n Use '%' instead."), name, defaultName);
193 1201 : synonymFinder->second = true;
194 : }
195 328102212 : return valuesFinder->second;
196 : }
197 :
198 :
199 : std::string
200 11562180 : OptionsCont::getValueString(const std::string& name) const {
201 11562180 : Option* o = getSecure(name);
202 11562180 : return o->getValueString();
203 : }
204 :
205 :
206 : std::string
207 26903583 : OptionsCont::getString(const std::string& name) const {
208 26903583 : Option* o = getSecure(name);
209 26903583 : return o->getString();
210 : }
211 :
212 :
213 : double
214 109692134 : OptionsCont::getFloat(const std::string& name) const {
215 109692134 : Option* o = getSecure(name);
216 109692132 : return o->getFloat();
217 : }
218 :
219 :
220 : int
221 2133137 : OptionsCont::getInt(const std::string& name) const {
222 2133137 : Option* o = getSecure(name);
223 2133137 : return o->getInt();
224 : }
225 :
226 :
227 : bool
228 121850286 : OptionsCont::getBool(const std::string& name) const {
229 121850286 : Option* o = getSecure(name);
230 121850286 : return o->getBool();
231 : }
232 :
233 :
234 : const IntVector&
235 0 : OptionsCont::getIntVector(const std::string& name) const {
236 0 : Option* o = getSecure(name);
237 0 : return o->getIntVector();
238 : }
239 :
240 : const StringVector&
241 1266998 : OptionsCont::getStringVector(const std::string& name) const {
242 1266998 : Option* o = getSecure(name);
243 1266998 : return o->getStringVector();
244 : }
245 :
246 :
247 : bool
248 1129555 : OptionsCont::set(const std::string& name, const std::string& value, const bool append) {
249 1129555 : Option* o = getSecure(name);
250 1129537 : if (!o->isWriteable()) {
251 0 : reportDoubleSetting(name);
252 0 : return false;
253 : }
254 : try {
255 : // Substitute environment variables defined by ${NAME} with their value
256 2259056 : if (!o->set(StringUtils::substituteEnvironment(value, &OptionsIO::getLoadTime()), value, append)) {
257 : return false;
258 : }
259 18 : } catch (ProcessError& e) {
260 36 : WRITE_ERROR("While processing option '" + name + "':\n " + e.what());
261 : return false;
262 18 : }
263 : return true;
264 : }
265 :
266 :
267 : bool
268 166810 : OptionsCont::setDefault(const std::string& name, const std::string& value) {
269 166810 : Option* const o = getSecure(name);
270 166810 : if (o->isWriteable() && set(name, value)) {
271 145884 : o->resetDefault();
272 145884 : return true;
273 : }
274 : return false;
275 : }
276 :
277 :
278 : bool
279 24 : OptionsCont::setByRootElement(const std::string& root, const std::string& value) {
280 : if (myXMLDefaults.count(root) > 0) {
281 1 : return set(myXMLDefaults[root], value);
282 : }
283 46 : if (myXMLDefaults.count("") > 0) {
284 46 : return set(myXMLDefaults[""], value);
285 : }
286 : return false;
287 : }
288 :
289 :
290 : std::vector<std::string>
291 41510 : OptionsCont::getSynonymes(const std::string& name) const {
292 41510 : Option* o = getSecure(name);
293 : std::vector<std::string> synonymes;
294 18310669 : for (const auto& value : myValues) {
295 18269159 : if ((value.second == o) && (name != value.first)) {
296 12400 : synonymes.push_back(value.first);
297 : }
298 : }
299 41510 : return synonymes;
300 0 : }
301 :
302 :
303 : const std::string&
304 0 : OptionsCont::getDescription(const std::string& name) const {
305 0 : return getSecure(name)->getDescription();
306 : }
307 :
308 :
309 : const std::string&
310 0 : OptionsCont::getSubTopic(const std::string& name) const {
311 0 : return getSecure(name)->getSubTopic();
312 : }
313 :
314 :
315 : std::ostream&
316 0 : operator<<(std::ostream& os, const OptionsCont& oc) {
317 : std::vector<std::string> done;
318 : os << "Options set:" << std::endl;
319 0 : for (const auto& value : oc.myValues) {
320 0 : const auto& finder = std::find(done.begin(), done.end(), value.first);
321 0 : if (finder == done.end()) {
322 0 : std::vector<std::string> synonymes = oc.getSynonymes(value.first);
323 0 : if (synonymes.size() != 0) {
324 0 : os << value.first << " (";
325 0 : for (auto synonym = synonymes.begin(); synonym != synonymes.end(); synonym++) {
326 0 : if (synonym != synonymes.begin()) {
327 0 : os << ", ";
328 : }
329 : os << (*synonym);
330 : }
331 0 : os << ")";
332 : } else {
333 : os << value.first;
334 : }
335 0 : if (value.second->isSet()) {
336 0 : os << ": " << value.second->getValueString() << std::endl;
337 : } else {
338 : os << ": <INVALID>" << std::endl;
339 : }
340 0 : done.push_back(value.first);
341 : copy(synonymes.begin(), synonymes.end(), back_inserter(done));
342 0 : }
343 : }
344 0 : return os;
345 0 : }
346 :
347 :
348 : void
349 11266 : OptionsCont::relocateFiles(const std::string& configuration) const {
350 4820074 : for (const auto& addresse : myAddresses) {
351 4808808 : if (addresse.second->isFileName() && addresse.second->isSet()) {
352 66943 : StringVector fileList = StringVector(addresse.second->getStringVector());
353 142657 : for (auto& file : fileList) {
354 75714 : file = FileHelpers::checkForRelativity(file, configuration);
355 : try {
356 151426 : file = StringUtils::urlDecode(file);
357 2 : } catch (NumberFormatException& e) {
358 4 : WRITE_WARNING(toString(e.what()) + " when trying to decode filename '" + file + "'.");
359 2 : }
360 : }
361 200829 : StringVector rawList = StringTokenizer(addresse.second->getValueString(), ",").getVector();
362 142657 : for (auto& file : rawList) {
363 151428 : file = FileHelpers::checkForRelativity(file, configuration);
364 : }
365 66943 : const std::string conv = joinToString(fileList, ',');
366 133886 : if (conv != joinToString(addresse.second->getStringVector(), ',')) {
367 2985 : const bool hadDefault = addresse.second->isDefault();
368 2985 : addresse.second->set(conv, joinToString(rawList, ','), false);
369 2985 : if (hadDefault) {
370 672 : addresse.second->resetDefault();
371 : }
372 : }
373 66943 : }
374 : }
375 11266 : }
376 :
377 :
378 : bool
379 96894 : OptionsCont::isUsableFileList(const std::string& name) const {
380 96894 : Option* const o = getSecure(name);
381 96894 : if (!o->isSet()) {
382 : return false;
383 : }
384 : // check whether the list of files is valid
385 : bool ok = true;
386 89442 : std::vector<std::string> files = getStringVector(name);
387 89442 : if (files.size() == 0) {
388 0 : WRITE_ERRORF(TL("The file list for '%' is empty."), name);
389 : ok = false;
390 : }
391 194215 : for (const auto& file : files) {
392 209546 : if (!FileHelpers::isReadable(file)) {
393 127 : if (file != "") {
394 381 : WRITE_ERRORF(TL("File '%' is not accessible (%)."), file, std::strerror(errno));
395 : ok = false;
396 : } else {
397 0 : WRITE_WARNING(TL("Empty file name given; ignoring."));
398 : }
399 : }
400 : }
401 : return ok;
402 89442 : }
403 :
404 :
405 : bool
406 5718 : OptionsCont::checkDependingSuboptions(const std::string& name, const std::string& prefix) const {
407 5718 : Option* o = getSecure(name);
408 5718 : if (o->isSet()) {
409 : return true;
410 : }
411 : bool ok = true;
412 : std::vector<std::string> seenSynonymes;
413 2684229 : for (const auto& value : myValues) {
414 2678530 : if (std::find(seenSynonymes.begin(), seenSynonymes.end(), value.first) != seenSynonymes.end()) {
415 1 : continue;
416 : }
417 2767902 : if (value.second->isSet() && !value.second->isDefault() && value.first.find(prefix) == 0) {
418 3 : WRITE_ERRORF(TL("Option '%' needs option '%'."), value.first, name);
419 1 : std::vector<std::string> synonymes = getSynonymes(value.first);
420 : std::copy(synonymes.begin(), synonymes.end(), std::back_inserter(seenSynonymes));
421 : ok = false;
422 1 : }
423 : }
424 : return ok;
425 5699 : }
426 :
427 :
428 : void
429 0 : OptionsCont::reportDoubleSetting(const std::string& arg) const {
430 0 : std::vector<std::string> synonymes = getSynonymes(arg);
431 0 : std::ostringstream s;
432 0 : s << TLF("A value for the option '%' was already set.\n Possible synonymes: ", arg);
433 : auto synonym = synonymes.begin();
434 0 : while (synonym != synonymes.end()) {
435 : s << (*synonym);
436 : synonym++;
437 0 : if (synonym != synonymes.end()) {
438 0 : s << ", ";
439 : }
440 : }
441 0 : WRITE_ERROR(s.str());
442 0 : }
443 :
444 :
445 : std::string
446 1448013 : OptionsCont::convertChar(char abbr) const {
447 : char buf[2];
448 1448013 : buf[0] = abbr;
449 1448013 : buf[1] = 0;
450 1448013 : std::string s(buf);
451 1448013 : return s;
452 : }
453 :
454 :
455 : bool
456 758203 : OptionsCont::isBool(const std::string& name) const {
457 758203 : Option* o = getSecure(name);
458 758173 : return o->isBool();
459 : }
460 :
461 :
462 : void
463 69747 : OptionsCont::resetWritable() {
464 26036991 : for (const auto& addresse : myAddresses) {
465 25967244 : addresse.second->resetWritable();
466 : }
467 69747 : }
468 :
469 :
470 : void
471 0 : OptionsCont::resetDefault() {
472 0 : for (const auto& addresse : myAddresses) {
473 0 : addresse.second->resetDefault();
474 : }
475 0 : }
476 :
477 :
478 : void
479 0 : OptionsCont::resetDefault(const std::string& name) {
480 0 : getSecure(name)->resetDefault();
481 0 : }
482 :
483 :
484 : bool
485 95632 : OptionsCont::isWriteable(const std::string& name) {
486 95632 : Option* o = getSecure(name);
487 95626 : return o->isWriteable();
488 : }
489 :
490 :
491 : void
492 212004 : OptionsCont::clear() {
493 : // delete only address (because synonyms placed in values aim to the same Option)
494 21466343 : for (const auto& addresse : myAddresses) {
495 21254339 : delete addresse.second;
496 : }
497 : myAddresses.clear();
498 : myValues.clear();
499 : mySubTopics.clear();
500 : mySubTopicEntries.clear();
501 212004 : }
502 :
503 :
504 : void
505 21154024 : OptionsCont::addDescription(const std::string& name, const std::string& subtopic,
506 : const std::string& description) {
507 21154024 : Option* o = getSecure(name);
508 21154024 : if (o == nullptr) {
509 0 : throw ProcessError("Option doesn't exist");
510 : }
511 21154024 : if (find(mySubTopics.begin(), mySubTopics.end(), subtopic) == mySubTopics.end()) {
512 0 : throw ProcessError("SubTopic '" + subtopic + "' doesn't exist");
513 : }
514 21154024 : o->setDescription(description);
515 21154024 : o->setSubtopic(subtopic);
516 21154024 : mySubTopicEntries[subtopic].push_back(name);
517 21154024 : }
518 :
519 :
520 : void
521 0 : OptionsCont::setFurtherAttributes(const std::string& name, const std::string& subtopic, bool required, bool positional, const std::string& listSep) {
522 0 : Option* o = getSecure(name);
523 0 : if (o == nullptr) {
524 0 : throw ProcessError("Option doesn't exist");
525 : }
526 0 : if (find(mySubTopics.begin(), mySubTopics.end(), subtopic) == mySubTopics.end()) {
527 0 : throw ProcessError("SubTopic '" + subtopic + "' doesn't exist");
528 : }
529 0 : if (required) {
530 0 : o->setRequired();
531 : }
532 0 : if (positional) {
533 0 : o->setPositional();
534 : }
535 0 : o->setListSeparator(listSep);
536 0 : }
537 :
538 :
539 : void
540 57956 : OptionsCont::setApplicationName(const std::string& appName,
541 : const std::string& fullName) {
542 57956 : myAppName = appName;
543 57956 : myFullName = fullName;
544 57956 : }
545 :
546 :
547 : void
548 57385 : OptionsCont::setApplicationDescription(const std::string& appDesc) {
549 57385 : myAppDescription = appDesc;
550 57385 : }
551 :
552 :
553 : void
554 146615 : OptionsCont::addCallExample(const std::string& example, const std::string& desc) {
555 146615 : myCallExamples.push_back(std::make_pair(example, desc));
556 146615 : }
557 :
558 :
559 : void
560 119 : OptionsCont::setAdditionalHelpMessage(const std::string& add) {
561 119 : myAdditionalMessage = add;
562 119 : }
563 :
564 :
565 : void
566 14 : OptionsCont::addCopyrightNotice(const std::string& copyrightLine) {
567 14 : myCopyrightNotices.push_back(copyrightLine);
568 14 : }
569 :
570 :
571 : void
572 0 : OptionsCont::clearCopyrightNotices() {
573 : myCopyrightNotices.clear();
574 0 : }
575 :
576 :
577 : void
578 1316201 : OptionsCont::addOptionSubTopic(const std::string& topic) {
579 1316201 : mySubTopics.push_back(topic);
580 1316201 : mySubTopicEntries[topic] = std::vector<std::string>();
581 1316201 : }
582 :
583 :
584 : void
585 19007 : OptionsCont::splitLines(std::ostream& os, std::string what,
586 : int offset, int nextOffset) {
587 58369 : while (what.length() > 0) {
588 39362 : if ((int)what.length() > 79 - offset) {
589 20388 : std::string::size_type splitPos = what.rfind(';', 79 - offset);
590 20388 : if (splitPos == std::string::npos) {
591 20126 : splitPos = what.rfind(' ', 79 - offset);
592 : } else {
593 262 : splitPos++;
594 : }
595 20388 : if (splitPos != std::string::npos) {
596 20355 : os << what.substr(0, splitPos) << std::endl;
597 20355 : what = what.substr(splitPos + 1);
598 828966 : for (int r = 0; r < (nextOffset + 1); ++r) {
599 808611 : os << ' ';
600 : }
601 : } else {
602 : os << what;
603 : what = "";
604 : }
605 : offset = nextOffset;
606 : } else {
607 : os << what;
608 : what = "";
609 : }
610 : }
611 : os << std::endl;
612 19007 : }
613 :
614 :
615 : bool
616 58389 : OptionsCont::processMetaOptions(bool missingOptions) {
617 58389 : MsgHandler::setupI18n(getString("language"));
618 58389 : if (missingOptions) {
619 : // no options are given
620 : std::cout << myFullName << std::endl;
621 74 : std::cout << TL(" Build features: ") << HAVE_ENABLED << std::endl;
622 149 : for (const auto& copyrightNotice : myCopyrightNotices) {
623 75 : std::cout << " " << copyrightNotice.data() << std::endl;
624 : }
625 74 : std::cout << TL(" License EPL-2.0: Eclipse Public License Version 2 <https://eclipse.org/legal/epl-v20.html>") << std::endl;
626 74 : std::cout << TL(" Use --help to get the list of options.") << std::endl;
627 74 : return true;
628 : }
629 :
630 : // check whether the help shall be printed
631 116630 : if (getBool("help")) {
632 : std::cout << myFullName << std::endl;
633 151 : for (const auto& copyrightNotice : myCopyrightNotices) {
634 76 : std::cout << " " << copyrightNotice.data() << std::endl;
635 : }
636 75 : printHelp(std::cout);
637 75 : return true;
638 : }
639 : // check whether the help shall be printed
640 116480 : if (getBool("version")) {
641 : std::cout << myFullName << std::endl;
642 13 : std::cout << TL(" Build features: ") << HAVE_ENABLED << std::endl;
643 27 : for (const auto& copyrightNotice : myCopyrightNotices) {
644 14 : std::cout << " " << copyrightNotice.data() << std::endl;
645 : }
646 13 : std::cout << "\n" << myFullName << " is part of SUMO.\n";
647 13 : std::cout << "This program and the accompanying materials\n";
648 13 : std::cout << "are made available under the terms of the Eclipse Public License v2.0\n";
649 13 : std::cout << "which accompanies this distribution, and is available at\n";
650 13 : std::cout << "http://www.eclipse.org/legal/epl-v20.html\n";
651 13 : std::cout << "This program may also be made available under the following Secondary\n";
652 13 : std::cout << "Licenses when the conditions for such availability set forth in the Eclipse\n";
653 13 : std::cout << "Public License 2.0 are satisfied: GNU General Public License, version 2\n";
654 13 : std::cout << "or later which is available at\n";
655 13 : std::cout << "https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html\n";
656 : std::cout << "SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later" << std::endl;
657 13 : return true;
658 : }
659 : // check whether the settings shall be printed
660 116454 : if (getBool("print-options")) {
661 0 : std::cout << (*this);
662 : }
663 : // check whether something has to be done with options
664 : // whether the current options shall be saved
665 116454 : if (isSet("save-configuration")) {
666 378 : const std::string& configPath = getString("save-configuration");
667 378 : if (configPath == "-" || configPath == "stdout") {
668 24 : writeConfiguration(std::cout, true, false, getBool("save-commented"));
669 12 : return true;
670 : }
671 732 : std::ofstream out(StringUtils::transcodeToLocal(configPath).c_str());
672 366 : if (!out.good()) {
673 0 : throw ProcessError(TLF("Could not save configuration to '%'", configPath));
674 : } else {
675 366 : writeConfiguration(out, true, false, getBool("save-commented"), configPath);
676 732 : if (getBool("verbose")) {
677 498 : WRITE_MESSAGEF(TL("Written configuration to '%'"), configPath);
678 : }
679 : return true;
680 : }
681 366 : }
682 : // whether the template shall be saved
683 115698 : if (isSet("save-template")) {
684 60 : if (getString("save-template") == "-" || getString("save-template") == "stdout") {
685 4 : writeConfiguration(std::cout, false, true, getBool("save-commented"));
686 2 : return true;
687 : }
688 36 : std::ofstream out(StringUtils::transcodeToLocal(getString("save-template")).c_str());
689 18 : if (!out.good()) {
690 0 : throw ProcessError(TLF("Could not save template to '%'", getString("save-template")));
691 : } else {
692 36 : writeConfiguration(out, false, true, getBool("save-commented"));
693 36 : if (getBool("verbose")) {
694 0 : WRITE_MESSAGEF(TL("Written template to '%'"), getString("save-template"));
695 : }
696 : return true;
697 : }
698 18 : }
699 115658 : if (isSet("save-schema")) {
700 3 : if (getString("save-schema") == "-" || getString("save-schema") == "stdout") {
701 0 : writeSchema(std::cout);
702 0 : return true;
703 : }
704 2 : std::ofstream out(StringUtils::transcodeToLocal(getString("save-schema")).c_str());
705 1 : if (!out.good()) {
706 0 : throw ProcessError(TLF("Could not save schema to '%'", getString("save-schema")));
707 : } else {
708 1 : writeSchema(out);
709 2 : if (getBool("verbose")) {
710 0 : WRITE_MESSAGEF(TL("Written schema to '%'"), getString("save-schema"));
711 : }
712 : return true;
713 : }
714 1 : }
715 : return false;
716 : }
717 :
718 :
719 : const std::vector<std::string>&
720 0 : OptionsCont::getSubTopics() const {
721 0 : return mySubTopics;
722 : }
723 :
724 :
725 : std::vector<std::string>
726 0 : OptionsCont::getSubTopicsEntries(const std::string& subtopic) const {
727 : if (mySubTopicEntries.count(subtopic) > 0) {
728 0 : return mySubTopicEntries.find(subtopic)->second;
729 : } else {
730 0 : return std::vector<std::string>();
731 : }
732 : }
733 :
734 :
735 : std::string
736 0 : OptionsCont::getTypeName(const std::string name) {
737 0 : return getSecure(name)->getTypeName();
738 : }
739 :
740 :
741 : const std::string&
742 0 : OptionsCont::getFullName() const {
743 0 : return myFullName;
744 : }
745 :
746 :
747 : bool
748 0 : OptionsCont::isEmpty() const {
749 0 : return myAddresses.size() == 0;
750 : }
751 :
752 :
753 : std::vector<std::pair<std::string, Option*> >::const_iterator
754 0 : OptionsCont::begin() const {
755 0 : return myAddresses.cbegin();
756 : }
757 :
758 :
759 : std::vector<std::pair<std::string, Option*> >::const_iterator
760 0 : OptionsCont::end() const {
761 0 : return myAddresses.cend();
762 : }
763 :
764 :
765 : void
766 75 : OptionsCont::printHelp(std::ostream& os) {
767 : // print application description
768 150 : splitLines(os, TL(myAppDescription.c_str()), 0, 0);
769 : os << std::endl;
770 :
771 : // check option sizes first
772 : // we want to know how large the largest not-too-large-entry will be
773 : int tooLarge = 40;
774 : int maxSize = 0;
775 1299 : for (const auto& subTopic : mySubTopics) {
776 20156 : for (const auto& entry : mySubTopicEntries[subTopic]) {
777 18932 : Option* o = getSecure(entry);
778 : // name, two leading spaces and "--"
779 18932 : int csize = (int)entry.length() + 2 + 4;
780 : // abbreviation length ("-X, "->4chars) if any
781 18932 : const auto synonymes = getSynonymes(entry);
782 21935 : for (const auto& synonym : synonymes) {
783 4640 : if (synonym.length() == 1 && myDeprecatedSynonymes.count(synonym) == 0) {
784 1637 : csize += 4;
785 1637 : break;
786 : }
787 : }
788 : // the type name
789 18932 : if (!o->isBool()) {
790 12988 : csize += 1 + (int)o->getTypeName().length();
791 : }
792 : // divider
793 18932 : csize += 2;
794 18932 : if (csize < tooLarge && maxSize < csize) {
795 : maxSize = csize;
796 : }
797 18932 : }
798 : }
799 :
800 150 : const std::string helpTopic = StringUtils::to_lower_case(getSecure("help")->getValueString());
801 75 : if (helpTopic != "") {
802 : bool foundTopic = false;
803 0 : for (const auto& topic : mySubTopics) {
804 0 : if (StringUtils::to_lower_case(topic).find(helpTopic) != std::string::npos) {
805 : foundTopic = true;
806 0 : printHelpOnTopic(topic, tooLarge, maxSize, os);
807 : }
808 : }
809 0 : if (!foundTopic) {
810 : // print topic list
811 : os << "Help Topics:" << std::endl;
812 0 : for (const std::string& t : mySubTopics) {
813 : os << " " << t << std::endl;
814 : }
815 : }
816 : return;
817 : }
818 : // print usage BNF
819 : os << "Usage: " << myAppName << " [OPTION]*" << std::endl;
820 : // print additional text if any
821 75 : if (myAdditionalMessage.length() > 0) {
822 : os << myAdditionalMessage << std::endl << std::endl;
823 : }
824 : // print the options
825 1299 : for (const auto& subTopic : mySubTopics) {
826 1224 : printHelpOnTopic(subTopic, tooLarge, maxSize, os);
827 : }
828 : os << std::endl;
829 : // print usage examples, calc size first
830 75 : if (myCallExamples.size() != 0) {
831 : os << "Examples:" << std::endl;
832 219 : for (const auto& callExample : myCallExamples) {
833 144 : os << " " << myAppName << ' ' << callExample.first << std::endl;
834 : os << " " << callExample.second << std::endl;
835 : }
836 : }
837 : os << std::endl;
838 : os << "Report bugs at <https://github.com/eclipse-sumo/sumo/issues>." << std::endl;
839 : os << "Get in contact via <sumo@dlr.de>." << std::endl;
840 : }
841 :
842 :
843 : void
844 1224 : OptionsCont::printHelpOnTopic(const std::string& topic, int tooLarge, int maxSize, std::ostream& os) {
845 : os << topic << " Options:" << std::endl;
846 20156 : for (const auto& entry : mySubTopicEntries[topic]) {
847 : // start length computation
848 18932 : int csize = (int)entry.length() + 2;
849 18932 : Option* o = getSecure(entry);
850 18932 : os << " ";
851 : // write abbreviation if given
852 18932 : const auto synonymes = getSynonymes(entry);
853 21935 : for (const auto& synonym : synonymes) {
854 4640 : if (synonym.length() == 1 && myDeprecatedSynonymes.count(synonym) == 0) {
855 3274 : os << '-' << synonym << ", ";
856 1637 : csize += 4;
857 1637 : break;
858 : }
859 : }
860 : // write leading '-'/"--"
861 18932 : os << "--";
862 18932 : csize += 2;
863 : // write the name
864 : os << entry;
865 : // write the type if not a bool option
866 18932 : if (!o->isBool()) {
867 12988 : os << ' ' << o->getTypeName();
868 12988 : csize += 1 + (int)o->getTypeName().length();
869 : }
870 18932 : csize += 2;
871 : // write the description formatting it
872 18932 : os << " ";
873 190831 : for (int r = maxSize; r > csize; --r) {
874 171899 : os << ' ';
875 : }
876 18932 : int offset = csize > tooLarge ? csize : maxSize;
877 37864 : splitLines(os, o->getDescription(), offset, maxSize);
878 18932 : }
879 : os << std::endl;
880 1224 : }
881 :
882 :
883 : void
884 82535 : OptionsCont::writeConfiguration(std::ostream& os, const bool filled,
885 : const bool complete, const bool addComments, const std::string& relativeTo,
886 : const bool forceRelative, const bool inComment) const {
887 82535 : if (!inComment) {
888 613 : writeXMLHeader(os, false);
889 : }
890 157633 : const std::string& app = myAppName == "sumo-gui" ? "sumo" : myAppName;
891 : os << "<" << app << "Configuration xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
892 82535 : << "xsi:noNamespaceSchemaLocation=\"http://sumo.dlr.de/xsd/" << app << "Configuration.xsd\">\n\n";
893 2040554 : for (std::string subtopic : mySubTopics) {
894 1958019 : if (subtopic == "Configuration" && !complete) {
895 : continue;
896 : }
897 : const std::vector<std::string>& entries = mySubTopicEntries.find(subtopic)->second;
898 : std::replace(subtopic.begin(), subtopic.end(), ' ', '_');
899 3751008 : subtopic = StringUtils::to_lower_case(subtopic);
900 : bool hadOne = false;
901 33082737 : for (const std::string& name : entries) {
902 31207233 : Option* o = getSecure(name);
903 31207233 : bool write = complete || (filled && !o->isDefault());
904 30415246 : if (!write) {
905 30415246 : continue;
906 : }
907 791987 : if (name == "registry-viewport" && !complete) {
908 0 : continue;
909 : }
910 791987 : if (!hadOne) {
911 357502 : os << " <" << subtopic << ">\n";
912 : }
913 : // add the comment if wished
914 791987 : if (addComments) {
915 4314 : os << " <!-- " << StringUtils::escapeXML(o->getDescription(), inComment) << " -->\n";
916 : }
917 : // write the option and the value (if given)
918 791987 : os << " <" << name << " value=\"";
919 791987 : if (o->isSet() && (filled || o->isDefault())) {
920 791136 : if (o->isFileName() && relativeTo != "") {
921 5205 : StringVector fileList = StringTokenizer(o->getValueString(), ",").getVector();
922 3523 : for (auto& file : fileList) {
923 3576 : if (StringUtils::startsWith(file, "${")) {
924 : // there is an environment variable up front, assume it points to an absolute path
925 : // not even forcing relativity makes sense here
926 34 : file = StringUtils::urlEncode(file, " ;%");
927 : } else {
928 5313 : file = FileHelpers::fixRelative(
929 3542 : StringUtils::urlEncode(file, " ;%"),
930 1771 : StringUtils::urlEncode(relativeTo, " ;%"),
931 7084 : forceRelative || getBool("save-configuration.relative"));
932 : }
933 : }
934 1735 : os << StringUtils::escapeXML(joinToString(fileList, ','), inComment);
935 1735 : } else {
936 1578802 : os << StringUtils::escapeXML(o->getValueString(), inComment);
937 : }
938 : }
939 791987 : if (complete) {
940 3645 : std::vector<std::string> synonymes = getSynonymes(name);
941 3645 : if (!synonymes.empty()) {
942 1011 : os << "\" synonymes=\"";
943 2444 : for (auto synonym = synonymes.begin(); synonym != synonymes.end(); synonym++) {
944 1433 : if (synonym != synonymes.begin()) {
945 422 : os << " ";
946 : }
947 : os << (*synonym);
948 : }
949 : }
950 3645 : os << "\" type=\"" << o->getTypeName();
951 3645 : if (!addComments) {
952 4414 : os << "\" help=\"" << StringUtils::escapeXML(o->getDescription());
953 : }
954 3645 : }
955 791987 : os << "\"/>\n";
956 : // append an endline if a comment was printed
957 791987 : if (addComments) {
958 1438 : os << "\n";
959 : }
960 : hadOne = true;
961 : }
962 1875504 : if (hadOne) {
963 357502 : os << " </" << subtopic << ">\n\n";
964 : }
965 : }
966 : os << "</" << app << "Configuration>" << std::endl; // flushing seems like a good idea here
967 82535 : }
968 :
969 :
970 : void
971 1 : OptionsCont::writeSchema(std::ostream& os) {
972 1 : const std::string& app = myAppName == "sumo-gui" ? "sumo" : myAppName;
973 1 : writeXMLHeader(os, false);
974 1 : os << "<xsd:schema elementFormDefault=\"qualified\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">\n\n";
975 1 : os << " <xsd:include schemaLocation=\"baseTypes.xsd\"/>\n";
976 1 : os << " <xsd:element name=\"" << app << "Configuration\" type=\"configurationType\"/>\n\n";
977 1 : os << " <xsd:complexType name=\"configurationType\">\n";
978 1 : os << " <xsd:all>\n";
979 28 : for (std::string subtopic : mySubTopics) {
980 27 : if (subtopic == "Configuration") {
981 : continue;
982 : }
983 : std::replace(subtopic.begin(), subtopic.end(), ' ', '_');
984 52 : subtopic = StringUtils::to_lower_case(subtopic);
985 26 : os << " <xsd:element name=\"" << subtopic << "\" type=\"" << subtopic << "TopicType\" minOccurs=\"0\"/>\n";
986 : }
987 1 : os << " </xsd:all>\n";
988 1 : os << " </xsd:complexType>\n\n";
989 28 : for (std::string subtopic : mySubTopics) {
990 27 : if (subtopic == "Configuration") {
991 : continue;
992 : }
993 : const std::vector<std::string>& entries = mySubTopicEntries.find(subtopic)->second;
994 : std::replace(subtopic.begin(), subtopic.end(), ' ', '_');
995 52 : subtopic = StringUtils::to_lower_case(subtopic);
996 26 : os << " <xsd:complexType name=\"" << subtopic << "TopicType\">\n";
997 26 : os << " <xsd:all>\n";
998 458 : for (const auto& entry : entries) {
999 432 : Option* o = getSecure(entry);
1000 432 : std::string type = o->getTypeName();
1001 432 : type = StringUtils::to_lower_case(type);
1002 432 : if (type == "int[]") {
1003 : type = "intArray";
1004 : }
1005 432 : if (type == "str[]") {
1006 : type = "strArray";
1007 : }
1008 432 : os << " <xsd:element name=\"" << entry << "\" type=\"" << type << "OptionType\" minOccurs=\"0\"/>\n";
1009 : }
1010 26 : os << " </xsd:all>\n";
1011 26 : os << " </xsd:complexType>\n\n";
1012 : }
1013 1 : os << "</xsd:schema>\n";
1014 1 : }
1015 :
1016 :
1017 : void
1018 82536 : OptionsCont::writeXMLHeader(std::ostream& os, const bool includeConfig) const {
1019 : time_t rawtime;
1020 : char buffer [80];
1021 :
1022 82536 : os << "<?xml version=\"1.0\"" << SUMOSAXAttributes::ENCODING << "?>\n\n";
1023 82536 : time(&rawtime);
1024 82536 : strftime(buffer, 80, "<!-- generated on %F %T by ", localtime(&rawtime));
1025 82536 : os << buffer << myFullName << "\n";
1026 165072 : if (getBool("write-license")) {
1027 : os << "This data file and the accompanying materials\n"
1028 : "are made available under the terms of the Eclipse Public License v2.0\n"
1029 : "which accompanies this distribution, and is available at\n"
1030 : "http://www.eclipse.org/legal/epl-v20.html\n"
1031 : "This file may also be made available under the following Secondary\n"
1032 : "Licenses when the conditions for such availability set forth in the Eclipse\n"
1033 : "Public License 2.0 are satisfied: GNU General Public License, version 2\n"
1034 : "or later which is available at\n"
1035 : "https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html\n"
1036 65594 : "SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later\n";
1037 : }
1038 82536 : if (includeConfig) {
1039 163844 : writeConfiguration(os, true, false, false, "", false, true);
1040 : }
1041 82536 : os << "-->\n\n";
1042 82536 : }
1043 :
1044 :
1045 : bool
1046 39083 : OptionsCont::isInStringVector(const std::string& optionName,
1047 : const std::string& itemName) const {
1048 39083 : if (isSet(optionName)) {
1049 0 : std::vector<std::string> values = getStringVector(optionName);
1050 0 : return std::find(values.begin(), values.end(), itemName) != values.end();
1051 0 : }
1052 : return false;
1053 : }
1054 :
1055 :
1056 : OptionsCont*
1057 164 : OptionsCont::clone() const {
1058 : // build a clone to call writeConfiguration on
1059 : // (with the possibility of changing a few settings and not affecting the original)
1060 164 : OptionsCont* oc = new OptionsCont(*this);
1061 164 : oc->resetWritable();
1062 56908 : for (auto& addr : oc->myAddresses) {
1063 56744 : addr.second = addr.second->clone();
1064 : }
1065 164 : return oc;
1066 : }
1067 :
1068 :
1069 : /****************************************************************************/
|