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 943819322 : OptionsCont::getOptions() {
62 943819322 : return myOptions;
63 : }
64 :
65 :
66 100980 : OptionsCont::OptionsCont() {
67 100980 : myCopyrightNotices.push_back(TL("Copyright (C) 2001-2025 German Aerospace Center (DLR) and others; https://sumo.dlr.de"));
68 100980 : }
69 :
70 :
71 101152 : OptionsCont::~OptionsCont() {
72 101152 : clear();
73 202304 : }
74 :
75 :
76 : void
77 25097404 : OptionsCont::doRegister(const std::string& name, Option* o) {
78 : // first check that option isn't null
79 25097404 : 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 25097404 : 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 5384413016 : for (const auto& addresse : myAddresses) {
89 5359315612 : if (addresse.second == o) {
90 : isSynonym = true;
91 : }
92 : }
93 25097404 : if (!isSynonym) {
94 40838086 : myAddresses.push_back(std::make_pair(name, o));
95 : }
96 : // insert in values
97 25097404 : myValues[name] = o;
98 25097404 : }
99 :
100 :
101 : void
102 1326340 : OptionsCont::doRegister(const std::string& name1, char abbr, Option* o) {
103 1326340 : doRegister(name1, o);
104 1326340 : doRegister(convertChar(abbr), o);
105 1326340 : }
106 :
107 :
108 : void
109 3352021 : 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 3352021 : if (i1 == myValues.end() && i2 == myValues.end()) {
113 0 : throw ProcessError("Neither the option '" + name1 + "' nor the option '" + name2 + "' is known yet");
114 : }
115 3352021 : 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 3352021 : if (i1 == myValues.end() && i2 != myValues.end()) {
122 108987 : doRegister(name1, (*i2).second);
123 108987 : if (isDeprecated) {
124 0 : myDeprecatedSynonymes[name1] = false;
125 : }
126 : }
127 3352021 : if (i1 != myValues.end() && i2 == myValues.end()) {
128 3243034 : doRegister(name2, (*i1).second);
129 3243034 : if (isDeprecated) {
130 1739068 : myDeprecatedSynonymes[name2] = false;
131 : }
132 : }
133 : }
134 :
135 :
136 : void
137 94151 : OptionsCont::addXMLDefault(const std::string& name, const std::string& xmlRoot) {
138 94151 : myXMLDefaults[xmlRoot] = name;
139 94151 : }
140 :
141 :
142 : bool
143 362577129 : OptionsCont::exists(const std::string& name) const {
144 362577129 : return myValues.count(name) > 0;
145 : }
146 :
147 :
148 : bool
149 992567420 : OptionsCont::isSet(const std::string& name, bool failOnNonExistant) const {
150 : auto i = myValues.find(name);
151 992567420 : if (i == myValues.end()) {
152 201 : if (failOnNonExistant) {
153 0 : throw ProcessError(TLF("Internal request for unknown option '%'!", name));
154 : } else {
155 : return false;
156 : }
157 : }
158 992567219 : return (*i).second->isSet();
159 : }
160 :
161 :
162 : bool
163 2203121 : OptionsCont::isDefault(const std::string& name) const {
164 : auto i = myValues.find(name);
165 2203121 : if (i == myValues.end()) {
166 : return false;
167 : }
168 2202673 : return (*i).second->isDefault();
169 : }
170 :
171 :
172 : Option*
173 343388558 : OptionsCont::getSecure(const std::string& name) const {
174 : const auto& valuesFinder = myValues.find(name);
175 343388558 : 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 343388504 : if ((synonymFinder != myDeprecatedSynonymes.end()) && !synonymFinder->second) {
180 : std::string defaultName;
181 7018 : for (const auto& subtopicEntry : mySubTopicEntries) {
182 118752 : for (const auto& value : subtopicEntry.second) {
183 : const auto l = myValues.find(value);
184 112935 : 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 343388504 : return valuesFinder->second;
197 : }
198 :
199 :
200 : std::string
201 14998631 : OptionsCont::getValueString(const std::string& name) const {
202 14998631 : Option* o = getSecure(name);
203 14998631 : return o->getValueString();
204 : }
205 :
206 :
207 : std::string
208 29499257 : OptionsCont::getString(const std::string& name) const {
209 29499257 : Option* o = getSecure(name);
210 29499257 : return o->getString();
211 : }
212 :
213 :
214 : double
215 112522736 : OptionsCont::getFloat(const std::string& name) const {
216 112522736 : Option* o = getSecure(name);
217 112522736 : return o->getFloat();
218 : }
219 :
220 :
221 : int
222 2123182 : OptionsCont::getInt(const std::string& name) const {
223 2123182 : Option* o = getSecure(name);
224 2123182 : return o->getInt();
225 : }
226 :
227 :
228 : bool
229 125514353 : OptionsCont::getBool(const std::string& name) const {
230 125514353 : Option* o = getSecure(name);
231 125514353 : 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 522233 : OptionsCont::getStringVector(const std::string& name) const {
243 522233 : Option* o = getSecure(name);
244 522233 : return o->getStringVector();
245 : }
246 :
247 :
248 : bool
249 1020728 : OptionsCont::set(const std::string& name, const std::string& value, const bool append) {
250 1020728 : Option* o = getSecure(name);
251 1020710 : 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 2041402 : 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 153911 : OptionsCont::setDefault(const std::string& name, const std::string& value) {
270 153911 : Option* const o = getSecure(name);
271 153911 : if (o->isWriteable() && set(name, value)) {
272 134919 : o->resetDefault();
273 134919 : 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 46016 : OptionsCont::getSynonymes(const std::string& name) const {
293 46016 : Option* o = getSecure(name);
294 : std::vector<std::string> synonymes;
295 20988798 : for (const auto& value : myValues) {
296 20942782 : if ((value.second == o) && (name != value.first)) {
297 13212 : synonymes.push_back(value.first);
298 : }
299 : }
300 46016 : 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 12879 : OptionsCont::relocateFiles(const std::string& configuration) const {
351 5755404 : for (const auto& addresse : myAddresses) {
352 5742525 : if (addresse.second->isFileName() && addresse.second->isSet()) {
353 76798 : StringVector fileList = StringVector(addresse.second->getStringVector());
354 164559 : for (auto& file : fileList) {
355 87761 : if (addresse.first != "configuration-file") {
356 149764 : file = FileHelpers::checkForRelativity(file, configuration);
357 : }
358 : try {
359 175520 : 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 230394 : StringVector rawList = StringTokenizer(addresse.second->getValueString(), ",").getVector();
365 164559 : for (auto& file : rawList) {
366 175522 : file = FileHelpers::checkForRelativity(file, configuration);
367 : }
368 76798 : const std::string conv = joinToString(fileList, ',');
369 153596 : 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 76798 : }
377 : }
378 12879 : }
379 :
380 :
381 : bool
382 79122 : OptionsCont::isUsableFileList(const std::string& name) const {
383 79122 : Option* const o = getSecure(name);
384 79122 : if (!o->isSet()) {
385 : return false;
386 : }
387 : // check whether the list of files is valid
388 : bool ok = true;
389 71115 : std::vector<std::string> files = getStringVector(name);
390 71115 : if (files.size() == 0) {
391 0 : WRITE_ERRORF(TL("The file list for '%' is empty."), name);
392 : ok = false;
393 : }
394 159488 : for (const auto& file : files) {
395 176746 : 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 71115 : }
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 2931552 : for (const auto& value : myValues) {
417 2925520 : if (std::find(seenSynonymes.begin(), seenSynonymes.end(), value.first) != seenSynonymes.end()) {
418 1 : continue;
419 : }
420 3020784 : 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 1326340 : OptionsCont::convertChar(char abbr) const {
450 : char buf[2];
451 1326340 : buf[0] = abbr;
452 1326340 : buf[1] = 0;
453 1326340 : std::string s(buf);
454 1326340 : return s;
455 : }
456 :
457 :
458 : bool
459 685598 : OptionsCont::isBool(const std::string& name) const {
460 685598 : Option* o = getSecure(name);
461 685568 : return o->isBool();
462 : }
463 :
464 :
465 : void
466 62995 : OptionsCont::resetWritable() {
467 25994401 : for (const auto& addresse : myAddresses) {
468 25931406 : addresse.second->resetWritable();
469 : }
470 62995 : }
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 73780 : OptionsCont::isWriteable(const std::string& name) {
489 73780 : 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 185548 : OptionsCont::clear() {
502 : // delete only address (because synonyms placed in values aim to the same Option)
503 20666683 : for (const auto& addresse : myAddresses) {
504 20481135 : delete addresse.second;
505 : }
506 : myAddresses.clear();
507 : myValues.clear();
508 : mySubTopics.clear();
509 : mySubTopicEntries.clear();
510 185548 : }
511 :
512 :
513 : void
514 20378330 : OptionsCont::addDescription(const std::string& name, const std::string& subtopic,
515 : const std::string& description) {
516 20378330 : Option* o = getSecure(name);
517 20378330 : if (o == nullptr) {
518 0 : throw ProcessError("Option doesn't exist");
519 : }
520 20378330 : if (find(mySubTopics.begin(), mySubTopics.end(), subtopic) == mySubTopics.end()) {
521 0 : throw ProcessError("SubTopic '" + subtopic + "' doesn't exist");
522 : }
523 20378330 : o->setDescription(description);
524 20378330 : o->setSubtopic(subtopic);
525 20378330 : mySubTopicEntries[subtopic].push_back(name);
526 20378330 : }
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 49987 : OptionsCont::setApplicationName(const std::string& appName, const std::string& fullName) {
556 49987 : myAppName = appName;
557 49987 : myFullName = fullName;
558 49987 : }
559 :
560 :
561 : void
562 99377 : OptionsCont::setApplicationDescription(const std::string& appDesc) {
563 99377 : myAppDescription = appDesc;
564 99377 : }
565 :
566 :
567 : void
568 134168 : OptionsCont::addCallExample(const std::string& example, const std::string& desc) {
569 134168 : myCallExamples.push_back(std::make_pair(example, desc));
570 134168 : }
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 1209802 : OptionsCont::addOptionSubTopic(const std::string& topic) {
593 1209802 : mySubTopics.push_back(topic);
594 1209802 : mySubTopicEntries[topic] = std::vector<std::string>();
595 1209802 : }
596 :
597 :
598 : void
599 20616 : OptionsCont::splitLines(std::ostream& os, std::string what,
600 : int offset, int nextOffset) {
601 63900 : while (what.length() > 0) {
602 43284 : if ((int)what.length() > 79 - offset) {
603 22702 : std::string::size_type splitPos = what.rfind(';', 79 - offset);
604 22702 : if (splitPos == std::string::npos) {
605 22432 : splitPos = what.rfind(' ', 79 - offset);
606 : } else {
607 270 : splitPos++;
608 : }
609 22702 : if (splitPos != std::string::npos) {
610 22668 : os << what.substr(0, splitPos) << std::endl;
611 22668 : what = what.substr(splitPos + 1);
612 923174 : for (int r = 0; r < (nextOffset + 1); ++r) {
613 900506 : 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 20616 : }
627 :
628 :
629 : bool
630 50030 : OptionsCont::processMetaOptions(bool missingOptions) {
631 50030 : MsgHandler::setupI18n(getString("language"));
632 50030 : localizeDescriptions();
633 50030 : 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 99908 : 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 99754 : 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 99728 : 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 99728 : 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 98710 : 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 98664 : 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 50030 : OptionsCont::localizeDescriptions() {
736 50030 : if (!myAmLocalized && gLocaleInitialized) {
737 : // options
738 20187810 : for (auto option : myAddresses) {
739 40275650 : option.second->setDescription(TL(option.second->getDescription().c_str()));
740 : }
741 : // examples
742 182563 : for (auto example : myCallExamples) {
743 132578 : example.second = TL(example.second.c_str());
744 : }
745 : // other text
746 49985 : setApplicationDescription(TL(myAppDescription.c_str()));
747 49985 : myAmLocalized = true;
748 : }
749 50030 : }
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 21798 : for (const auto& entry : mySubTopicEntries[subTopic]) {
810 20539 : Option* o = getSecure(entry);
811 : // name, two leading spaces and "--"
812 20539 : int csize = (int)entry.length() + 2 + 4;
813 : // abbreviation length ("-X, "->4chars) if any
814 20539 : const auto synonymes = getSynonymes(entry);
815 23659 : for (const auto& synonym : synonymes) {
816 4835 : if (synonym.length() == 1 && myDeprecatedSynonymes.count(synonym) == 0) {
817 1715 : csize += 4;
818 1715 : break;
819 : }
820 : }
821 : // the type name
822 20539 : if (!o->isBool()) {
823 14142 : csize += 1 + (int)o->getTypeName().length();
824 : }
825 : // divider
826 20539 : csize += 2;
827 20539 : if (csize < tooLarge && maxSize < csize) {
828 : maxSize = csize;
829 : }
830 20539 : }
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 21798 : for (const auto& entry : mySubTopicEntries[topic]) {
880 : // start length computation
881 20539 : int csize = (int)entry.length() + 2;
882 20539 : Option* o = getSecure(entry);
883 20539 : os << " ";
884 : // write abbreviation if given
885 20539 : const auto synonymes = getSynonymes(entry);
886 23659 : for (const auto& synonym : synonymes) {
887 4835 : 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 20539 : os << "--";
895 20539 : csize += 2;
896 : // write the name
897 : os << entry;
898 : // write the type if not a bool option
899 20539 : if (!o->isBool()) {
900 14142 : os << ' ' << o->getTypeName();
901 14142 : csize += 1 + (int)o->getTypeName().length();
902 : }
903 20539 : csize += 2;
904 : // write the description formatting it
905 20539 : os << " ";
906 206862 : for (int r = maxSize; r > csize; --r) {
907 186323 : os << ' ';
908 : }
909 20539 : int offset = csize > tooLarge ? csize : maxSize;
910 41078 : splitLines(os, o->getDescription(), offset, maxSize);
911 20539 : }
912 : os << std::endl;
913 1259 : }
914 :
915 :
916 : void
917 84410 : 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 84410 : if (!inComment) {
921 765 : writeXMLHeader(os, false);
922 : }
923 160932 : const std::string& app = myAppName == "sumo-gui" ? "sumo" : myAppName;
924 : os << indent << "<" << app << "Configuration xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
925 84410 : << "xsi:noNamespaceSchemaLocation=\"http://sumo.dlr.de/xsd/" << app << "Configuration.xsd\">\n\n";
926 2222787 : for (std::string subtopic : mySubTopics) {
927 2138377 : 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 4107980 : subtopic = StringUtils::to_lower_case(subtopic);
933 : bool hadOne = false;
934 37777015 : for (const std::string& name : entries) {
935 35723025 : Option* o = getSecure(name);
936 35723025 : bool write = complete || (filled && !o->isDefault());
937 35030916 : if (!write) {
938 35030916 : continue;
939 : }
940 692109 : if (name == "registry-viewport" && !complete) {
941 0 : continue;
942 : }
943 692109 : if (!hadOne) {
944 291669 : os << indent << " <" << subtopic << ">\n";
945 : }
946 : // add the comment if wished
947 692109 : if (addComments) {
948 4563 : os << indent << " <!-- " << StringUtils::escapeXML(o->getDescription(), inComment) << " -->\n";
949 : }
950 : // write the option and the value (if given)
951 692109 : os << indent << " <" << name << " value=\"";
952 692109 : if (o->isSet() && (filled || o->isDefault())) {
953 690954 : if (o->isFileName() && relativeTo != "") {
954 7542 : StringVector fileList = StringTokenizer(o->getValueString(), ",").getVector();
955 5082 : for (auto& file : fileList) {
956 5136 : 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 7653 : file = FileHelpers::fixRelative(
962 5102 : StringUtils::urlEncode(file, " ;%"),
963 2551 : StringUtils::urlEncode(relativeTo, " ;%"),
964 10204 : forceRelative || getBool("save-configuration.relative"));
965 : }
966 : }
967 2514 : os << StringUtils::escapeXML(joinToString(fileList, ','), inComment);
968 2514 : } else {
969 1376880 : os << StringUtils::escapeXML(o->getValueString(), inComment);
970 : }
971 : }
972 692109 : if (complete) {
973 4937 : const std::vector<std::string> synonymes = getSynonymes(name);
974 4937 : if (!synonymes.empty()) {
975 2568 : os << "\" synonymes=\"" << toString(synonymes);
976 : }
977 : std::string deprecated;
978 6748 : for (const auto& synonym : synonymes) {
979 : if (myDeprecatedSynonymes.count(synonym) > 0) {
980 1554 : deprecated += " " + synonym;
981 : }
982 : }
983 4937 : if (deprecated != "") {
984 1398 : os << "\" deprecated=\"" << deprecated.substr(1);
985 : }
986 4937 : os << "\" type=\"" << o->getTypeName();
987 4937 : if (!addComments) {
988 6832 : os << "\" help=\"" << StringUtils::escapeXML(o->getDescription());
989 : }
990 4937 : }
991 692109 : os << "\"/>\n";
992 : // append an endline if a comment was printed
993 692109 : if (addComments) {
994 1521 : os << "\n";
995 : }
996 : hadOne = true;
997 : }
998 2053990 : if (hadOne) {
999 291669 : os << indent << " </" << subtopic << ">\n\n";
1000 : }
1001 : }
1002 : os << indent << "</" << app << "Configuration>" << std::endl; // flushing seems like a good idea here
1003 84410 : }
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 476 : for (const auto& entry : entries) {
1033 450 : Option* o = getSecure(entry);
1034 450 : std::string type = o->getTypeName();
1035 450 : type = StringUtils::to_lower_case(type);
1036 450 : if (type == "int[]") {
1037 : type = "intArray";
1038 : }
1039 450 : if (type == "str[]") {
1040 : type = "strArray";
1041 : }
1042 450 : 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 84411 : OptionsCont::writeXMLHeader(std::ostream& os, const bool includeConfig) const {
1053 84411 : os << "<?xml version=\"1.0\"" << SUMOSAXAttributes::ENCODING << "?>\n\n";
1054 84411 : os << "<!-- ";
1055 168822 : if (!getBool("write-metadata")) {
1056 253230 : os << "generated on " << StringUtils::isoTimeString() << " by " << myFullName << "\n";
1057 : }
1058 168822 : 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 33884 : "SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later\n";
1069 : }
1070 168057 : if (includeConfig && !getBool("write-metadata")) {
1071 167288 : writeConfiguration(os, true, false, false, "", false, true);
1072 : }
1073 84411 : os << "-->\n\n";
1074 84411 : }
1075 :
1076 :
1077 : bool
1078 39926 : OptionsCont::isInStringVector(const std::string& optionName,
1079 : const std::string& itemName) const {
1080 39926 : 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 : /****************************************************************************/
|