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 1151792359 : OptionsCont::getOptions() {
63 1151792359 : return myOptions;
64 : }
65 :
66 :
67 109428 : OptionsCont::OptionsCont() {
68 109428 : myCopyrightNotices.push_back(TL("Copyright (C) 2001-2026 German Aerospace Center (DLR) and others; https://sumo.dlr.de"));
69 109428 : }
70 :
71 :
72 109602 : OptionsCont::~OptionsCont() {
73 109602 : clear();
74 219204 : }
75 :
76 :
77 : void
78 27817438 : OptionsCont::doRegister(const std::string& name, Option* o) {
79 : // first check that option isn't null
80 27817438 : 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 27817438 : 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 6075421768 : for (const auto& addresse : myAddresses) {
90 6047604330 : if (addresse.second == o) {
91 : isSynonym = true;
92 : }
93 : }
94 27817438 : if (!isSynonym) {
95 45229798 : myAddresses.push_back(std::make_pair(name, o));
96 : }
97 : // insert in values
98 27817438 : myValues[name] = o;
99 27817438 : }
100 :
101 :
102 : void
103 1443834 : OptionsCont::doRegister(const std::string& name1, char abbr, Option* o) {
104 1443834 : doRegister(name1, o);
105 1443834 : doRegister(convertChar(abbr), o);
106 1443834 : }
107 :
108 :
109 : void
110 3758705 : 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 3758705 : if (i1 == myValues.end() && i2 == myValues.end()) {
114 0 : throw ProcessError("Neither the option '" + name1 + "' nor the option '" + name2 + "' is known yet");
115 : }
116 3758705 : 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 3758705 : if (i1 == myValues.end() && i2 != myValues.end()) {
123 117435 : doRegister(name1, (*i2).second);
124 117435 : if (isDeprecated) {
125 0 : myDeprecatedSynonymes[name1] = false;
126 : }
127 : }
128 3758705 : if (i1 != myValues.end() && i2 == myValues.end()) {
129 3641270 : doRegister(name2, (*i1).second);
130 3641270 : if (isDeprecated) {
131 1902357 : myDeprecatedSynonymes[name2] = false;
132 : }
133 : }
134 : }
135 :
136 :
137 : void
138 102520 : OptionsCont::addXMLDefault(const std::string& name, const std::string& xmlRoot) {
139 102520 : myXMLDefaults[xmlRoot] = name;
140 102520 : }
141 :
142 :
143 : bool
144 356576666 : OptionsCont::exists(const std::string& name) const {
145 356576666 : return myValues.count(name) > 0;
146 : }
147 :
148 :
149 : bool
150 1194494317 : OptionsCont::isSet(const std::string& name, bool failOnNonExistant) const {
151 : auto i = myValues.find(name);
152 1194494317 : if (i == myValues.end()) {
153 204 : if (failOnNonExistant) {
154 9 : throw ProcessError(TLF("Internal request for unknown option '%'!", name));
155 : } else {
156 : return false;
157 : }
158 : }
159 1194494113 : return (*i).second->isSet();
160 : }
161 :
162 :
163 : bool
164 2161787 : OptionsCont::isDefault(const std::string& name) const {
165 : auto i = myValues.find(name);
166 2161787 : if (i == myValues.end()) {
167 : return false;
168 : }
169 2161339 : return (*i).second->isDefault();
170 : }
171 :
172 :
173 : Option*
174 364282563 : OptionsCont::getSecure(const std::string& name) const {
175 : const auto& valuesFinder = myValues.find(name);
176 364282563 : 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 364282509 : if ((synonymFinder != myDeprecatedSynonymes.end()) && !synonymFinder->second) {
181 : std::string defaultName;
182 7018 : for (const auto& subtopicEntry : mySubTopicEntries) {
183 119758 : for (const auto& value : subtopicEntry.second) {
184 : const auto l = myValues.find(value);
185 113941 : 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 364282509 : return valuesFinder->second;
198 : }
199 :
200 :
201 : std::string
202 13959824 : OptionsCont::getValueString(const std::string& name) const {
203 13959824 : Option* o = getSecure(name);
204 13959824 : return o->getValueString();
205 : }
206 :
207 :
208 : std::string
209 32760985 : OptionsCont::getString(const std::string& name) const {
210 32760985 : Option* o = getSecure(name);
211 32760985 : return o->getString();
212 : }
213 :
214 :
215 : double
216 110969563 : OptionsCont::getFloat(const std::string& name) const {
217 110969563 : Option* o = getSecure(name);
218 110969563 : return o->getFloat();
219 : }
220 :
221 :
222 : int
223 18190310 : OptionsCont::getInt(const std::string& name) const {
224 18190310 : Option* o = getSecure(name);
225 18190310 : return o->getInt();
226 : }
227 :
228 :
229 : bool
230 124850259 : OptionsCont::getBool(const std::string& name) const {
231 124850259 : Option* o = getSecure(name);
232 124850259 : 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 660643 : OptionsCont::getStringVector(const std::string& name) const {
244 660643 : Option* o = getSecure(name);
245 660643 : return o->getStringVector();
246 : }
247 :
248 :
249 : bool
250 1241523 : OptionsCont::set(const std::string& name, const std::string& value, const bool append) {
251 1241523 : Option* o = getSecure(name);
252 1241505 : 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 2482992 : 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 168979 : OptionsCont::setDefault(const std::string& name, const std::string& value) {
271 168979 : Option* const o = getSecure(name);
272 168979 : if (o->isWriteable() && set(name, value)) {
273 149020 : o->resetDefault();
274 149020 : 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 46606 : OptionsCont::getSynonymes(const std::string& name) const {
294 46606 : Option* o = getSecure(name);
295 : std::vector<std::string> synonymes;
296 21608287 : for (const auto& value : myValues) {
297 21561681 : if ((value.second == o) && (name != value.first)) {
298 13438 : synonymes.push_back(value.first);
299 : }
300 : }
301 46606 : 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 13700 : OptionsCont::relocateFiles(const std::string& configuration) const {
352 6224006 : for (const auto& addresse : myAddresses) {
353 6210306 : if (addresse.second->isFileName() && addresse.second->isSet()) {
354 81504 : StringVector fileList = StringVector(addresse.second->getStringVector());
355 174178 : for (auto& file : fileList) {
356 92674 : if (addresse.first != "configuration-file") {
357 157948 : file = FileHelpers::checkForRelativity(file, configuration);
358 : }
359 : try {
360 185346 : 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 244512 : StringVector rawList = StringTokenizer(addresse.second->getValueString(), ",").getVector();
366 174178 : for (auto& file : rawList) {
367 185348 : file = FileHelpers::checkForRelativity(file, configuration);
368 : }
369 81504 : const std::string conv = joinToString(fileList, ',');
370 163008 : 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 81504 : }
378 : }
379 13700 : }
380 :
381 :
382 : bool
383 87710 : OptionsCont::isUsableFileList(const std::string& name) const {
384 87710 : Option* const o = getSecure(name);
385 87710 : if (!o->isSet()) {
386 : return false;
387 : }
388 : // check whether the list of files is valid
389 : bool ok = true;
390 78477 : std::vector<std::string> files = getStringVector(name);
391 78477 : if (files.size() == 0) {
392 0 : WRITE_ERRORF(TL("The file list for '%' is empty."), name);
393 : ok = false;
394 : }
395 176307 : for (const auto& file : files) {
396 195660 : if (!FileHelpers::isReadable(file)) {
397 30 : if (file != "") {
398 90 : 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 78477 : }
407 :
408 :
409 : bool
410 7275 : OptionsCont::checkDependingSuboptions(const std::string& name, const std::string& prefix) const {
411 7275 : Option* o = getSecure(name);
412 7275 : if (o->isSet()) {
413 : return true;
414 : }
415 : bool ok = true;
416 : std::vector<std::string> seenSynonymes;
417 3569460 : for (const auto& value : myValues) {
418 3562205 : if (std::find(seenSynonymes.begin(), seenSynonymes.end(), value.first) != seenSynonymes.end()) {
419 1 : continue;
420 : }
421 3671278 : 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 7255 : }
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 1443834 : OptionsCont::convertChar(char abbr) const {
451 : char buf[2];
452 1443834 : buf[0] = abbr;
453 1443834 : buf[1] = 0;
454 1443834 : std::string s(buf);
455 1443834 : return s;
456 : }
457 :
458 :
459 : bool
460 883262 : OptionsCont::isBool(const std::string& name) const {
461 883262 : Option* o = getSecure(name);
462 883232 : return o->isBool();
463 : }
464 :
465 :
466 : void
467 68239 : OptionsCont::resetWritable() {
468 28748004 : for (const auto& addresse : myAddresses) {
469 28679765 : addresse.second->resetWritable();
470 : }
471 68239 : }
472 :
473 :
474 : void
475 239 : OptionsCont::resetWritable(const std::string& name) {
476 239 : getSecure(name)->resetWritable();
477 239 : }
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 80917 : OptionsCont::isWriteable(const std::string& name) {
496 80917 : 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 201188 : OptionsCont::clear() {
509 : // delete only address (because synonyms placed in values aim to the same Option)
510 22879597 : for (const auto& addresse : myAddresses) {
511 22678409 : delete addresse.second;
512 : }
513 : myAddresses.clear();
514 : myValues.clear();
515 : mySubTopics.clear();
516 : mySubTopicEntries.clear();
517 201188 : }
518 :
519 :
520 : void
521 22570269 : OptionsCont::addDescription(const std::string& name, const std::string& subtopic,
522 : const std::string& description) {
523 22570269 : Option* o = getSecure(name);
524 22570269 : if (o == nullptr) {
525 0 : throw ProcessError("Option doesn't exist");
526 : }
527 22570269 : if (find(mySubTopics.begin(), mySubTopics.end(), subtopic) == mySubTopics.end()) {
528 0 : throw ProcessError("SubTopic '" + subtopic + "' doesn't exist");
529 : }
530 22570269 : o->setDescription(description);
531 22570269 : o->setSubtopic(subtopic);
532 22570269 : mySubTopicEntries[subtopic].push_back(name);
533 22570269 : }
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 54194 : OptionsCont::setApplicationName(const std::string& appName, const std::string& fullName) {
563 54194 : myAppName = appName;
564 54194 : myFullName = fullName;
565 54194 : }
566 :
567 :
568 : void
569 107976 : OptionsCont::setApplicationDescription(const std::string& appDesc) {
570 107976 : myAppDescription = appDesc;
571 107976 : }
572 :
573 :
574 : void
575 146274 : OptionsCont::addCallExample(const std::string& example, const std::string& desc) {
576 146274 : myCallExamples.push_back(std::make_pair(example, desc));
577 146274 : }
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 1318269 : OptionsCont::addOptionSubTopic(const std::string& topic) {
600 1318269 : mySubTopics.push_back(topic);
601 1318269 : mySubTopicEntries[topic] = std::vector<std::string>();
602 1318269 : }
603 :
604 :
605 : void
606 20888 : OptionsCont::splitLines(std::ostream& os, std::string what,
607 : int offset, int nextOffset) {
608 64680 : while (what.length() > 0) {
609 43792 : if ((int)what.length() > 79 - offset) {
610 22939 : std::string::size_type splitPos = what.rfind(';', 79 - offset);
611 22939 : if (splitPos == std::string::npos) {
612 22669 : splitPos = what.rfind(' ', 79 - offset);
613 : } else {
614 270 : splitPos++;
615 : }
616 22939 : if (splitPos != std::string::npos) {
617 22904 : os << what.substr(0, splitPos) << std::endl;
618 22904 : what = what.substr(splitPos + 1);
619 932814 : for (int r = 0; r < (nextOffset + 1); ++r) {
620 909910 : 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 20888 : }
634 :
635 :
636 : bool
637 54434 : OptionsCont::processMetaOptions(bool missingOptions) {
638 54434 : MsgHandler::setupI18n(getString("language"));
639 54434 : localizeDescriptions();
640 54434 : 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 108716 : 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 108562 : 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 108536 : 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 108536 : 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 107518 : 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 107472 : 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 54434 : OptionsCont::localizeDescriptions() {
743 54434 : if (!myAmLocalized && gLocaleInitialized) {
744 : // options
745 22463823 : for (auto option : myAddresses) {
746 44818868 : option.second->setDescription(TL(option.second->getDescription().c_str()));
747 : }
748 : // examples
749 199613 : for (auto example : myCallExamples) {
750 145224 : example.second = TL(example.second.c_str());
751 : }
752 : // other text
753 54389 : setApplicationDescription(TL(myAppDescription.c_str()));
754 54389 : myAmLocalized = true;
755 : }
756 54434 : }
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 22070 : for (const auto& entry : mySubTopicEntries[subTopic]) {
817 20811 : Option* o = getSecure(entry);
818 : // name, two leading spaces and "--"
819 20811 : int csize = (int)entry.length() + 2 + 4;
820 : // abbreviation length ("-X, "->4chars) if any
821 20811 : const auto synonymes = getSynonymes(entry);
822 24035 : 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 20811 : if (!o->isBool()) {
830 14279 : csize += 1 + (int)o->getTypeName().length();
831 : }
832 : // divider
833 20811 : csize += 2;
834 20811 : if (csize < tooLarge && maxSize < csize) {
835 : maxSize = csize;
836 : }
837 20811 : }
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 22070 : for (const auto& entry : mySubTopicEntries[topic]) {
887 : // start length computation
888 20811 : int csize = (int)entry.length() + 2;
889 20811 : Option* o = getSecure(entry);
890 20811 : os << " ";
891 : // write abbreviation if given
892 20811 : const auto synonymes = getSynonymes(entry);
893 24035 : 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 20811 : os << "--";
902 20811 : csize += 2;
903 : // write the name
904 : os << entry;
905 : // write the type if not a bool option
906 20811 : if (!o->isBool()) {
907 14279 : os << ' ' << o->getTypeName();
908 14279 : csize += 1 + (int)o->getTypeName().length();
909 : }
910 20811 : csize += 2;
911 : // write the description formatting it
912 20811 : os << " ";
913 209577 : for (int r = maxSize; r > csize; --r) {
914 188766 : os << ' ';
915 : }
916 20811 : int offset = csize > tooLarge ? csize : maxSize;
917 41622 : splitLines(os, o->getDescription(), offset, maxSize);
918 20811 : }
919 : os << std::endl;
920 1259 : }
921 :
922 :
923 : void
924 87858 : 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 87858 : if (!inComment) {
928 772 : writeXMLHeader(os, false);
929 : }
930 166868 : const std::string& app = myAppName == "sumo-gui" ? "sumo" : myAppName;
931 : os << indent << "<" << app << "Configuration xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
932 87858 : << "xsi:noNamespaceSchemaLocation=\"http://sumo.dlr.de/xsd/" << app << "Configuration.xsd\">\n\n";
933 2312686 : for (std::string subtopic : mySubTopics) {
934 2224828 : 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 4273986 : subtopic = StringUtils::to_lower_case(subtopic);
940 : bool hadOne = false;
941 39899036 : for (const std::string& name : entries) {
942 37762043 : Option* o = getSecure(name);
943 37762043 : bool write = complete || (filled && !o->isDefault());
944 36972208 : if (!write) {
945 36972208 : continue;
946 : }
947 789835 : if (name == "registry-viewport" && !complete) {
948 0 : continue;
949 : }
950 789835 : if (!hadOne) {
951 308801 : os << indent << " <" << subtopic << ">\n";
952 : }
953 : // add the comment if wished
954 789835 : if (addComments) {
955 4602 : os << indent << " <!-- " << StringUtils::escapeXML(o->getDescription(), inComment) << " -->\n";
956 : }
957 : // write the option and the value (if given)
958 789835 : os << indent << " <" << name << " value=\"";
959 789835 : if (o->isSet() && (filled || o->isDefault())) {
960 788670 : 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 1572312 : os << StringUtils::escapeXML(o->getValueString(), inComment);
977 : }
978 : }
979 789835 : if (complete) {
980 4983 : const std::vector<std::string> synonymes = getSynonymes(name);
981 4983 : if (!synonymes.empty()) {
982 2594 : os << "\" synonymes=\"" << toString(synonymes);
983 : }
984 : std::string deprecated;
985 6812 : for (const auto& synonym : synonymes) {
986 : if (myDeprecatedSynonymes.count(synonym) > 0) {
987 1554 : deprecated += " " + synonym;
988 : }
989 : }
990 4983 : if (deprecated != "") {
991 1398 : os << "\" deprecated=\"" << deprecated.substr(1);
992 : }
993 4983 : os << "\" type=\"" << o->getTypeName();
994 4983 : if (!addComments) {
995 6898 : os << "\" help=\"" << StringUtils::escapeXML(o->getDescription());
996 : }
997 4983 : }
998 789835 : os << "\"/>\n";
999 : // append an endline if a comment was printed
1000 789835 : if (addComments) {
1001 1534 : os << "\n";
1002 : }
1003 : hadOne = true;
1004 : }
1005 2136993 : if (hadOne) {
1006 308801 : os << indent << " </" << subtopic << ">\n\n";
1007 : }
1008 : }
1009 : os << indent << "</" << app << "Configuration>" << std::endl; // flushing seems like a good idea here
1010 87858 : }
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 483 : for (const auto& entry : entries) {
1040 457 : Option* o = getSecure(entry);
1041 457 : std::string type = o->getTypeName();
1042 457 : type = StringUtils::to_lower_case(type);
1043 457 : if (type == "int[]") {
1044 : type = "intArray";
1045 : }
1046 457 : if (type == "str[]") {
1047 : type = "strArray";
1048 : }
1049 457 : 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 87859 : OptionsCont::writeXMLHeader(std::ostream& os, const bool includeConfig) const {
1060 87859 : os << "<?xml version=\"1.0\"" << SUMOSAXAttributes::ENCODING << "?>\n\n";
1061 87859 : os << "<!-- ";
1062 175718 : if (!getBool("write-metadata")) {
1063 263574 : os << "generated on " << StringUtils::isoTimeString() << " by " << myFullName << "\n";
1064 : }
1065 175718 : 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 35897 : "SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later\n";
1076 : }
1077 174946 : if (includeConfig && !getBool("write-metadata")) {
1078 174170 : writeConfiguration(os, true, false, false, "", false, true);
1079 : }
1080 87859 : os << "-->\n\n";
1081 87859 : }
1082 :
1083 :
1084 : bool
1085 41289 : OptionsCont::isInStringVector(const std::string& optionName,
1086 : const std::string& itemName) const {
1087 41289 : 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 174 : 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 174 : OptionsCont* oc = new OptionsCont(*this);
1100 174 : oc->resetWritable();
1101 63684 : for (auto& addr : oc->myAddresses) {
1102 63510 : addr.second = addr.second->clone();
1103 : }
1104 174 : return oc;
1105 : }
1106 :
1107 :
1108 : /****************************************************************************/
|