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