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