Line data Source code
1 : /****************************************************************************/
2 : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3 : // Copyright (C) 2001-2026 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 1028715938 : OptionsCont::getOptions() {
62 1028715938 : return myOptions;
63 : }
64 :
65 :
66 102420 : OptionsCont::OptionsCont() {
67 102420 : myCopyrightNotices.push_back(TL("Copyright (C) 2001-2026 German Aerospace Center (DLR) and others; https://sumo.dlr.de"));
68 102420 : }
69 :
70 :
71 102592 : OptionsCont::~OptionsCont() {
72 102592 : clear();
73 205184 : }
74 :
75 :
76 : void
77 25607099 : OptionsCont::doRegister(const std::string& name, Option* o) {
78 : // first check that option isn't null
79 25607099 : 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 25607099 : 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 5518386336 : for (const auto& addresse : myAddresses) {
89 5492779237 : if (addresse.second == o) {
90 : isSynonym = true;
91 : }
92 : }
93 25607099 : if (!isSynonym) {
94 41632576 : myAddresses.push_back(std::make_pair(name, o));
95 : }
96 : // insert in values
97 25607099 : myValues[name] = o;
98 25607099 : }
99 :
100 :
101 : void
102 1345940 : OptionsCont::doRegister(const std::string& name1, char abbr, Option* o) {
103 1345940 : doRegister(name1, o);
104 1345940 : doRegister(convertChar(abbr), o);
105 1345940 : }
106 :
107 :
108 : void
109 3444871 : 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 3444871 : if (i1 == myValues.end() && i2 == myValues.end()) {
113 0 : throw ProcessError("Neither the option '" + name1 + "' nor the option '" + name2 + "' is known yet");
114 : }
115 3444871 : 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 3444871 : if (i1 == myValues.end() && i2 != myValues.end()) {
122 110427 : doRegister(name1, (*i2).second);
123 110427 : if (isDeprecated) {
124 0 : myDeprecatedSynonymes[name1] = false;
125 : }
126 : }
127 3444871 : if (i1 != myValues.end() && i2 == myValues.end()) {
128 3334444 : doRegister(name2, (*i1).second);
129 3334444 : if (isDeprecated) {
130 1765307 : myDeprecatedSynonymes[name2] = false;
131 : }
132 : }
133 : }
134 :
135 :
136 : void
137 95550 : OptionsCont::addXMLDefault(const std::string& name, const std::string& xmlRoot) {
138 95550 : myXMLDefaults[xmlRoot] = name;
139 95550 : }
140 :
141 :
142 : bool
143 375642886 : OptionsCont::exists(const std::string& name) const {
144 375642886 : return myValues.count(name) > 0;
145 : }
146 :
147 :
148 : bool
149 1082222129 : OptionsCont::isSet(const std::string& name, bool failOnNonExistant) const {
150 : auto i = myValues.find(name);
151 1082222129 : 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 1082221927 : return (*i).second->isSet();
159 : }
160 :
161 :
162 : bool
163 2237254 : OptionsCont::isDefault(const std::string& name) const {
164 : auto i = myValues.find(name);
165 2237254 : if (i == myValues.end()) {
166 : return false;
167 : }
168 2236806 : return (*i).second->isDefault();
169 : }
170 :
171 :
172 : Option*
173 355549452 : OptionsCont::getSecure(const std::string& name) const {
174 : const auto& valuesFinder = myValues.find(name);
175 355549452 : 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 355549398 : if ((synonymFinder != myDeprecatedSynonymes.end()) && !synonymFinder->second) {
180 : std::string defaultName;
181 7018 : for (const auto& subtopicEntry : mySubTopicEntries) {
182 118754 : for (const auto& value : subtopicEntry.second) {
183 : const auto l = myValues.find(value);
184 112937 : 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 355549398 : return valuesFinder->second;
197 : }
198 :
199 :
200 : std::string
201 15361765 : OptionsCont::getValueString(const std::string& name) const {
202 15361765 : Option* o = getSecure(name);
203 15361765 : return o->getValueString();
204 : }
205 :
206 :
207 : std::string
208 31560672 : OptionsCont::getString(const std::string& name) const {
209 31560672 : Option* o = getSecure(name);
210 31560672 : return o->getString();
211 : }
212 :
213 :
214 : double
215 116322234 : OptionsCont::getFloat(const std::string& name) const {
216 116322234 : Option* o = getSecure(name);
217 116322234 : return o->getFloat();
218 : }
219 :
220 :
221 : int
222 2161780 : OptionsCont::getInt(const std::string& name) const {
223 2161780 : Option* o = getSecure(name);
224 2161780 : return o->getInt();
225 : }
226 :
227 :
228 : bool
229 130019477 : OptionsCont::getBool(const std::string& name) const {
230 130019477 : Option* o = getSecure(name);
231 130019477 : 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 580922 : OptionsCont::getStringVector(const std::string& name) const {
243 580922 : Option* o = getSecure(name);
244 580922 : return o->getStringVector();
245 : }
246 :
247 :
248 : bool
249 1041678 : OptionsCont::set(const std::string& name, const std::string& value, const bool append) {
250 1041678 : Option* o = getSecure(name);
251 1041660 : 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 2083302 : 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 156966 : OptionsCont::setDefault(const std::string& name, const std::string& value) {
270 156966 : Option* const o = getSecure(name);
271 156966 : if (o->isWriteable() && set(name, value)) {
272 137621 : o->resetDefault();
273 137621 : 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 46154 : OptionsCont::getSynonymes(const std::string& name) const {
293 46154 : Option* o = getSecure(name);
294 : std::vector<std::string> synonymes;
295 21160332 : for (const auto& value : myValues) {
296 21114178 : if ((value.second == o) && (name != value.first)) {
297 13281 : synonymes.push_back(value.first);
298 : }
299 : }
300 46154 : 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 13066 : OptionsCont::relocateFiles(const std::string& configuration) const {
351 5866161 : for (const auto& addresse : myAddresses) {
352 5853095 : if (addresse.second->isFileName() && addresse.second->isSet()) {
353 77888 : StringVector fileList = StringVector(addresse.second->getStringVector());
354 166933 : for (auto& file : fileList) {
355 89045 : if (addresse.first != "configuration-file") {
356 151958 : file = FileHelpers::checkForRelativity(file, configuration);
357 : }
358 : try {
359 178088 : 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 233664 : StringVector rawList = StringTokenizer(addresse.second->getValueString(), ",").getVector();
365 166933 : for (auto& file : rawList) {
366 178090 : file = FileHelpers::checkForRelativity(file, configuration);
367 : }
368 77888 : const std::string conv = joinToString(fileList, ',');
369 155776 : 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 77888 : }
377 : }
378 13066 : }
379 :
380 :
381 : bool
382 83914 : OptionsCont::isUsableFileList(const std::string& name) const {
383 83914 : Option* const o = getSecure(name);
384 83914 : if (!o->isSet()) {
385 : return false;
386 : }
387 : // check whether the list of files is valid
388 : bool ok = true;
389 75810 : std::vector<std::string> files = getStringVector(name);
390 75810 : if (files.size() == 0) {
391 0 : WRITE_ERRORF(TL("The file list for '%' is empty."), name);
392 : ok = false;
393 : }
394 170872 : for (const auto& file : files) {
395 190124 : if (!FileHelpers::isReadable(file)) {
396 30 : if (file != "") {
397 90 : 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 75810 : }
406 :
407 :
408 : bool
409 6135 : OptionsCont::checkDependingSuboptions(const std::string& name, const std::string& prefix) const {
410 6135 : Option* o = getSecure(name);
411 6135 : if (o->isSet()) {
412 : return true;
413 : }
414 : bool ok = true;
415 : std::vector<std::string> seenSynonymes;
416 2971890 : for (const auto& value : myValues) {
417 2965775 : if (std::find(seenSynonymes.begin(), seenSynonymes.end(), value.first) != seenSynonymes.end()) {
418 1 : continue;
419 : }
420 3062536 : 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 6115 : }
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 1345940 : OptionsCont::convertChar(char abbr) const {
450 : char buf[2];
451 1345940 : buf[0] = abbr;
452 1345940 : buf[1] = 0;
453 1345940 : std::string s(buf);
454 1345940 : return s;
455 : }
456 :
457 :
458 : bool
459 700360 : OptionsCont::isBool(const std::string& name) const {
460 700360 : Option* o = getSecure(name);
461 700330 : return o->isBool();
462 : }
463 :
464 :
465 : void
466 64075 : OptionsCont::resetWritable() {
467 26580564 : for (const auto& addresse : myAddresses) {
468 26516489 : addresse.second->resetWritable();
469 : }
470 64075 : }
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 74681 : OptionsCont::isWriteable(const std::string& name) {
489 74681 : return getSecure(name)->isWriteable();
490 : }
491 :
492 :
493 : bool
494 0 : OptionsCont::isEditable(const std::string& name) {
495 0 : return getSecure(name)->isEditable();
496 :
497 : }
498 :
499 :
500 : void
501 187954 : OptionsCont::clear() {
502 : // delete only address (because synonyms placed in values aim to the same Option)
503 21066334 : for (const auto& addresse : myAddresses) {
504 20878380 : delete addresse.second;
505 : }
506 : myAddresses.clear();
507 : myValues.clear();
508 : mySubTopics.clear();
509 : mySubTopicEntries.clear();
510 187954 : }
511 :
512 :
513 : void
514 20774751 : OptionsCont::addDescription(const std::string& name, const std::string& subtopic,
515 : const std::string& description) {
516 20774751 : Option* o = getSecure(name);
517 20774751 : if (o == nullptr) {
518 0 : throw ProcessError("Option doesn't exist");
519 : }
520 20774751 : if (find(mySubTopics.begin(), mySubTopics.end(), subtopic) == mySubTopics.end()) {
521 0 : throw ProcessError("SubTopic '" + subtopic + "' doesn't exist");
522 : }
523 20774751 : o->setDescription(description);
524 20774751 : o->setSubtopic(subtopic);
525 20774751 : mySubTopicEntries[subtopic].push_back(name);
526 20774751 : }
527 :
528 :
529 : void
530 0 : OptionsCont::setFurtherAttributes(const std::string& name, const std::string& subtopic, bool required, bool positional, const std::string& listSep) {
531 0 : Option* o = getSecure(name);
532 0 : if (o == nullptr) {
533 0 : throw ProcessError("Option doesn't exist");
534 : }
535 0 : if (find(mySubTopics.begin(), mySubTopics.end(), subtopic) == mySubTopics.end()) {
536 0 : throw ProcessError("SubTopic '" + subtopic + "' doesn't exist");
537 : }
538 0 : if (required) {
539 0 : o->setRequired();
540 : }
541 0 : if (positional) {
542 0 : o->setPositional();
543 : }
544 0 : o->setListSeparator(listSep);
545 0 : }
546 :
547 :
548 : void
549 0 : OptionsCont::setOptionEditable(const std::string& name, const bool value) {
550 0 : getSecure(name)->setEditable(value);
551 0 : }
552 :
553 :
554 : void
555 50699 : OptionsCont::setApplicationName(const std::string& appName, const std::string& fullName) {
556 50699 : myAppName = appName;
557 50699 : myFullName = fullName;
558 50699 : }
559 :
560 :
561 : void
562 100976 : OptionsCont::setApplicationDescription(const std::string& appDesc) {
563 100976 : myAppDescription = appDesc;
564 100976 : }
565 :
566 :
567 : void
568 136218 : OptionsCont::addCallExample(const std::string& example, const std::string& desc) {
569 136218 : myCallExamples.push_back(std::make_pair(example, desc));
570 136218 : }
571 :
572 :
573 : void
574 120 : OptionsCont::setAdditionalHelpMessage(const std::string& add) {
575 120 : myAdditionalMessage = add;
576 120 : }
577 :
578 :
579 : void
580 14 : OptionsCont::addCopyrightNotice(const std::string& copyrightLine) {
581 14 : myCopyrightNotices.push_back(copyrightLine);
582 14 : }
583 :
584 :
585 : void
586 0 : OptionsCont::clearCopyrightNotices() {
587 : myCopyrightNotices.clear();
588 0 : }
589 :
590 :
591 : void
592 1228183 : OptionsCont::addOptionSubTopic(const std::string& topic) {
593 1228183 : mySubTopics.push_back(topic);
594 1228183 : mySubTopicEntries[topic] = std::vector<std::string>();
595 1228183 : }
596 :
597 :
598 : void
599 20682 : OptionsCont::splitLines(std::ostream& os, std::string what,
600 : int offset, int nextOffset) {
601 64098 : while (what.length() > 0) {
602 43416 : if ((int)what.length() > 79 - offset) {
603 22768 : std::string::size_type splitPos = what.rfind(';', 79 - offset);
604 22768 : if (splitPos == std::string::npos) {
605 22498 : splitPos = what.rfind(' ', 79 - offset);
606 : } else {
607 270 : splitPos++;
608 : }
609 22768 : if (splitPos != std::string::npos) {
610 22734 : os << what.substr(0, splitPos) << std::endl;
611 22734 : what = what.substr(splitPos + 1);
612 925880 : for (int r = 0; r < (nextOffset + 1); ++r) {
613 903146 : os << ' ';
614 : }
615 : } else {
616 : os << what;
617 : what = "";
618 : }
619 : offset = nextOffset;
620 : } else {
621 : os << what;
622 : what = "";
623 : }
624 : }
625 : os << std::endl;
626 20682 : }
627 :
628 :
629 : bool
630 50923 : OptionsCont::processMetaOptions(bool missingOptions) {
631 50923 : MsgHandler::setupI18n(getString("language"));
632 50923 : localizeDescriptions();
633 50923 : if (missingOptions) {
634 : // no options are given
635 : std::cout << myFullName << std::endl;
636 76 : std::cout << TL(" Build features: ") << HAVE_ENABLED << std::endl;
637 153 : for (const auto& copyrightNotice : myCopyrightNotices) {
638 77 : std::cout << " " << copyrightNotice.data() << std::endl;
639 : }
640 76 : std::cout << TL(" License EPL-2.0: Eclipse Public License Version 2 <https://eclipse.org/legal/epl-v20.html>") << std::endl;
641 76 : std::cout << TL(" Use --help to get the list of options.") << std::endl;
642 76 : return true;
643 : }
644 :
645 : // check whether the help shall be printed
646 101694 : if (getBool("help")) {
647 : std::cout << myFullName << std::endl;
648 155 : for (const auto& copyrightNotice : myCopyrightNotices) {
649 78 : std::cout << " " << copyrightNotice.data() << std::endl;
650 : }
651 77 : printHelp(std::cout);
652 77 : return true;
653 : }
654 : // check whether the help shall be printed
655 101540 : if (getBool("version")) {
656 : std::cout << myFullName << std::endl;
657 13 : std::cout << TL(" Build features: ") << HAVE_ENABLED << std::endl;
658 27 : for (const auto& copyrightNotice : myCopyrightNotices) {
659 14 : std::cout << " " << copyrightNotice.data() << std::endl;
660 : }
661 13 : std::cout << "\n" << myFullName << " is part of SUMO.\n";
662 13 : std::cout << "This program and the accompanying materials\n";
663 13 : std::cout << "are made available under the terms of the Eclipse Public License v2.0\n";
664 13 : std::cout << "which accompanies this distribution, and is available at\n";
665 13 : std::cout << "http://www.eclipse.org/legal/epl-v20.html\n";
666 13 : std::cout << "This program may also be made available under the following Secondary\n";
667 13 : std::cout << "Licenses when the conditions for such availability set forth in the Eclipse\n";
668 13 : std::cout << "Public License 2.0 are satisfied: GNU General Public License, version 2\n";
669 13 : std::cout << "or later which is available at\n";
670 13 : std::cout << "https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html\n";
671 : std::cout << "SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later" << std::endl;
672 13 : return true;
673 : }
674 : // check whether the settings shall be printed
675 101514 : if (getBool("print-options")) {
676 0 : std::cout << (*this);
677 : }
678 : // check whether something has to be done with options
679 : // whether the current options shall be saved
680 101514 : if (isSet("save-configuration")) {
681 509 : const std::string& configPath = getString("save-configuration");
682 509 : if (configPath == "-" || configPath == "stdout") {
683 24 : writeConfiguration(std::cout, true, false, getBool("save-commented"));
684 12 : return true;
685 : }
686 994 : std::ofstream out(StringUtils::transcodeToLocal(configPath).c_str());
687 497 : if (!out.good()) {
688 0 : throw ProcessError(TLF("Could not save configuration to '%'", configPath));
689 : } else {
690 994 : writeConfiguration(out, true, false, getBool("save-commented"), configPath);
691 994 : if (getBool("verbose")) {
692 876 : WRITE_MESSAGEF(TL("Written configuration to '%'"), configPath);
693 : }
694 : return true;
695 : }
696 497 : }
697 : // whether the template shall be saved
698 100496 : if (isSet("save-template")) {
699 65 : if (getString("save-template") == "-" || getString("save-template") == "stdout") {
700 10 : writeConfiguration(std::cout, false, true, getBool("save-commented"));
701 5 : return true;
702 : }
703 36 : std::ofstream out(StringUtils::transcodeToLocal(getString("save-template")).c_str());
704 18 : if (!out.good()) {
705 0 : throw ProcessError(TLF("Could not save template to '%'", getString("save-template")));
706 : } else {
707 36 : writeConfiguration(out, false, true, getBool("save-commented"));
708 36 : if (getBool("verbose")) {
709 0 : WRITE_MESSAGEF(TL("Written template to '%'"), getString("save-template"));
710 : }
711 : return true;
712 : }
713 18 : }
714 100450 : if (isSet("save-schema")) {
715 3 : if (getString("save-schema") == "-" || getString("save-schema") == "stdout") {
716 0 : writeSchema(std::cout);
717 0 : return true;
718 : }
719 2 : std::ofstream out(StringUtils::transcodeToLocal(getString("save-schema")).c_str());
720 1 : if (!out.good()) {
721 0 : throw ProcessError(TLF("Could not save schema to '%'", getString("save-schema")));
722 : } else {
723 1 : writeSchema(out);
724 2 : if (getBool("verbose")) {
725 0 : WRITE_MESSAGEF(TL("Written schema to '%'"), getString("save-schema"));
726 : }
727 : return true;
728 : }
729 1 : }
730 : return false;
731 : }
732 :
733 :
734 : void
735 50923 : OptionsCont::localizeDescriptions() {
736 50923 : if (!myAmLocalized && gLocaleInitialized) {
737 : // options
738 20663312 : for (auto option : myAddresses) {
739 41224868 : option.second->setDescription(TL(option.second->getDescription().c_str()));
740 : }
741 : // examples
742 186025 : for (auto example : myCallExamples) {
743 135147 : example.second = TL(example.second.c_str());
744 : }
745 : // other text
746 50878 : setApplicationDescription(TL(myAppDescription.c_str()));
747 50878 : myAmLocalized = true;
748 : }
749 50923 : }
750 :
751 :
752 : const std::vector<std::string>&
753 0 : OptionsCont::getSubTopics() const {
754 0 : return mySubTopics;
755 : }
756 :
757 :
758 : std::vector<std::string>
759 0 : OptionsCont::getSubTopicsEntries(const std::string& subtopic) const {
760 : if (mySubTopicEntries.count(subtopic) > 0) {
761 0 : return mySubTopicEntries.find(subtopic)->second;
762 : } else {
763 0 : return std::vector<std::string>();
764 : }
765 : }
766 :
767 :
768 : std::string
769 0 : OptionsCont::getTypeName(const std::string name) {
770 0 : return getSecure(name)->getTypeName();
771 : }
772 :
773 :
774 : const std::string&
775 1 : OptionsCont::getFullName() const {
776 1 : return myFullName;
777 : }
778 :
779 :
780 : bool
781 0 : OptionsCont::isEmpty() const {
782 0 : return myAddresses.size() == 0;
783 : }
784 :
785 :
786 : std::vector<std::pair<std::string, Option*> >::const_iterator
787 0 : OptionsCont::begin() const {
788 0 : return myAddresses.cbegin();
789 : }
790 :
791 :
792 : std::vector<std::pair<std::string, Option*> >::const_iterator
793 0 : OptionsCont::end() const {
794 0 : return myAddresses.cend();
795 : }
796 :
797 :
798 : void
799 77 : OptionsCont::printHelp(std::ostream& os) {
800 : // print application description
801 154 : splitLines(os, TL(myAppDescription.c_str()), 0, 0);
802 : os << std::endl;
803 :
804 : // check option sizes first
805 : // we want to know how large the largest not-too-large-entry will be
806 : int tooLarge = 40;
807 : int maxSize = 0;
808 1336 : for (const auto& subTopic : mySubTopics) {
809 21864 : for (const auto& entry : mySubTopicEntries[subTopic]) {
810 20605 : Option* o = getSecure(entry);
811 : // name, two leading spaces and "--"
812 20605 : int csize = (int)entry.length() + 2 + 4;
813 : // abbreviation length ("-X, "->4chars) if any
814 20605 : const auto synonymes = getSynonymes(entry);
815 23758 : for (const auto& synonym : synonymes) {
816 4868 : if (synonym.length() == 1 && myDeprecatedSynonymes.count(synonym) == 0) {
817 1715 : csize += 4;
818 1715 : break;
819 : }
820 : }
821 : // the type name
822 20605 : if (!o->isBool()) {
823 14208 : csize += 1 + (int)o->getTypeName().length();
824 : }
825 : // divider
826 20605 : csize += 2;
827 20605 : if (csize < tooLarge && maxSize < csize) {
828 : maxSize = csize;
829 : }
830 20605 : }
831 : }
832 :
833 154 : const std::string helpTopic = StringUtils::to_lower_case(getSecure("help")->getValueString());
834 77 : if (helpTopic != "") {
835 : bool foundTopic = false;
836 0 : for (const auto& topic : mySubTopics) {
837 0 : if (StringUtils::to_lower_case(topic).find(helpTopic) != std::string::npos) {
838 : foundTopic = true;
839 0 : printHelpOnTopic(topic, tooLarge, maxSize, os);
840 : }
841 : }
842 0 : if (!foundTopic) {
843 : // print topic list
844 0 : os << TL("Help Topics:") << std::endl;
845 0 : for (const std::string& t : mySubTopics) {
846 : os << " " << t << std::endl;
847 : }
848 : }
849 : return;
850 : }
851 : // print usage BNF
852 154 : os << TL("Usage: ") << myAppName << TL(" [OPTION]*") << std::endl;
853 : // print additional text if any
854 77 : if (myAdditionalMessage.length() > 0) {
855 : os << myAdditionalMessage << std::endl << std::endl;
856 : }
857 : // print the options
858 1336 : for (const auto& subTopic : mySubTopics) {
859 1259 : printHelpOnTopic(subTopic, tooLarge, maxSize, os);
860 : }
861 : os << std::endl;
862 : // print usage examples, calc size first
863 77 : if (myCallExamples.size() != 0) {
864 77 : os << TL("Examples:") << std::endl;
865 225 : for (const auto& callExample : myCallExamples) {
866 148 : os << " " << myAppName << ' ' << callExample.first << std::endl;
867 : os << " " << callExample.second << std::endl;
868 : }
869 : }
870 : os << std::endl;
871 77 : os << TLF("Report bugs at %.", "<https://github.com/eclipse-sumo/sumo/issues>") << std::endl;
872 154 : os << TLF("Get in contact via %.", "<sumo@dlr.de>") << std::endl;
873 : }
874 :
875 :
876 : void
877 1259 : OptionsCont::printHelpOnTopic(const std::string& topic, int tooLarge, int maxSize, std::ostream& os) {
878 2518 : os << TLF("% Options:", topic) << std::endl;
879 21864 : for (const auto& entry : mySubTopicEntries[topic]) {
880 : // start length computation
881 20605 : int csize = (int)entry.length() + 2;
882 20605 : Option* o = getSecure(entry);
883 20605 : os << " ";
884 : // write abbreviation if given
885 20605 : const auto synonymes = getSynonymes(entry);
886 23758 : for (const auto& synonym : synonymes) {
887 4868 : if (synonym.length() == 1 && myDeprecatedSynonymes.count(synonym) == 0) {
888 3430 : os << '-' << synonym << ", ";
889 1715 : csize += 4;
890 1715 : break;
891 : }
892 : }
893 : // write leading '-'/"--"
894 20605 : os << "--";
895 20605 : csize += 2;
896 : // write the name
897 : os << entry;
898 : // write the type if not a bool option
899 20605 : if (!o->isBool()) {
900 14208 : os << ' ' << o->getTypeName();
901 14208 : csize += 1 + (int)o->getTypeName().length();
902 : }
903 20605 : csize += 2;
904 : // write the description formatting it
905 20605 : os << " ";
906 206928 : for (int r = maxSize; r > csize; --r) {
907 186323 : os << ' ';
908 : }
909 20605 : int offset = csize > tooLarge ? csize : maxSize;
910 41210 : splitLines(os, o->getDescription(), offset, maxSize);
911 20605 : }
912 : os << std::endl;
913 1259 : }
914 :
915 :
916 : void
917 86117 : OptionsCont::writeConfiguration(std::ostream& os, const bool filled,
918 : const bool complete, const bool addComments, const std::string& relativeTo,
919 : const bool forceRelative, const bool inComment, const std::string& indent) const {
920 86117 : if (!inComment) {
921 770 : writeXMLHeader(os, false);
922 : }
923 163755 : const std::string& app = myAppName == "sumo-gui" ? "sumo" : myAppName;
924 : os << indent << "<" << app << "Configuration xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
925 86117 : << "xsi:noNamespaceSchemaLocation=\"http://sumo.dlr.de/xsd/" << app << "Configuration.xsd\">\n\n";
926 2268676 : for (std::string subtopic : mySubTopics) {
927 2182559 : if (subtopic == "Configuration" && !complete) {
928 : continue;
929 : }
930 : const std::vector<std::string>& entries = mySubTopicEntries.find(subtopic)->second;
931 : std::replace(subtopic.begin(), subtopic.end(), ' ', '_');
932 4192930 : subtopic = StringUtils::to_lower_case(subtopic);
933 : bool hadOne = false;
934 38712689 : for (const std::string& name : entries) {
935 36616224 : Option* o = getSecure(name);
936 36616224 : bool write = complete || (filled && !o->isDefault());
937 35907903 : if (!write) {
938 35907903 : continue;
939 : }
940 708321 : if (name == "registry-viewport" && !complete) {
941 0 : continue;
942 : }
943 708321 : if (!hadOne) {
944 298332 : os << indent << " <" << subtopic << ">\n";
945 : }
946 : // add the comment if wished
947 708321 : if (addComments) {
948 4569 : os << indent << " <!-- " << StringUtils::escapeXML(o->getDescription(), inComment) << " -->\n";
949 : }
950 : // write the option and the value (if given)
951 708321 : os << indent << " <" << name << " value=\"";
952 708321 : if (o->isSet() && (filled || o->isDefault())) {
953 707166 : if (o->isFileName() && relativeTo != "") {
954 7542 : StringVector fileList = StringTokenizer(o->getValueString(), ",").getVector();
955 5083 : for (auto& file : fileList) {
956 5138 : if (StringUtils::startsWith(file, "${")) {
957 : // there is an environment variable up front, assume it points to an absolute path
958 : // not even forcing relativity makes sense here
959 34 : file = StringUtils::urlEncode(file, " ;%");
960 : } else {
961 7656 : file = FileHelpers::fixRelative(
962 5104 : StringUtils::urlEncode(file, " ;%"),
963 2552 : StringUtils::urlEncode(relativeTo, " ;%"),
964 10208 : forceRelative || getBool("save-configuration.relative"));
965 : }
966 : }
967 2514 : os << StringUtils::escapeXML(joinToString(fileList, ','), inComment);
968 2514 : } else {
969 1409304 : os << StringUtils::escapeXML(o->getValueString(), inComment);
970 : }
971 : }
972 708321 : if (complete) {
973 4943 : const std::vector<std::string> synonymes = getSynonymes(name);
974 4943 : if (!synonymes.empty()) {
975 2574 : os << "\" synonymes=\"" << toString(synonymes);
976 : }
977 : std::string deprecated;
978 6757 : for (const auto& synonym : synonymes) {
979 : if (myDeprecatedSynonymes.count(synonym) > 0) {
980 1554 : deprecated += " " + synonym;
981 : }
982 : }
983 4943 : if (deprecated != "") {
984 1398 : os << "\" deprecated=\"" << deprecated.substr(1);
985 : }
986 4943 : os << "\" type=\"" << o->getTypeName();
987 4943 : if (!addComments) {
988 6840 : os << "\" help=\"" << StringUtils::escapeXML(o->getDescription());
989 : }
990 4943 : }
991 708321 : os << "\"/>\n";
992 : // append an endline if a comment was printed
993 708321 : if (addComments) {
994 1523 : os << "\n";
995 : }
996 : hadOne = true;
997 : }
998 2096465 : if (hadOne) {
999 298332 : os << indent << " </" << subtopic << ">\n\n";
1000 : }
1001 : }
1002 : os << indent << "</" << app << "Configuration>" << std::endl; // flushing seems like a good idea here
1003 86117 : }
1004 :
1005 :
1006 : void
1007 1 : OptionsCont::writeSchema(std::ostream& os) {
1008 1 : const std::string& app = myAppName == "sumo-gui" ? "sumo" : myAppName;
1009 1 : writeXMLHeader(os, false);
1010 1 : os << "<xsd:schema elementFormDefault=\"qualified\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">\n";
1011 1 : os << " <xsd:complexType name=\"" << app << "ConfigurationType\">\n";
1012 1 : os << " <xsd:all>\n";
1013 28 : for (std::string subtopic : mySubTopics) {
1014 27 : if (subtopic == "Configuration") {
1015 : continue;
1016 : }
1017 : std::replace(subtopic.begin(), subtopic.end(), ' ', '_');
1018 52 : subtopic = StringUtils::to_lower_case(subtopic);
1019 26 : os << " <xsd:element name=\"" << subtopic << "\" type=\"" << app << subtopic << "TopicType\" minOccurs=\"0\"/>\n";
1020 : }
1021 1 : os << " </xsd:all>\n";
1022 1 : os << " </xsd:complexType>\n\n";
1023 28 : for (std::string subtopic : mySubTopics) {
1024 27 : if (subtopic == "Configuration") {
1025 : continue;
1026 : }
1027 : const std::vector<std::string>& entries = mySubTopicEntries.find(subtopic)->second;
1028 : std::replace(subtopic.begin(), subtopic.end(), ' ', '_');
1029 52 : subtopic = StringUtils::to_lower_case(subtopic);
1030 26 : os << " <xsd:complexType name=\"" << app << subtopic << "TopicType\">\n";
1031 26 : os << " <xsd:all>\n";
1032 478 : for (const auto& entry : entries) {
1033 452 : Option* o = getSecure(entry);
1034 452 : std::string type = o->getTypeName();
1035 452 : type = StringUtils::to_lower_case(type);
1036 452 : if (type == "int[]") {
1037 : type = "intArray";
1038 : }
1039 452 : if (type == "str[]") {
1040 : type = "strArray";
1041 : }
1042 452 : os << " <xsd:element name=\"" << entry << "\" type=\"" << type << "OptionType\" minOccurs=\"0\"/>\n";
1043 : }
1044 26 : os << " </xsd:all>\n";
1045 26 : os << " </xsd:complexType>\n\n";
1046 : }
1047 1 : os << "</xsd:schema>\n";
1048 1 : }
1049 :
1050 :
1051 : void
1052 86118 : OptionsCont::writeXMLHeader(std::ostream& os, const bool includeConfig) const {
1053 86118 : os << "<?xml version=\"1.0\"" << SUMOSAXAttributes::ENCODING << "?>\n\n";
1054 86118 : os << "<!-- ";
1055 172236 : if (!getBool("write-metadata")) {
1056 258351 : os << "generated on " << StringUtils::isoTimeString() << " by " << myFullName << "\n";
1057 : }
1058 172236 : if (getBool("write-license")) {
1059 : os << "This data file and the accompanying materials\n"
1060 : "are made available under the terms of the Eclipse Public License v2.0\n"
1061 : "which accompanies this distribution, and is available at\n"
1062 : "http://www.eclipse.org/legal/epl-v20.html\n"
1063 : "This file may also be made available under the following Secondary\n"
1064 : "Licenses when the conditions for such availability set forth in the Eclipse\n"
1065 : "Public License 2.0 are satisfied: GNU General Public License, version 2\n"
1066 : "or later which is available at\n"
1067 : "https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html\n"
1068 34852 : "SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later\n";
1069 : }
1070 171466 : if (includeConfig && !getBool("write-metadata")) {
1071 170692 : writeConfiguration(os, true, false, false, "", false, true);
1072 : }
1073 86118 : os << "-->\n\n";
1074 86118 : }
1075 :
1076 :
1077 : bool
1078 40149 : OptionsCont::isInStringVector(const std::string& optionName,
1079 : const std::string& itemName) const {
1080 40149 : if (isSet(optionName)) {
1081 0 : std::vector<std::string> values = getStringVector(optionName);
1082 0 : return std::find(values.begin(), values.end(), itemName) != values.end();
1083 0 : }
1084 : return false;
1085 : }
1086 :
1087 :
1088 : OptionsCont*
1089 172 : OptionsCont::clone() const {
1090 : // build a clone to call writeConfiguration on
1091 : // (with the possibility of changing a few settings and not affecting the original)
1092 172 : OptionsCont* oc = new OptionsCont(*this);
1093 172 : oc->resetWritable();
1094 62264 : for (auto& addr : oc->myAddresses) {
1095 62092 : addr.second = addr.second->clone();
1096 : }
1097 172 : return oc;
1098 : }
1099 :
1100 :
1101 : /****************************************************************************/
|