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