MythTV master
mythcommandlineparser.cpp
Go to the documentation of this file.
1/* -*- Mode: c++ -*-
2*
3* Class CommandLineArg
4* Class MythCommandLineParser
5*
6* Copyright (C) Raymond Wagner 2011
7*
8* This program is free software; you can redistribute it and/or modify
9* it under the terms of the GNU General Public License as published by
10* the Free Software Foundation; either version 2 of the License, or
11* (at your option) any later version.
12*
13* This program is distributed in the hope that it will be useful,
14* but WITHOUT ANY WARRANTY; without even the implied warranty of
15* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16* GNU General Public License for more details.
17*
18* You should have received a copy of the GNU General Public License
19* along with this program; if not, write to the Free Software
20* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
21*/
22
23#include <QtGlobal>
24#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
25#include <QtEnvironmentVariables>
26#include <QtSystemDetection>
27#endif
28
29#if defined ANDROID && __ANDROID_API__ < 24
30// ftello and fseeko do not exist in android before api level 24
31#define ftello ftell
32#define fseeko fseek
33#endif
34
35// C++ headers
36#include <algorithm>
37#include <csignal>
38#include <cstdio>
39#include <cstdlib>
40#include <fstream>
41#include <iostream>
42#include <unistd.h>
43
44// System headers
45#include <sys/types.h>
46#ifndef Q_OS_WINDOWS
47# include <sys/ioctl.h>
48# include <pwd.h>
49# include <grp.h>
50# ifdef Q_OS_LINUX
51# include <sys/prctl.h>
52# endif // linux
53#endif // not Q_OS_WINDOWS
54
55// Qt headers
56#include <QCoreApplication>
57#include <QDateTime>
58#include <QDir>
59#include <QFile>
60#include <QFileInfo>
61#include <QRegularExpression>
62#include <QSize>
63#include <QString>
64#include <QTextStream>
65#include <QVariant>
66#include <QVariantList>
67#include <QVariantMap>
68#include <utility>
69
70// MythTV headers
72#include "mythcorecontext.h"
73#include "exitcodes.h"
74#include "mythconfig.h"
75#include "mythlogging.h"
76#include "mythversion.h"
77#include "logging.h"
78#include "mythmiscutil.h"
79#include "mythdate.h"
80
81static constexpr int k_defaultWidth = 79;
82
86static int GetTermWidth(void)
87{
88#if defined(Q_OS_WINDOWS) || defined(Q_OS_ANDROID)
89 return k_defaultWidth;
90#else
91 struct winsize ws {};
92
93 if (ioctl(0, TIOCGWINSZ, &ws) != 0)
94 return k_defaultWidth;
95
96 return static_cast<int>(ws.ws_col);
97#endif
98}
99
100static QByteArray strip_quotes(const QByteArray& array)
101{
102 return ((array.startsWith('"') && array.endsWith('"') ) ||
103 (array.startsWith('\'') && array.endsWith('\''))
104 ) ? array.mid(1, array.size() - 2) : array;
105}
106
107static void wrapList(QStringList &list, int width)
108{
109 // Set a minimum width of 5 to prevent a crash; if this is triggered,
110 // something has gone seriously wrong and the result won't really be usable
111 width = std::max(width, 5);
112
113 for (int i = 0; i < list.size(); i++)
114 {
115 QString string = list.at(i);
116
117 if( string.size() <= width )
118 continue;
119
120 QString left = string.left(width);
121 bool inserted = false;
122
123 while( !inserted && !left.endsWith(" " ))
124 {
125 if( string.mid(left.size(), 1) == " " )
126 {
127 list.replace(i, left);
128 list.insert(i+1, string.mid(left.size()).trimmed());
129 inserted = true;
130 }
131 else
132 {
133 left.chop(1);
134 if( !left.contains(" ") )
135 {
136 // Line is too long, just hyphenate it
137 list.replace(i, left + "-");
138 list.insert(i+1, string.mid(left.size()));
139 inserted = true;
140 }
141 }
142 }
143
144 if( !inserted )
145 {
146 left.chop(1);
147 list.replace(i, left);
148 list.insert(i+1, string.mid(left.size()).trimmed());
149 }
150 }
151}
152
157QStringList MythCommandLineParser::MythSplitCommandString(const QString &line)
158{
159 QStringList fields;
163 enum states : std::uint8_t {
164 START,
165 INTEXT,
166 INSQUOTE,
167 INDQUOTE,
168 ESCTEXT,
169 ESCSQUOTE,
170 ESCDQUOTE,
171 };
172 states state = START;
173 int tokenStart = -1;
174
175 for (int i = 0; i < line.size(); i++)
176 {
177 const QChar c = line.at(i);
178
179 switch (state) {
180 case START:
181 tokenStart = i;
182 if (c.isSpace()) break;
183 if (c == '\'') state = INSQUOTE;
184 else if (c == '\"') state = INDQUOTE;
185 else if (c == '\\') state = ESCTEXT;
186 else state = INTEXT;
187 break;
188 case INTEXT:
189 if (c.isSpace()) {
190 fields += line.mid(tokenStart, i - tokenStart);
191 state = START;
192 break;
193 }
194 else if (c == '\'') {
195 state = INSQUOTE;
196 } else if (c == '\"') {
197 state = INDQUOTE;
198 } else if (c == '\\') {
199 state = ESCTEXT;
200 }
201 break;
202 case INSQUOTE:
203 if (c == '\'') state = INTEXT;
204 else if (c == '\\') state = ESCSQUOTE;
205 break;
206 case INDQUOTE:
207 if (c == '\"') state = INTEXT;
208 else if (c == '\\') state = ESCDQUOTE;
209 break;
210 case ESCTEXT: state = INTEXT; break;
211 case ESCSQUOTE: state = INSQUOTE; break;
212 case ESCDQUOTE: state = INDQUOTE; break;
213 }
214 }
215
216 if (state != START)
217 fields += line.mid(tokenStart);
218 return fields;
219}
220
225{
226 switch (type)
227 {
228 case Result::kEnd:
229 return "kEnd";
230
231 case Result::kEmpty:
232 return "kEmpty";
233
234 case Result::kOptOnly:
235 return "kOptOnly";
236
237 case Result::kOptVal:
238 return "kOptVal";
239
241 return "kCombOptVal";
242
243 case Result::kArg:
244 return "kArg";
245
247 return "kPassthrough";
248
249 case Result::kInvalid:
250 return "kInvalid";
251 }
252 return "kUnknown";
253}
254
289CommandLineArg::CommandLineArg(const QString& name, QMetaType::Type type,
290 QVariant def, QString help, QString longhelp) :
291 ReferenceCounter(QString("CommandLineArg:%1").arg(name)),
292 m_name(name), m_type(type), m_default(std::move(def)),
293 m_help(std::move(help)), m_longhelp(std::move(longhelp))
294{
295 if ((m_type != QMetaType::QString) && (m_type != QMetaType::QStringList) &&
296 (m_type != QMetaType::QVariantMap))
297 m_converted = true;
298}
299
306CommandLineArg::CommandLineArg(const QString& name, QMetaType::Type type, QVariant def)
307 : ReferenceCounter(QString("CommandLineArg:%1").arg(name)),
308 m_name(name), m_type(type), m_default(std::move(def))
309{
310 if ((m_type != QMetaType::QString) && (m_type != QMetaType::QStringList) &&
311 (m_type != QMetaType::QVariantMap))
312 m_converted = true;
313}
314
322CommandLineArg::CommandLineArg(const QString& name) :
323 ReferenceCounter(QString("CommandLineArg:%1").arg(name)),
324 m_name(name)
325{
326}
327
332{
333 // this may cause problems if the terminal is too narrow, or if too
334 // many keywords for the same argument are used
335 return m_keywords.join(", ");
336}
337
342{
343 int len = GetKeywordString().length();
344
345 QList<CommandLineArg*>::const_iterator i1;
346 for (i1 = m_parents.begin(); i1 != m_parents.end(); ++i1)
347 len = std::max(len, (*i1)->GetKeywordLength()+2);
348
349 return len;
350}
351
367QString CommandLineArg::GetHelpString(int off, const QString& group, bool force) const
368{
369 QString helpstr;
370#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
371 QTextStream msg(&helpstr, QIODevice::WriteOnly);
372#else
373 QTextStream msg(&helpstr, QIODeviceBase::WriteOnly);
374#endif
375 int termwidth = GetTermWidth();
376 if (termwidth < off)
377 {
378 if (off > 70)
379 {
380 // developer has configured some absurdly long command line
381 // arguments, but we still need to do something
382 termwidth = off+40;
383 }
384 else
385 {
386 // user is running uselessly narrow console, use a sane console
387 // width instead
388 termwidth = k_defaultWidth;
389 }
390 }
391
392 if (m_help.isEmpty() && !force)
393 // only print if there is a short help to print
394 return helpstr;
395
396 if ((m_group != group) && !force)
397 // only print if looping over the correct group
398 return helpstr;
399
400 if (!m_parents.isEmpty() && !force)
401 {
402 // only print if an independent option, not subject
403 // to a parent option
404 return helpstr;
405 }
406
407 if (!m_deprecated.isEmpty())
408 // option is marked as deprecated, do not show
409 return helpstr;
410
411 if (!m_removed.isEmpty())
412 // option is marked as removed, do not show
413 return helpstr;
414
415 QString pad;
416 pad.fill(' ', off);
417
418 // print the first line with the available keywords
419 QStringList hlist = m_help.split('\n');
420 wrapList(hlist, termwidth-off);
421 msg << " ";
422 if (!m_parents.isEmpty())
423 msg << " ";
424 msg << GetKeywordString().leftJustified(off, ' ')
425 << hlist.takeFirst() << Qt::endl;
426
427 // print remaining lines with necessary padding
428 for (const auto & line : std::as_const(hlist))
429 msg << pad << line << Qt::endl;
430
431 // loop through any child arguments to print underneath
432 for (auto * arg : std::as_const(m_children))
433 msg << arg->GetHelpString(off, group, true);
434
435 msg.flush();
436 return helpstr;
437}
438
446QString CommandLineArg::GetLongHelpString(QString keyword) const
447{
448 QString helpstr;
449#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
450 QTextStream msg(&helpstr, QIODevice::WriteOnly);
451#else
452 QTextStream msg(&helpstr, QIODeviceBase::WriteOnly);
453#endif
454 int termwidth = GetTermWidth();
455
456 // help called for an argument that is not me, this should not happen
457 if (!m_keywords.contains(keyword))
458 return helpstr;
459
460 // argument has been marked as removed, so warn user of such
461 if (!m_removed.isEmpty())
462 {
463 PrintRemovedWarning(keyword);
464 // argument has been marked as deprecated, so warn user of such
465 }
466 else if (!m_deprecated.isEmpty())
467 {
468 PrintDeprecatedWarning(keyword);
469 }
470
471 msg << "Option: " << keyword << Qt::endl << Qt::endl;
472
473 bool first = true;
474
475 // print all related keywords, padding for multiples
476 for (const auto & word : std::as_const(m_keywords))
477 {
478 if (word != keyword)
479 {
480 if (first)
481 {
482 msg << "Aliases: " << word << Qt::endl;
483 first = false;
484 }
485 else
486 {
487 msg << " " << word << Qt::endl;
488 }
489 }
490 }
491
492 // print type and default for the stored value
493 msg << "Type: " << QMetaType(m_type).name() << Qt::endl;
494 if (m_default.canConvert<QString>())
495 msg << "Default: " << m_default.toString() << Qt::endl;
496
497 QStringList help;
498 if (m_longhelp.isEmpty())
499 help = m_help.split("\n");
500 else
501 help = m_longhelp.split("\n");
502 wrapList(help, termwidth-13);
503
504 // print description, wrapping and padding as necessary
505 msg << "Description: " << help.takeFirst() << Qt::endl;
506 for (const auto & line : std::as_const(help))
507 msg << " " << line << Qt::endl;
508
509 QList<CommandLineArg*>::const_iterator i2;
510
511 // loop through the four relation types and print
512 if (!m_parents.isEmpty())
513 {
514 msg << Qt::endl << "Can be used in combination with:" << Qt::endl;
515 for (auto * parent : std::as_const(m_parents))
516 msg << " " << parent->GetPreferredKeyword()
517 .toLocal8Bit().constData();
518 msg << Qt::endl;
519 }
520
521 if (!m_children.isEmpty())
522 {
523 msg << Qt::endl << "Allows the use of:" << Qt::endl;
524 for (i2 = m_children.constBegin(); i2 != m_children.constEnd(); ++i2)
525 msg << " " << (*i2)->GetPreferredKeyword()
526 .toLocal8Bit().constData();
527 msg << Qt::endl;
528 }
529
530 if (!m_requires.isEmpty())
531 {
532 msg << Qt::endl << "Requires the use of:" << Qt::endl;
533 for (i2 = m_requires.constBegin(); i2 != m_requires.constEnd(); ++i2)
534 msg << " " << (*i2)->GetPreferredKeyword()
535 .toLocal8Bit().constData();
536 msg << Qt::endl;
537 }
538
539 if (!m_blocks.isEmpty())
540 {
541 msg << Qt::endl << "Prevents the use of:" << Qt::endl;
542 for (i2 = m_blocks.constBegin(); i2 != m_blocks.constEnd(); ++i2)
543 msg << " " << (*i2)->GetPreferredKeyword()
544 .toLocal8Bit().constData();
545 msg << Qt::endl;
546 }
547
548 msg.flush();
549 return helpstr;
550}
551
558bool CommandLineArg::Set(const QString& opt)
559{
560 m_usedKeyword = opt;
561
562 switch (m_type)
563 {
564 case QMetaType::Bool:
565 m_stored = QVariant(!m_default.toBool());
566 break;
567
568 case QMetaType::Int:
569 if (m_stored.isNull())
570 m_stored = QVariant(1);
571 else
572 m_stored = QVariant(m_stored.toInt() + 1);
573 break;
574
575 case QMetaType::QString:
577 break;
578
579 default:
580 std::cerr << "Command line option did not receive value:\n"
581 << " " << opt.toLocal8Bit().constData() << '\n';
582 return false;
583 }
584
585 m_given = true;
586 return true;
587}
588
591bool CommandLineArg::Set(const QString& opt, const QByteArray& val)
592{
593 QVariantList vlist;
594 QList<QByteArray> blist;
595 QVariantMap vmap;
596 m_usedKeyword = opt;
597
598 switch (m_type)
599 {
600 case QMetaType::Bool:
601 std::cerr << "Boolean type options do not accept values:\n"
602 << " " << opt.toLocal8Bit().constData() << '\n';
603 return false;
604
605 case QMetaType::QString:
606 m_stored = QVariant(val);
607 break;
608
609 case QMetaType::Int:
610 m_stored = QVariant(val.toInt());
611 break;
612
613 case QMetaType::UInt:
614 m_stored = QVariant(val.toUInt());
615 break;
616
617 case QMetaType::LongLong:
618 m_stored = QVariant(val.toLongLong());
619 break;
620
621 case QMetaType::Double:
622 m_stored = QVariant(val.toDouble());
623 break;
624
625 case QMetaType::QDateTime:
626 m_stored = QVariant(MythDate::fromString(QString(val)));
627 break;
628
629 case QMetaType::QStringList:
630 if (!m_stored.isNull())
631 vlist = m_stored.toList();
632 vlist << val;
633 m_stored = QVariant(vlist);
634 break;
635
636 case QMetaType::QVariantMap:
637 if (!val.contains('='))
638 {
639 std::cerr << "Command line option did not get expected "
640 << "key/value pair\n";
641 return false;
642 }
643
644 blist = val.split('=');
645
646 if (!m_stored.isNull())
647 vmap = m_stored.toMap();
648 vmap[QString(strip_quotes(blist[0]))] = QVariant(strip_quotes(blist[1]));
649 m_stored = QVariant(vmap);
650 break;
651
652 case QMetaType::QSize:
653 if (!val.contains('x'))
654 {
655 std::cerr << "Command line option did not get expected "
656 << "XxY pair\n";
657 return false;
658 }
659
660 blist = val.split('x');
661 m_stored = QVariant(QSize(blist[0].toInt(), blist[1].toInt()));
662 break;
663
664 default:
665 m_stored = QVariant(val);
666 }
667
668 m_given = true;
669 return true;
670}
671
675{
676 m_children << new CommandLineArg(opt);
677 return this;
678}
679
683{
684 for (const auto& opt : std::as_const(opts))
685 m_children << new CommandLineArg(opt);
686 return this;
687}
688
692{
693 m_parents << new CommandLineArg(opt);
694 return this;
695}
696
700{
701 for (const auto& opt : std::as_const(opts))
702 m_parents << new CommandLineArg(opt);
703 return this;
704}
705
709{
710 m_parents << new CommandLineArg(opt);
711 return this;
712}
713
717{
718 for (const auto& opt : std::as_const(opts))
719 m_parents << new CommandLineArg(opt);
720 return this;
721}
722
726{
727 m_children << new CommandLineArg(opt);
728 return this;
729}
730
734{
735 for (const auto& opt : std::as_const(opts))
736 m_children << new CommandLineArg(opt);
737 return this;
738}
739
743{
744 m_children << new CommandLineArg(opt);
745 m_requires << new CommandLineArg(opt);
746 return this;
747}
748
752{
753 for (const auto& opt : std::as_const(opts))
754 {
755 m_children << new CommandLineArg(opt);
756 m_requires << new CommandLineArg(opt);
757 }
758 return this;
759}
760
764{
765 m_parents << new CommandLineArg(opt);
766 m_requiredby << new CommandLineArg(opt);
767 return this;
768}
769
773{
774 for (const auto& opt : std::as_const(opts))
775 {
776 m_parents << new CommandLineArg(opt);
777 m_requiredby << new CommandLineArg(opt);
778 }
779 return this;
780}
781
785{
786 m_requires << new CommandLineArg(opt);
787 return this;
788}
789
793{
794 for (const auto& opt : std::as_const(opts))
795 m_requires << new CommandLineArg(opt);
796 return this;
797}
798
802{
803 m_blocks << new CommandLineArg(opt);
804 return this;
805}
806
810{
811 for (const auto& opt : std::as_const(opts))
812 m_blocks << new CommandLineArg(opt);
813 return this;
814}
815
819{
820 if (depstr.isEmpty())
821 depstr = "and will be removed in a future version.";
822 m_deprecated = depstr;
823 return this;
824}
825
828CommandLineArg* CommandLineArg::SetRemoved(QString remstr, QString remver)
829{
830 if (remstr.isEmpty())
831 remstr = "and is no longer available in this version.";
832 m_removed = remstr;
833 m_removedversion = std::move(remver);
834 return this;
835}
836
843{
844 bool replaced = false;
845 other->IncrRef();
846
847 for (int i = 0; i < m_children.size(); i++)
848 {
849 if (m_children[i]->m_name == other->m_name)
850 {
851 m_children[i]->DecrRef();
852 m_children.replace(i, other);
853 replaced = true;
854 break;
855 }
856 }
857
858 if (!replaced)
859 m_children << other;
860
861 if (forward)
862 other->SetChildOf(this, false);
863}
864
871{
872 bool replaced = false;
873 other->IncrRef();
874
875 for (int i = 0; i < m_parents.size(); i++)
876 {
877 if (m_parents[i]->m_name == other->m_name)
878 {
879 m_parents[i]->DecrRef();
880 m_parents.replace(i, other);
881 replaced = true;
882 break;
883 }
884 }
885
886 if (!replaced)
887 m_parents << other;
888
889 if (forward)
890 other->SetParentOf(this, false);
891}
892
898void CommandLineArg::SetRequires(CommandLineArg *other, bool /*forward*/)
899{
900 bool replaced = false;
901 other->IncrRef();
902
903 for (int i = 0; i < m_requires.size(); i++)
904 {
905 if (m_requires[i]->m_name == other->m_name)
906 {
907 m_requires[i]->DecrRef();
908 m_requires.replace(i, other);
909 replaced = true;
910 break;
911 }
912 }
913
914 if (!replaced)
915 m_requires << other;
916
917// requirements need not be reciprocal
918// if (forward)
919// other->SetRequires(this, false);
920}
921
928{
929 bool replaced = false;
930 other->IncrRef();
931
932 for (int i = 0; i < m_blocks.size(); i++)
933 {
934 if (m_blocks[i]->m_name == other->m_name)
935 {
936 m_blocks[i]->DecrRef();
937 m_blocks.replace(i, other);
938 replaced = true;
939 break;
940 }
941 }
942
943 if (!replaced)
944 m_blocks << other;
945
946 if (forward)
947 other->SetBlocks(this, false);
948}
949
952void CommandLineArg::AllowOneOf(const QList<CommandLineArg*>& args)
953{
954 // TODO: blocks do not get set properly if multiple dummy arguments
955 // are provided. since this method will not have access to the
956 // argument list, this issue will have to be resolved later in
957 // ReconcileLinks().
958
959 // loop through all but the last entry
960 for (auto i1 = args.cbegin(); i1 != args.cend()-1; ++i1)
961 {
962 // loop through the next to the last entry
963 // and block use with the current
964 for (auto i2 = i1+1; i2 != args.cend(); ++i2)
965 {
966 (*i1)->SetBlocks(*i2);
967 }
968
969 if ((*i1)->m_type == QMetaType::UnknownType)
970 (*i1)->DecrRef();
971 }
972}
973
981{
982 if (!QCoreApplication::instance())
983 // QApplication not available, no sense doing anything yet
984 return;
985
986 if (m_converted)
987 // already run, abort
988 return;
989
990 if (!m_given)
991 {
992 // nothing to work on, abort
993 m_converted = true;
994 return;
995 }
996
997#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
998 auto storedType = static_cast<QMetaType::Type>(m_stored.type());
999#else
1000 auto storedType = m_stored.typeId();
1001#endif
1002 if (m_type == QMetaType::QString)
1003 {
1004 if (storedType == QMetaType::QByteArray)
1005 {
1006 m_stored = QString::fromLocal8Bit(m_stored.toByteArray());
1007 }
1008 // else
1009 // not sure why this isnt a bytearray, but ignore it and
1010 // set it as converted
1011 }
1012 else if (m_type == QMetaType::QStringList)
1013 {
1014 if (storedType == QMetaType::QVariantList)
1015 {
1016 QVariantList vlist = m_stored.toList();
1017 QStringList slist;
1018 for (const auto& item : std::as_const(vlist))
1019 slist << QString::fromLocal8Bit(item.toByteArray());
1020 m_stored = QVariant(slist);
1021 }
1022 }
1023 else if (m_type == QMetaType::QVariantMap)
1024 {
1025 QVariantMap vmap = m_stored.toMap();
1026 // NOLINTNEXTLINE(modernize-loop-convert)
1027 for (auto iter = vmap.begin(); iter != vmap.end(); ++iter)
1028 (*iter) = QString::fromLocal8Bit(iter->toByteArray());
1029 }
1030 else
1031 {
1032 return;
1033 }
1034
1035 m_converted = true;
1036}
1037
1038
1045{
1046 QStringList::const_iterator it;
1047 QString preferred;
1048 int len = 0;
1049
1050 for (it = m_keywords.constBegin(); it != m_keywords.constEnd(); ++it)
1051 {
1052 int len2 = (*it).size();
1053 if (len2 > len)
1054 {
1055 preferred = *it;
1056 len = len2;
1057 }
1058 }
1059
1060 return preferred;
1061}
1062
1067{
1068 if (!m_given)
1069 return true; // not in use, no need for checks
1070
1071 QList<CommandLineArg*>::const_iterator i;
1072
1073 bool passes = false;
1074 for (i = m_parents.constBegin(); i != m_parents.constEnd(); ++i)
1075 {
1076 // one of these must have been defined
1077 if ((*i)->m_given)
1078 {
1079 passes = true;
1080 break;
1081 }
1082 }
1083 if (!passes && !m_parents.isEmpty())
1084 {
1085 std::cerr << "ERROR: " << m_usedKeyword.toLocal8Bit().constData()
1086 << " requires at least one of the following arguments\n";
1087 for (i = m_parents.constBegin(); i != m_parents.constEnd(); ++i)
1088 std::cerr << " "
1089 << (*i)->GetPreferredKeyword().toLocal8Bit().constData();
1090 std::cerr << "\n\n";
1091 return false;
1092 }
1093
1094 // we dont care about children
1095
1096 for (i = m_requires.constBegin(); i != m_requires.constEnd(); ++i)
1097 {
1098 // all of these must have been defined
1099 if (!(*i)->m_given)
1100 {
1101 std::cerr << "ERROR: " << m_usedKeyword.toLocal8Bit().constData()
1102 << " requires all of the following be defined as well\n";
1103 for (i = m_requires.constBegin(); i != m_requires.constEnd(); ++i)
1104 {
1105 std::cerr << " "
1106 << (*i)->GetPreferredKeyword().toLocal8Bit()
1107 .constData();
1108 }
1109 std::cerr << "\n\n";
1110 return false;
1111 }
1112 }
1113
1114 for (i = m_blocks.constBegin(); i != m_blocks.constEnd(); ++i)
1115 {
1116 // none of these can be defined
1117 if ((*i)->m_given)
1118 {
1119 std::cerr << "ERROR: " << m_usedKeyword.toLocal8Bit().constData()
1120 << " requires that none of the following be defined\n";
1121 for (i = m_blocks.constBegin(); i != m_blocks.constEnd(); ++i)
1122 {
1123 std::cerr << " "
1124 << (*i)->GetPreferredKeyword().toLocal8Bit()
1125 .constData();
1126 }
1127 std::cerr << "\n\n";
1128 return false;
1129 }
1130 }
1131
1132 return true;
1133}
1134
1138{
1139 // clear out interdependent pointers in preparation for deletion
1140 while (!m_parents.isEmpty())
1141 m_parents.takeFirst()->DecrRef();
1142
1143 while (!m_children.isEmpty())
1144 m_children.takeFirst()->DecrRef();
1145
1146 while (!m_blocks.isEmpty())
1147 m_blocks.takeFirst()->DecrRef();
1148
1149 while (!m_requires.isEmpty())
1150 m_requires.takeFirst()->DecrRef();
1151
1152 while (!m_requiredby.isEmpty())
1153 m_requiredby.takeFirst()->DecrRef();
1154}
1155
1159{
1160 if (!m_given)
1161 return;
1162
1163 std::cerr << " " << m_name.leftJustified(30).toLocal8Bit().constData();
1164
1165 QSize tmpsize;
1166 QMap<QString, QVariant> tmpmap;
1167 QMap<QString, QVariant>::const_iterator it;
1168 QVariantList vlist;
1169 bool first = true;
1170
1171 switch (m_type)
1172 {
1173 case QMetaType::Bool:
1174 std::cerr << (m_stored.toBool() ? "True" : "False") << '\n';
1175 break;
1176
1177 case QMetaType::Int:
1178 std::cerr << m_stored.toInt() << '\n';
1179 break;
1180
1181 case QMetaType::UInt:
1182 std::cerr << m_stored.toUInt() << '\n';
1183 break;
1184
1185 case QMetaType::LongLong:
1186 std::cerr << m_stored.toLongLong() << '\n';
1187 break;
1188
1189 case QMetaType::Double:
1190 std::cerr << m_stored.toDouble() << '\n';
1191 break;
1192
1193 case QMetaType::QSize:
1194 tmpsize = m_stored.toSize();
1195 std::cerr << "x=" << tmpsize.width()
1196 << " y=" << tmpsize.height()
1197 << '\n';
1198 break;
1199
1200 case QMetaType::QString:
1201 std::cerr << '"' << m_stored.toByteArray().constData()
1202 << '"' << '\n';
1203 break;
1204
1205 case QMetaType::QStringList:
1206 vlist = m_stored.toList();
1207 std::cerr << '"' << vlist.takeFirst().toByteArray().constData() << '"';
1208 for (const auto& str : std::as_const(vlist))
1209 {
1210 std::cerr << ", \""
1211 << str.constData()
1212 << '"';
1213 }
1214 std::cerr << '\n';
1215 break;
1216
1217 case QMetaType::QVariantMap:
1218 tmpmap = m_stored.toMap();
1219 for (it = tmpmap.cbegin(); it != tmpmap.cend(); ++it)
1220 {
1221 if (first)
1222 first = false;
1223 else
1224 std::cerr << QString("").leftJustified(32)
1225 .toLocal8Bit().constData();
1226
1227 std::cerr << it.key().toLocal8Bit().constData()
1228 << '='
1229 << it->toByteArray().constData()
1230 << '\n';
1231 }
1232
1233 break;
1234
1235 case QMetaType::QDateTime:
1236 std::cerr << m_stored.toDateTime().toString(Qt::ISODate)
1237 .toLocal8Bit().constData()
1238 << '\n';
1239 break;
1240
1241 default:
1242 std::cerr << '\n';
1243 }
1244}
1245
1248void CommandLineArg::PrintRemovedWarning(QString &keyword) const
1249{
1250 QString warn = QString("%1 has been removed").arg(keyword);
1251 if (!m_removedversion.isEmpty())
1252 warn += QString(" as of MythTV %1").arg(m_removedversion);
1253
1254 std::cerr << QString("****************************************************\n"
1255 " WARNING: %1\n"
1256 " %2\n"
1257 "****************************************************\n\n")
1258 .arg(warn, m_removed)
1259 .toLocal8Bit().constData();
1260}
1261
1264void CommandLineArg::PrintDeprecatedWarning(QString &keyword) const
1265{
1266 std::cerr << QString("****************************************************\n"
1267 " WARNING: %1 has been deprecated\n"
1268 " %2\n"
1269 "****************************************************\n\n")
1270 .arg(keyword, m_deprecated)
1271 .toLocal8Bit().constData();
1272}
1273
1288 : m_appname(std::move(appname))
1289{
1290 if (qEnvironmentVariableIsSet("VERBOSE_PARSER"))
1291 {
1292 std::cerr << "MythCommandLineParser is now operating verbosely.\n";
1293 m_verbose = true;
1294 }
1295
1297}
1298
1300{
1301 QString pidfile = toString("pidfile");
1302 if (!pidfile.isEmpty())
1303 {
1304 QFile::remove(pidfile);
1305 }
1306
1307 QMap<QString, CommandLineArg*>::iterator i;
1308
1309 i = m_namedArgs.begin();
1310 while (i != m_namedArgs.end())
1311 {
1312 (*i)->CleanupLinks();
1313 (*i)->DecrRef();
1314 i = m_namedArgs.erase(i);
1315 }
1316
1317 i = m_optionedArgs.begin();
1318 while (i != m_optionedArgs.end())
1319 {
1320 (*i)->DecrRef();
1321 i = m_optionedArgs.erase(i);
1322 }
1323}
1324
1359 const QString& name, QMetaType::Type type, QVariant def,
1360 QString help, QString longhelp)
1361{
1362 CommandLineArg *arg = nullptr;
1363
1364 if (m_namedArgs.contains(name))
1365 {
1366 arg = m_namedArgs[name];
1367 }
1368 else
1369 {
1370 arg = new CommandLineArg(name, type, std::move(def), std::move(help), std::move(longhelp));
1371 m_namedArgs.insert(name, arg);
1372 }
1373
1374 for (const auto & str : std::as_const(arglist))
1375 {
1376 if (!m_optionedArgs.contains(str))
1377 {
1378 arg->AddKeyword(str);
1379 if (m_verbose)
1380 {
1381 std::cerr << "Adding " << str.toLocal8Bit().constData()
1382 << " as taking type '"
1383#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1384 << QVariant::typeToName(static_cast<int>(type))
1385#else
1386 << QMetaType(type).name()
1387#endif
1388 << "'\n";
1389 }
1390 arg->IncrRef();
1391 m_optionedArgs.insert(str, arg);
1392 }
1393 }
1394
1395 return arg;
1396}
1397
1401{
1402 std::cout << "Please attach all output as a file in bug reports.\n";
1403 std::cout << "MythTV Version : " << GetMythSourceVersion() << '\n';
1404 std::cout << "MythTV Branch : " << GetMythSourcePath() << '\n';
1405 std::cout << "Network Protocol : " << MYTH_PROTO_VERSION << '\n';
1406 std::cout << "Library API : " << MYTH_BINARY_VERSION << '\n';
1407 std::cout << "QT Version : " << QT_VERSION_STR << '\n';
1408#ifdef MYTH_BUILD_CONFIG
1409 std::cout << "Options compiled in:\n";
1410 std::cout << MYTH_BUILD_CONFIG << '\n';
1411#endif
1412}
1413
1417{
1418 QString help = GetHelpString();
1419 std::cerr << help.toLocal8Bit().constData();
1420}
1421
1428{
1429 QString helpstr;
1430#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1431 QTextStream msg(&helpstr, QIODevice::WriteOnly);
1432#else
1433 QTextStream msg(&helpstr, QIODeviceBase::WriteOnly);
1434#endif
1435
1436 QString versionStr = QString("%1 version: %2 [%3] www.mythtv.org")
1438 msg << versionStr << Qt::endl;
1439
1440 if (toString("showhelp").isEmpty())
1441 {
1442 // build generic help text
1443
1444 QString descr = GetHelpHeader();
1445 if (descr.size() > 0)
1446 msg << Qt::endl << descr << Qt::endl << Qt::endl;
1447
1448 // loop through registered arguments to populate list of groups
1449 QStringList groups("");
1450 int maxlen = 0;
1451 for (auto * cmdarg : std::as_const(m_namedArgs))
1452 {
1453 maxlen = std::max(cmdarg->GetKeywordLength(), maxlen);
1454 if (!groups.contains(cmdarg->m_group))
1455 groups << cmdarg->m_group;
1456 }
1457
1458 // loop through list of groups and print help string for each
1459 // arguments will filter themselves if they are not in the group
1460 maxlen += 4;
1461 for (const auto & group : std::as_const(groups))
1462 {
1463 if (group.isEmpty())
1464 msg << "Misc. Options:" << Qt::endl << Qt::endl;
1465 else
1466 msg << group.toLocal8Bit().constData() << " Options:" << Qt::endl << Qt::endl;
1467
1468 for (auto * cmdarg : std::as_const(m_namedArgs))
1469 msg << cmdarg->GetHelpString(maxlen, group);
1470 msg << Qt::endl;
1471 }
1472 }
1473 else
1474 {
1475 // build help for a specific argument
1476 QString optstr = "-" + toString("showhelp");
1477 if (!m_optionedArgs.contains(optstr))
1478 {
1479 optstr = "-" + optstr;
1480 if (!m_optionedArgs.contains(optstr))
1481 return QString("Could not find option matching '%1'\n")
1482 .arg(toString("showhelp"));
1483 }
1484
1485 if (m_optionedArgs[optstr] != nullptr)
1486 msg << m_optionedArgs[optstr]->GetLongHelpString(optstr);
1487 }
1488
1489 msg.flush();
1490 return helpstr;
1491}
1492
1496 int &argpos, QString &opt, QByteArray &val)
1497{
1498 opt.clear();
1499 val.clear();
1500
1501 if (argpos >= argc)
1502 // this shouldnt happen, return and exit
1503 return Result::kEnd;
1504
1505 QByteArray tmp(argv[argpos]);
1506 if (tmp.isEmpty())
1507 // string is empty, return and loop
1508 return Result::kEmpty;
1509
1511 {
1512 // pass through has been activated
1513 val = tmp;
1514 return Result::kArg;
1515 }
1516
1517 if (tmp.startsWith('-') && tmp.size() > 1)
1518 {
1519 if (tmp == "--")
1520 {
1521 // all options beyond this will be passed as a single string
1522 m_passthroughActive = true;
1523 return Result::kPassthrough;
1524 }
1525
1526 if (tmp.contains('='))
1527 {
1528 // option contains '=', split
1529 QList<QByteArray> blist = tmp.split('=');
1530
1531 if (blist.size() != 2)
1532 {
1533 // more than one '=' in option, this is not handled
1534 opt = QString(tmp);
1535 return Result::kInvalid;
1536 }
1537
1538 opt = QString(strip_quotes(blist[0]));
1539 val = strip_quotes(blist[1]);
1540 return Result::kCombOptVal;
1541 }
1542
1543 opt = QString(tmp);
1544
1545 if (argpos+1 >= argc)
1546 // end of input, option only
1547 return Result::kOptOnly;
1548
1549 tmp = QByteArray(argv[++argpos]);
1550 if (tmp.isEmpty())
1551 // empty string, option only
1552 return Result::kOptOnly;
1553
1554 if (tmp.startsWith("-") && tmp.size() > 1)
1555 {
1556 // no value found for option, backtrack
1557 argpos--;
1558 return Result::kOptOnly;
1559 }
1560
1561 val = tmp;
1562 return Result::kOptVal;
1563 }
1564
1565 // input is not an option string, return as arg
1566 val = tmp;
1567 return Result::kArg;
1568}
1569
1576bool MythCommandLineParser::Parse(int argc, const char * const * argv)
1577{
1578 Result res = Result::kEnd;
1579 QString opt;
1580 QByteArray val;
1581 CommandLineArg *argdef = nullptr;
1582
1583 // reconnect interdependencies between command line options
1584 if (!ReconcileLinks())
1585 return false;
1586
1587 // loop through command line arguments until all are spent
1588 for (int argpos = 1; argpos < argc; ++argpos)
1589 {
1590
1591 // pull next option
1592 res = getOpt(argc, argv, argpos, opt, val);
1593
1594 if (m_verbose)
1595 {
1596 std::cerr << "res: " << NamedOptType(res) << '\n'
1597 << "opt: " << opt.toLocal8Bit().constData() << '\n'
1598 << "val: " << val.constData() << "\n\n";
1599 }
1600
1601 // '--' found on command line, enable passthrough mode
1602 if (res == Result::kPassthrough && !m_namedArgs.contains("_passthrough"))
1603 {
1604 std::cerr << "Received '--' but passthrough has not been enabled\n";
1605 SetValue("showhelp", "");
1606 return false;
1607 }
1608
1609 // end of options found, terminate loop
1610 if (res == Result::kEnd)
1611 break;
1612
1613 // GetOpt pulled an empty option, this shouldnt happen by ignore
1614 // it and continue
1615 if (res == Result::kEmpty)
1616 continue;
1617
1618 // more than one equal found in key/value pair, fault out
1619 if (res == Result::kInvalid)
1620 {
1621 std::cerr << "Invalid option received:\n "
1622 << opt.toLocal8Bit().constData();
1623 SetValue("showhelp", "");
1624 return false;
1625 }
1626
1627 // passthrough is active, so add the data to the stringlist
1629 {
1630 m_namedArgs["_passthrough"]->Set("", val);
1631 continue;
1632 }
1633
1634 // argument with no preceeding '-' encountered, add to stringlist
1635 if (res == Result::kArg)
1636 {
1637 if (!m_namedArgs.contains("_args"))
1638 {
1639 std::cerr << "Received '"
1640 << val.constData()
1641 << "' but unassociated arguments have not been enabled\n";
1642 SetValue("showhelp", "");
1643 return false;
1644 }
1645
1646 m_namedArgs["_args"]->Set("", val);
1647 continue;
1648 }
1649
1650 // this line should not be passed once arguments have started collecting
1651 if (toBool("_args"))
1652 {
1653 std::cerr << "Command line arguments received out of sequence\n";
1654 SetValue("showhelp", "");
1655 return false;
1656 }
1657
1658#ifdef Q_OS_DARWIN
1659 if (opt.startsWith("-psn_"))
1660 {
1661 std::cerr << "Ignoring Process Serial Number from command line\n";
1662 continue;
1663 }
1664#endif
1665
1666 if (!m_optionedArgs.contains(opt))
1667 {
1668 // argument is unhandled, check if parser allows arbitrary input
1669 if (m_namedArgs.contains("_extra"))
1670 {
1671 // arbitrary allowed, specify general collection pool
1672 argdef = m_namedArgs["_extra"];
1673 QByteArray tmp = opt.toLocal8Bit();
1674 tmp += '=';
1675 tmp += val;
1676 val = tmp;
1677 res = Result::kOptVal;
1678 }
1679 else
1680 {
1681 // arbitrary not allowed, fault out
1682 std::cerr << "Unhandled option given on command line:\n"
1683 << " " << opt.toLocal8Bit().constData() << '\n';
1684 SetValue("showhelp", "");
1685 return false;
1686 }
1687 }
1688 else
1689 {
1690 argdef = m_optionedArgs[opt];
1691 }
1692
1693 // argument has been marked as removed, warn user and fail
1694 if (!argdef->m_removed.isEmpty())
1695 {
1696 argdef->PrintRemovedWarning(opt);
1697 SetValue("showhelp", "");
1698 return false;
1699 }
1700
1701 // argument has been marked as deprecated, warn user
1702 if (!argdef->m_deprecated.isEmpty())
1703 argdef->PrintDeprecatedWarning(opt);
1704
1705 if (m_verbose)
1706 std::cerr << "name: " << argdef->GetName().toLocal8Bit().constData()
1707 << '\n';
1708
1709 // argument is keyword only, no value
1710 if (res == Result::kOptOnly)
1711 {
1712 if (!argdef->Set(opt))
1713 {
1714 SetValue("showhelp", "");
1715 return false;
1716 }
1717 }
1718 // argument has keyword and value
1719 else if ((res == Result::kOptVal) || (res == Result::kCombOptVal))
1720 {
1721 if (!argdef->Set(opt, val))
1722 {
1723 // if option and value were combined with a '=', abort directly
1724 // otherwise, attempt processing them independenly
1725 if ((res == Result::kCombOptVal) || !argdef->Set(opt))
1726 {
1727 SetValue("showhelp", "");
1728 return false;
1729 }
1730 // drop back an iteration so the unused value will get
1731 // processed a second time as a keyword-less argument
1732 --argpos;
1733 }
1734 }
1735 else
1736 {
1737 SetValue("showhelp", "");
1738 return false; // this should not occur
1739 }
1740
1741 if (m_verbose)
1742 std::cerr << "value: " << argdef->m_stored.toString().toLocal8Bit().constData()
1743 << '\n';
1744 }
1745
1746 if (m_verbose)
1747 {
1748 std::cerr << "Processed option list:\n";
1749 for (auto * cmdarg : std::as_const(m_namedArgs))
1750 cmdarg->PrintVerbose();
1751
1752 if (m_namedArgs.contains("_args"))
1753 {
1754 std::cerr << "\nExtra argument list:\n";
1755 QStringList slist = toStringList("_args");
1756 for (const auto& lopt : std::as_const(slist))
1757 std::cerr << " " << lopt.toLocal8Bit().constData() << '\n';
1758 }
1759
1760 if (m_namedArgs.contains("_passthrough"))
1761 {
1762 std::cerr << "\nPassthrough string:\n";
1763 std::cerr << " " << GetPassthrough().toLocal8Bit().constData() << '\n';
1764 }
1765
1766 std::cerr << '\n';
1767 }
1768
1769 // make sure all interdependencies are fulfilled
1770 for (auto * cmdarg : std::as_const(m_namedArgs))
1771 {
1772 if (!cmdarg->TestLinks())
1773 {
1774 QString keyword = cmdarg->m_usedKeyword;
1775 if (keyword.startsWith('-'))
1776 {
1777 if (keyword.startsWith("--"))
1778 keyword.remove(0,2);
1779 else
1780 keyword.remove(0,1);
1781 }
1782
1783 SetValue("showhelp", keyword);
1784 return false;
1785 }
1786 }
1787
1788 return true;
1789}
1790
1791CommandLineArg* MythCommandLineParser::add(const QString& arg, const QString& name, bool def,
1792 QString help, QString longhelp)
1793{
1794 return add(QStringList(arg), name, QMetaType::Bool, QVariant(def), std::move(help), std::move(longhelp));
1795}
1796
1797CommandLineArg* MythCommandLineParser::add(const QString& arg, const QString& name, int def,
1798 QString help, QString longhelp)
1799{
1800 return add(QStringList(arg), name, QMetaType::Int, QVariant(def), std::move(help), std::move(longhelp));
1801}
1802
1803CommandLineArg* MythCommandLineParser::add(const QString& arg, const QString& name, uint def,
1804 QString help, QString longhelp)
1805{
1806 return add(QStringList(arg), name, QMetaType::UInt, QVariant(def), std::move(help), std::move(longhelp));
1807}
1808
1809CommandLineArg* MythCommandLineParser::add(const QString& arg, const QString& name, long long def,
1810 QString help, QString longhelp)
1811{
1812 return add(QStringList(arg), name, QMetaType::LongLong, QVariant(def), std::move(help), std::move(longhelp));
1813}
1814
1815CommandLineArg* MythCommandLineParser::add(const QString& arg, const QString& name, double def,
1816 QString help, QString longhelp)
1817{
1818 return add(QStringList(arg), name, QMetaType::Double, QVariant(def), std::move(help), std::move(longhelp));
1819}
1820
1821CommandLineArg* MythCommandLineParser::add(const QString& arg, const QString& name, const char *def,
1822 QString help, QString longhelp)
1823{
1824 return add(QStringList(arg), name, QMetaType::QString, QVariant(def), std::move(help), std::move(longhelp));
1825}
1826
1827CommandLineArg* MythCommandLineParser::add(const QString& arg, const QString& name, const QString& def,
1828 QString help, QString longhelp)
1829{
1830 return add(QStringList(arg), name, QMetaType::QString, QVariant(def), std::move(help), std::move(longhelp));
1831}
1832
1833CommandLineArg* MythCommandLineParser::add(const QString& arg, const QString& name, QSize def,
1834 QString help, QString longhelp)
1835{
1836 return add(QStringList(arg), name, QMetaType::QSize, QVariant(def), std::move(help), std::move(longhelp));
1837}
1838
1839CommandLineArg* MythCommandLineParser::add(const QString& arg, const QString& name, const QDateTime& def,
1840 QString help, QString longhelp)
1841{
1842 return add(QStringList(arg), name, QMetaType::QDateTime, QVariant(def), std::move(help), std::move(longhelp));
1843}
1844
1845CommandLineArg* MythCommandLineParser::add(const QString& arg, const QString& name, QMetaType::Type type,
1846 QString help, QString longhelp)
1847{
1848 return add(QStringList(arg), name, type,
1849#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1850 QVariant(static_cast<QVariant::Type>(type)),
1851#else
1852 QVariant(QMetaType(type)),
1853#endif
1854 std::move(help), std::move(longhelp));
1855}
1856
1857CommandLineArg* MythCommandLineParser::add(const QString& arg, const QString& name,
1858 QMetaType::Type type,
1859 QVariant def, QString help, QString longhelp)
1860{
1861 return add(QStringList(arg), name, type, std::move(def), std::move(help), std::move(longhelp));
1862}
1863
1864CommandLineArg* MythCommandLineParser::add(QStringList arglist, const QString& name, bool def,
1865 QString help, QString longhelp)
1866{
1867 return add(std::move(arglist), name, QMetaType::Bool, QVariant(def), std::move(help), std::move(longhelp));
1868}
1869
1870CommandLineArg* MythCommandLineParser::add(QStringList arglist, const QString& name, int def,
1871 QString help, QString longhelp)
1872{
1873 return add(std::move(arglist), name, QMetaType::Int, QVariant(def), std::move(help), std::move(longhelp));
1874}
1875
1876CommandLineArg* MythCommandLineParser::add(QStringList arglist, const QString& name, uint def,
1877 QString help, QString longhelp)
1878{
1879 return add(std::move(arglist), name, QMetaType::UInt, QVariant(def), std::move(help), std::move(longhelp));
1880}
1881
1882CommandLineArg* MythCommandLineParser::add(QStringList arglist, const QString& name, long long def,
1883 QString help, QString longhelp)
1884{
1885 return add(std::move(arglist), name, QMetaType::LongLong, QVariant(def), std::move(help), std::move(longhelp));
1886}
1887
1888CommandLineArg* MythCommandLineParser::add(QStringList arglist, const QString& name, double def,
1889 QString help, QString longhelp)
1890{
1891 return add(std::move(arglist), name, QMetaType::Double, QVariant(def), std::move(help), std::move(longhelp));
1892}
1893
1894CommandLineArg* MythCommandLineParser::add(QStringList arglist, const QString& name, const char *def,
1895 QString help, QString longhelp)
1896{
1897 return add(std::move(arglist), name, QMetaType::QString, QVariant(def), std::move(help), std::move(longhelp));
1898}
1899
1900CommandLineArg* MythCommandLineParser::add(QStringList arglist, const QString& name, const QString& def,
1901 QString help, QString longhelp)
1902{
1903 return add(std::move(arglist), name, QMetaType::QString, QVariant(def), std::move(help), std::move(longhelp));
1904}
1905
1906CommandLineArg* MythCommandLineParser::add(QStringList arglist, const QString& name, QSize def,
1907 QString help, QString longhelp)
1908{
1909 return add(std::move(arglist), name, QMetaType::QSize, QVariant(def), std::move(help), std::move(longhelp));
1910}
1911
1912CommandLineArg* MythCommandLineParser::add(QStringList arglist, const QString& name, const QDateTime& def,
1913 QString help, QString longhelp)
1914{
1915 return add(std::move(arglist), name, QMetaType::QDateTime, QVariant(def), std::move(help), std::move(longhelp));
1916}
1917
1918CommandLineArg* MythCommandLineParser::add(QStringList arglist, const QString& name,
1919 QMetaType::Type type,
1920 QString help, QString longhelp)
1921{
1922 return add(std::move(arglist), name, type,
1923#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1924 QVariant(static_cast<QVariant::Type>(type)),
1925#else
1926 QVariant(QMetaType(type)),
1927#endif
1928 std::move(help), std::move(longhelp));
1929}
1930
1935{
1936 if (m_verbose)
1937 std::cerr << "Reconciling links for option interdependencies.\n";
1938
1939 QMap<QString,CommandLineArg*>::iterator args_it;
1940 for (args_it = m_namedArgs.begin(); args_it != m_namedArgs.end(); ++args_it)
1941 {
1942 QList<CommandLineArg*> links = (*args_it)->m_parents;
1943 QList<CommandLineArg*>::iterator links_it;
1944 for (links_it = links.begin(); links_it != links.end(); ++links_it)
1945 {
1946 if ((*links_it)->m_type != QMetaType::UnknownType)
1947 continue; // already handled
1948
1949 if (!m_namedArgs.contains((*links_it)->m_name))
1950 {
1951 // not found
1952 std::cerr << "ERROR: could not reconcile linked argument.\n"
1953 << " '" << (*args_it)->m_name.toLocal8Bit().constData()
1954 << "' could not find '"
1955 << (*links_it)->m_name.toLocal8Bit().constData()
1956 << "'.\n"
1957 << " Please resolve dependency and recompile.\n";
1958 return false;
1959 }
1960
1961 // replace linked argument
1962 if (m_verbose)
1963 {
1964 std::cerr << QString(" Setting %1 as child of %2")
1965 .arg((*args_it)->m_name, (*links_it)->m_name)
1966 .toLocal8Bit().constData()
1967 << '\n';
1968 }
1969 (*args_it)->SetChildOf(m_namedArgs[(*links_it)->m_name]);
1970 }
1971
1972 links = (*args_it)->m_children;
1973 for (links_it = links.begin(); links_it != links.end(); ++links_it)
1974 {
1975 if ((*links_it)->m_type != QMetaType::UnknownType)
1976 continue; // already handled
1977
1978 if (!m_namedArgs.contains((*links_it)->m_name))
1979 {
1980 // not found
1981 std::cerr << "ERROR: could not reconcile linked argument.\n"
1982 << " '" << (*args_it)->m_name.toLocal8Bit().constData()
1983 << "' could not find '"
1984 << (*links_it)->m_name.toLocal8Bit().constData()
1985 << "'.\n"
1986 << " Please resolve dependency and recompile.\n";
1987 return false;
1988 }
1989
1990 // replace linked argument
1991 if (m_verbose)
1992 {
1993 std::cerr << QString(" Setting %1 as parent of %2")
1994 .arg((*args_it)->m_name, (*links_it)->m_name)
1995 .toLocal8Bit().constData()
1996 << '\n';
1997 }
1998 (*args_it)->SetParentOf(m_namedArgs[(*links_it)->m_name]);
1999 }
2000
2001 links = (*args_it)->m_requires;
2002 for (links_it = links.begin(); links_it != links.end(); ++links_it)
2003 {
2004 if ((*links_it)->m_type != QMetaType::UnknownType)
2005 continue; // already handled
2006
2007 if (!m_namedArgs.contains((*links_it)->m_name))
2008 {
2009 // not found
2010 std::cerr << "ERROR: could not reconcile linked argument.\n"
2011 << " '" << (*args_it)->m_name.toLocal8Bit().constData()
2012 << "' could not find '"
2013 << (*links_it)->m_name.toLocal8Bit().constData()
2014 << "'.\n"
2015 << " Please resolve dependency and recompile.\n";
2016 return false;
2017 }
2018
2019 // replace linked argument
2020 if (m_verbose)
2021 {
2022 std::cerr << QString(" Setting %1 as requiring %2")
2023 .arg((*args_it)->m_name, (*links_it)->m_name)
2024 .toLocal8Bit().constData()
2025 << '\n';
2026 }
2027 (*args_it)->SetRequires(m_namedArgs[(*links_it)->m_name]);
2028 }
2029
2030 QList<CommandLineArg*>::iterator req_it =
2031 (*args_it)->m_requiredby.begin();
2032 while (req_it != (*args_it)->m_requiredby.end())
2033 {
2034 if ((*req_it)->m_type == QMetaType::UnknownType)
2035 {
2036 // if its not an invalid, it shouldnt be here anyway
2037 if (m_namedArgs.contains((*req_it)->m_name))
2038 {
2039 m_namedArgs[(*req_it)->m_name]->SetRequires(*args_it);
2040 if (m_verbose)
2041 {
2042 std::cerr << QString(" Setting %1 as blocking %2")
2043 .arg((*args_it)->m_name,
2044 (*req_it)->m_name)
2045 .toLocal8Bit().constData()
2046 << '\n';
2047 }
2048 }
2049 }
2050
2051 (*req_it)->DecrRef();
2052 req_it = (*args_it)->m_requiredby.erase(req_it);
2053 }
2054
2055 QList<CommandLineArg*>::iterator block_it =
2056 (*args_it)->m_blocks.begin();
2057 while (block_it != (*args_it)->m_blocks.end())
2058 {
2059 if ((*block_it)->m_type != QMetaType::UnknownType)
2060 {
2061 ++block_it;
2062 continue; // already handled
2063 }
2064
2065 if (!m_namedArgs.contains((*block_it)->m_name))
2066 {
2067 (*block_it)->DecrRef();
2068 block_it = (*args_it)->m_blocks.erase(block_it);
2069 continue; // if it doesnt exist, it cant block this command
2070 }
2071
2072 // replace linked argument
2073 if (m_verbose)
2074 {
2075 std::cerr << QString(" Setting %1 as blocking %2")
2076 .arg((*args_it)->m_name, (*block_it)->m_name)
2077 .toLocal8Bit().constData()
2078 << '\n';
2079 }
2080 (*args_it)->SetBlocks(m_namedArgs[(*block_it)->m_name]);
2081 ++block_it;
2082 }
2083 }
2084
2085 return true;
2086}
2087
2091QVariant MythCommandLineParser::operator[](const QString &name)
2092{
2093 QVariant var("");
2094 if (!m_namedArgs.contains(name))
2095 return var;
2096
2097 CommandLineArg *arg = m_namedArgs[name];
2098
2099 if (arg->m_given)
2100 var = arg->m_stored;
2101 else
2102 var = arg->m_default;
2103
2104 return var;
2105}
2106
2110QStringList MythCommandLineParser::GetArgs(void) const
2111{
2112 return toStringList("_args");
2113}
2114
2118QMap<QString,QString> MythCommandLineParser::GetExtra(void) const
2119{
2120 return toMap("_extra");
2121}
2122
2126{
2127 return toStringList("_passthrough").join(" ");
2128}
2129
2138{
2139 QMap<QString,QString> smap = toMap("overridesettings");
2140
2142 {
2143 if (toBool("overridesettingsfile"))
2144 {
2145 QString filename = toString("overridesettingsfile");
2146 if (!filename.isEmpty())
2147 {
2148 QFile f(filename);
2149 if (f.open(QIODevice::ReadOnly))
2150 {
2151 QTextStream in(&f);
2152 while (!in.atEnd()) {
2153 QString line = in.readLine().trimmed();
2154 QStringList tokens = line.split("=",
2155 Qt::SkipEmptyParts);
2156 if (tokens.size() == 2)
2157 {
2158 static const QRegularExpression kQuoteStartRE { "^[\"']" };
2159 static const QRegularExpression kQuoteEndRE { "[\"']$" };
2160 tokens[0].remove(kQuoteStartRE);
2161 tokens[0].remove(kQuoteEndRE);
2162 tokens[1].remove(kQuoteStartRE);
2163 tokens[1].remove(kQuoteEndRE);
2164 if (!tokens[0].isEmpty())
2165 smap[tokens[0]] = tokens[1];
2166 }
2167 }
2168 }
2169 else
2170 {
2171 QByteArray tmp = filename.toLatin1();
2172 std::cerr << "Failed to open the override settings file: '"
2173 << tmp.constData() << "'\n";
2174 }
2175 }
2176 }
2177
2178 if (toBool("windowed"))
2179 smap["RunFrontendInWindow"] = "1";
2180 else if (toBool("notwindowed"))
2181 smap["RunFrontendInWindow"] = "0";
2182
2183 if (toBool("mousecursor"))
2184 smap["HideMouseCursor"] = "0";
2185 else if (toBool("nomousecursor"))
2186 smap["HideMouseCursor"] = "1";
2187
2188 m_overridesImported = true;
2189
2190 if (!smap.isEmpty())
2191 {
2192 QVariantMap vmap;
2193 for (auto it = smap.cbegin(); it != smap.cend(); ++it)
2194 vmap[it.key()] = QVariant(it.value());
2195
2196 m_namedArgs["overridesettings"]->Set(QVariant(vmap));
2197 }
2198 }
2199
2200 if (m_verbose)
2201 {
2202 std::cerr << "Option Overrides:\n";
2203 QMap<QString, QString>::const_iterator it;
2204 for (it = smap.constBegin(); it != smap.constEnd(); ++it)
2205 std::cerr << QString(" %1 - %2").arg(it.key(), 30).arg(*it)
2206 .toLocal8Bit().constData() << '\n';
2207 }
2208
2209 return smap;
2210}
2211
2218bool MythCommandLineParser::toBool(const QString& key) const
2219{
2220 if (!m_namedArgs.contains(key))
2221 return false;
2222
2223 CommandLineArg *arg = m_namedArgs[key];
2224 if (arg == nullptr)
2225 return false;
2226
2227 if (arg->m_type == QMetaType::Bool)
2228 {
2229 if (arg->m_given)
2230 return arg->m_stored.toBool();
2231 return arg->m_default.toBool();
2232 }
2233
2234 return arg->m_given;
2235}
2236
2240int MythCommandLineParser::toInt(const QString& key) const
2241{
2242 int val = 0;
2243 if (!m_namedArgs.contains(key))
2244 return val;
2245
2246 CommandLineArg *arg = m_namedArgs[key];
2247 if (arg == nullptr)
2248 return val;
2249
2250 if (arg->m_given)
2251 {
2252 if (arg->m_stored.canConvert<int>())
2253 val = arg->m_stored.toInt();
2254 }
2255 else
2256 {
2257 if (arg->m_default.canConvert<int>())
2258 val = arg->m_default.toInt();
2259 }
2260
2261 return val;
2262}
2263
2267uint MythCommandLineParser::toUInt(const QString& key) const
2268{
2269 uint val = 0;
2270 if (!m_namedArgs.contains(key))
2271 return val;
2272
2273 CommandLineArg *arg = m_namedArgs[key];
2274 if (arg == nullptr)
2275 return val;
2276
2277 if (arg->m_given)
2278 {
2279 if (arg->m_stored.canConvert<uint>())
2280 val = arg->m_stored.toUInt();
2281 }
2282 else
2283 {
2284 if (arg->m_default.canConvert<uint>())
2285 val = arg->m_default.toUInt();
2286 }
2287
2288 return val;
2289}
2290
2294long long MythCommandLineParser::toLongLong(const QString& key) const
2295{
2296 long long val = 0;
2297 if (!m_namedArgs.contains(key))
2298 return val;
2299
2300 CommandLineArg *arg = m_namedArgs[key];
2301 if (arg == nullptr)
2302 return val;
2303
2304 if (arg->m_given)
2305 {
2306 if (arg->m_stored.canConvert<long long>())
2307 val = arg->m_stored.toLongLong();
2308 }
2309 else
2310 {
2311 if (arg->m_default.canConvert<long long>())
2312 val = arg->m_default.toLongLong();
2313 }
2314
2315 return val;
2316}
2317
2321double MythCommandLineParser::toDouble(const QString& key) const
2322{
2323 double val = 0.0;
2324 if (!m_namedArgs.contains(key))
2325 return val;
2326
2327 CommandLineArg *arg = m_namedArgs[key];
2328 if (arg == nullptr)
2329 return val;
2330
2331 if (arg->m_given)
2332 {
2333 if (arg->m_stored.canConvert<double>())
2334 val = arg->m_stored.toDouble();
2335 }
2336 else
2337 {
2338 if (arg->m_default.canConvert<double>())
2339 val = arg->m_default.toDouble();
2340 }
2341
2342 return val;
2343}
2344
2348QSize MythCommandLineParser::toSize(const QString& key) const
2349{
2350 QSize val(0,0);
2351 if (!m_namedArgs.contains(key))
2352 return val;
2353
2354 CommandLineArg *arg = m_namedArgs[key];
2355 if (arg == nullptr)
2356 return val;
2357
2358 if (arg->m_given)
2359 {
2360 if (arg->m_stored.canConvert<QSize>())
2361 val = arg->m_stored.toSize();
2362 }
2363 else
2364 {
2365 if (arg->m_default.canConvert<QSize>())
2366 val = arg->m_default.toSize();
2367 }
2368
2369 return val;
2370}
2371
2375QString MythCommandLineParser::toString(const QString& key) const
2376{
2377 QString val("");
2378 if (!m_namedArgs.contains(key))
2379 return val;
2380
2381 CommandLineArg *arg = m_namedArgs[key];
2382 if (arg == nullptr)
2383 return val;
2384
2385 if (arg->m_given)
2386 {
2387 if (!arg->m_converted)
2388 arg->Convert();
2389
2390 if (arg->m_stored.canConvert<QString>())
2391 val = arg->m_stored.toString();
2392 }
2393 else
2394 {
2395 if (arg->m_default.canConvert<QString>())
2396 val = arg->m_default.toString();
2397 }
2398
2399 return val;
2400}
2401
2406QStringList MythCommandLineParser::toStringList(const QString& key, const QString& sep) const
2407{
2408 QVariant varval;
2409 QStringList val;
2410 if (!m_namedArgs.contains(key))
2411 return val;
2412
2413 CommandLineArg *arg = m_namedArgs[key];
2414 if (arg == nullptr)
2415 return val;
2416
2417 if (arg->m_given)
2418 {
2419 if (!arg->m_converted)
2420 arg->Convert();
2421
2422 varval = arg->m_stored;
2423 }
2424 else
2425 {
2426 varval = arg->m_default;
2427 }
2428
2429 if (arg->m_type == QMetaType::QString && !sep.isEmpty())
2430 val = varval.toString().split(sep);
2431 else if (varval.canConvert<QStringList>())
2432 val = varval.toStringList();
2433
2434 return val;
2435}
2436
2440QMap<QString,QString> MythCommandLineParser::toMap(const QString& key) const
2441{
2442 QMap<QString, QString> val;
2443 QMap<QString, QVariant> tmp;
2444 if (!m_namedArgs.contains(key))
2445 return val;
2446
2447 CommandLineArg *arg = m_namedArgs[key];
2448 if (arg == nullptr)
2449 return val;
2450
2451 if (arg->m_given)
2452 {
2453 if (!arg->m_converted)
2454 arg->Convert();
2455
2456 if (arg->m_stored.canConvert<QMap<QString, QVariant>>())
2457 tmp = arg->m_stored.toMap();
2458 }
2459 else
2460 {
2461 if (arg->m_default.canConvert<QMap<QString, QVariant>>())
2462 tmp = arg->m_default.toMap();
2463 }
2464
2465 for (auto i = tmp.cbegin(); i != tmp.cend(); ++i)
2466 val[i.key()] = i.value().toString();
2467
2468 return val;
2469}
2470
2474QDateTime MythCommandLineParser::toDateTime(const QString& key) const
2475{
2476 QDateTime val;
2477 if (!m_namedArgs.contains(key))
2478 return val;
2479
2480 CommandLineArg *arg = m_namedArgs[key];
2481 if (arg == nullptr)
2482 return val;
2483
2484 if (arg->m_given)
2485 {
2486 if (arg->m_stored.canConvert<QDateTime>())
2487 val = arg->m_stored.toDateTime();
2488 }
2489 else
2490 {
2491 if (arg->m_default.canConvert<QDateTime>())
2492 val = arg->m_default.toDateTime();
2493 }
2494
2495 return val;
2496}
2497
2502{
2503 if (m_namedArgs.contains("_args"))
2504 {
2505 if (!allow)
2506 m_namedArgs.remove("_args");
2507 }
2508 else if (!allow)
2509 {
2510 return;
2511 }
2512
2513 auto *arg = new CommandLineArg("_args", QMetaType::QStringList, QStringList());
2514 m_namedArgs["_args"] = arg;
2515}
2516
2521{
2522 if (m_namedArgs.contains("_extra"))
2523 {
2524 if (!allow)
2525 m_namedArgs.remove("_extra");
2526 }
2527 else if (!allow)
2528 {
2529 return;
2530 }
2531
2532 QMap<QString,QVariant> vmap;
2533 auto *arg = new CommandLineArg("_extra", QMetaType::QVariantMap, vmap);
2534
2535 m_namedArgs["_extra"] = arg;
2536}
2537
2542{
2543 if (m_namedArgs.contains("_passthrough"))
2544 {
2545 if (!allow)
2546 m_namedArgs.remove("_passthrough");
2547 }
2548 else if (!allow)
2549 {
2550 return;
2551 }
2552
2553 auto *arg = new CommandLineArg("_passthrough",
2554 QMetaType::QStringList, QStringList());
2555 m_namedArgs["_passthrough"] = arg;
2556}
2557
2561{
2562 add(QStringList{"-h", "--help", "--usage"},
2563 "showhelp", "", "Display this help printout, or give detailed "
2564 "information of selected option.",
2565 "Displays a list of all commands available for use with "
2566 "this application. If another option is provided as an "
2567 "argument, it will provide detailed information on that "
2568 "option.");
2569}
2570
2574{
2575 add("--version", "showversion", false, "Display version information.",
2576 "Display informtion about build, including:\n"
2577 " version, branch, protocol, library API, Qt "
2578 "and compiled options.");
2579}
2580
2584{
2585 add(QStringList{"-nw", "--no-windowed"},
2586 "notwindowed", false,
2587 "Prevent application from running in a window.", "")
2588 ->SetBlocks("windowed")
2589 ->SetGroup("User Interface");
2590
2591 add(QStringList{"-w", "--windowed"}, "windowed",
2592 false, "Force application to run in a window.", "")
2593 ->SetGroup("User Interface");
2594}
2595
2599{
2600 add("--mouse-cursor", "mousecursor", false,
2601 "Force visibility of the mouse cursor.", "")
2602 ->SetBlocks("nomousecursor")
2603 ->SetGroup("User Interface");
2604
2605 add("--no-mouse-cursor", "nomousecursor", false,
2606 "Force the mouse cursor to be hidden.", "")
2607 ->SetGroup("User Interface");
2608}
2609
2613{
2614 add(QStringList{"-d", "--daemon"}, "daemon", false,
2615 "Fork application into background after startup.",
2616 "Fork application into background, detatching from "
2617 "the local terminal.\nOften used with: "
2618 " --logpath --pidfile --user");
2619}
2620
2625{
2626 add(QStringList{"-O", "--override-setting"},
2627 "overridesettings", QMetaType::QVariantMap,
2628 "Override a single setting defined by a key=value pair.",
2629 "Override a single setting from the database using "
2630 "options defined as one or more key=value pairs\n"
2631 "Multiple can be defined by multiple uses of the "
2632 "-O option.");
2633 add("--override-settings-file", "overridesettingsfile", "",
2634 "Define a file of key=value pairs to be "
2635 "loaded for setting overrides.", "");
2636}
2637
2641{
2642 add("--chanid", "chanid", 0U,
2643 "Specify chanid of recording to operate on.", "")
2644 ->SetRequires("starttime");
2645
2646 add("--starttime", "starttime", QDateTime(),
2647 "Specify start time of recording to operate on.", "")
2648 ->SetRequires("chanid");
2649}
2650
2654{
2655 add(QStringList{"-geometry", "--geometry"}, "geometry",
2656 "", "Specify window size and position (WxH[+X+Y])", "")
2657 ->SetGroup("User Interface");
2658}
2659
2663{
2664 add("--noupnp", "noupnp", false, "Disable use of UPnP.", "");
2665}
2666
2670{
2671 add("--dvbv3", "dvbv3", false, "Use legacy DVBv3 API.", "");
2672}
2673
2678 const QString &defaultVerbosity, LogLevel_t defaultLogLevel)
2679{
2680 defaultLogLevel =
2681 ((defaultLogLevel >= LOG_UNKNOWN) || (defaultLogLevel <= LOG_ANY)) ?
2682 LOG_INFO : defaultLogLevel;
2683
2684 QString logLevelStr = logLevelGetName(defaultLogLevel);
2685
2686 add(QStringList{"-v", "--verbose"}, "verbose",
2687 defaultVerbosity,
2688 "Specify log filtering. Use '-v help' for level info.", "")
2689 ->SetGroup("Logging");
2690 add("-V", "verboseint", 0LL, "",
2691 "This option is intended for internal use only.\n"
2692 "This option takes an unsigned value corresponding "
2693 "to the bitwise log verbosity operator.")
2694 ->SetGroup("Logging");
2695 add("--logpath", "logpath", "",
2696 "Writes logging messages to a file in the directory logpath with "
2697 "filenames in the format: applicationName.date.pid.log.\n"
2698 "This is typically used in combination with --daemon, and if used "
2699 "in combination with --pidfile, this can be used with log "
2700 "rotators, using the HUP call to inform MythTV to reload the "
2701 "file", "")
2702 ->SetGroup("Logging");
2703 add(QStringList{"-q", "--quiet"}, "quiet", 0,
2704 "Don't log to the console (-q). Don't log anywhere (-q -q)", "")
2705 ->SetGroup("Logging");
2706 add("--loglong", "loglong", 0,
2707 "Use long log format for the console, i.e. show file, line number, etc. in the console log.", "")
2708 ->SetGroup("Logging");
2709 add("--loglevel", "loglevel", logLevelStr,
2710 QString(
2711 "Set the logging level. All log messages at lower levels will be "
2712 "discarded.\n"
2713 "In descending order: emerg, alert, crit, err, warning, notice, "
2714 "info, debug, trace\ndefaults to ") + logLevelStr, "")
2715 ->SetGroup("Logging");
2716 add("--syslog", "syslog", "none",
2717 "Set the syslog logging facility.\nSet to \"none\" to disable, "
2718 "defaults to none.", "")
2719 ->SetGroup("Logging");
2720#if CONFIG_SYSTEMD_JOURNAL
2721 add("--systemd-journal", "systemd-journal", "false",
2722 "Use systemd-journal instead of syslog.", "")
2723 ->SetBlocks(QStringList()
2724 << "syslog"
2725 )
2726 ->SetGroup("Logging");
2727#endif
2728 add("--nodblog", "nodblog", false, "", "")
2729 ->SetGroup("Logging")
2730 ->SetRemoved("Database logging has been removed.", "34");
2731 add("--enable-dblog", "enabledblog", false, "", "")
2732 ->SetGroup("Logging")
2733 ->SetRemoved("Database logging has been removed.", "34");
2734
2735 add(QStringList{"-l", "--logfile"},
2736 "logfile", "", "", "")
2737 ->SetGroup("Logging")
2738 ->SetRemoved("This option has been removed as part of "
2739 "rewrite of the logging interface. Please update your init "
2740 "scripts to use --syslog to interface with your system's "
2741 "existing system logging daemon, or --logpath to specify a "
2742 "dirctory for MythTV to write its logs to.", "0.25");
2743}
2744
2748{
2749 add(QStringList{"-p", "--pidfile"}, "pidfile", "",
2750 "Write PID of application to filename.",
2751 "Write the PID of the currently running process as a single "
2752 "line to this file. Used for init scripts to know what "
2753 "process to terminate, and with log rotators "
2754 "to send a HUP signal to process to have it re-open files.");
2755}
2756
2760{
2761 add(QStringList{"-j", "--jobid"}, "jobid", 0, "",
2762 "Intended for internal use only, specify the JobID to match "
2763 "up with in the database for additional information and the "
2764 "ability to update runtime status in the database.");
2765}
2766
2770{
2771 add("--infile", "infile", "", "Input file URI", "");
2772 if (addOutFile)
2773 add("--outfile", "outfile", "", "Output file URI", "");
2774}
2775
2779{
2780#if CONFIG_X11
2781 add(QStringList{"-display", "--display"}, "display", "",
2782 "Qt (QPA) X11 connection name when using xcb (X11) platform plugin", "")
2783 ->SetGroup("Qt");
2784#endif
2785}
2786
2790{
2791 add(QStringList{"-platform", "--platform"}, "platform", "", "Qt (QPA) platform argument",
2792 "Qt platform argument that is passed through to Qt")
2793 ->SetGroup("Qt");
2794}
2795
2799{
2800 QString logfile = toString("logpath");
2801 pid_t pid = getpid();
2802
2803 if (logfile.isEmpty())
2804 return logfile;
2805
2806 QFileInfo finfo(logfile);
2807 if (!finfo.isDir())
2808 {
2809 LOG(VB_GENERAL, LOG_ERR,
2810 QString("%1 is not a directory, disabling logfiles")
2811 .arg(logfile));
2812 return {};
2813 }
2814
2815 QString logdir = finfo.filePath();
2816 logfile = QCoreApplication::applicationName() + "." +
2818 QString(".%1").arg(pid) + ".log";
2819
2820 SetValue("logdir", logdir);
2821 SetValue("logfile", logfile);
2822 SetValue("filepath", QFileInfo(QDir(logdir), logfile).filePath());
2823
2824 return toString("filepath");
2825}
2826
2830{
2831 QString setting = toString("syslog").toLower();
2832 if (setting == "none")
2833 return -2;
2834
2835 return syslogGetFacility(setting);
2836}
2837
2841{
2842 QString setting = toString("loglevel");
2843 if (setting.isEmpty())
2844 return LOG_INFO;
2845
2846 LogLevel_t level = logLevelGet(setting);
2847 if (level == LOG_UNKNOWN)
2848 std::cerr << "Unknown log level: " << setting.toLocal8Bit().constData()
2849 << '\n';
2850
2851 return level;
2852}
2853
2858bool MythCommandLineParser::SetValue(const QString &key, const QVariant& value)
2859{
2860 CommandLineArg *arg = nullptr;
2861
2862 if (!m_namedArgs.contains(key))
2863 {
2864 const QVariant& val(value);
2865#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
2866 auto type = static_cast<QMetaType::Type>(val.type());
2867#else
2868 auto type = static_cast<QMetaType::Type>(val.typeId());
2869#endif
2870 arg = new CommandLineArg(key, type, val);
2871 m_namedArgs.insert(key, arg);
2872 }
2873 else
2874 {
2875 arg = m_namedArgs[key];
2876#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
2877 auto type = static_cast<QMetaType::Type>(value.type());
2878#else
2879 auto type = value.typeId();
2880#endif
2881 if (arg->m_type != type)
2882 return false;
2883 }
2884
2885 arg->Set(value);
2886 return true;
2887}
2888
2892{
2893 // Setup the defaults
2894 verboseString = "";
2895 verboseMask = 0;
2896 verboseArgParse(mask);
2897
2898 if (toBool("verbose"))
2899 {
2900 int err = verboseArgParse(toString("verbose"));
2901 if (err != 0)
2902 return err;
2903 }
2904 else if (toBool("verboseint"))
2905 {
2906 verboseMask = static_cast<uint64_t>(toLongLong("verboseint"));
2907 }
2908
2909 verboseMask |= VB_STDIO|VB_FLUSH;
2910
2911 int quiet = toInt("quiet");
2912 if (std::max(quiet, static_cast<int>(progress)) > 1)
2913 {
2914 verboseMask = VB_NONE|VB_FLUSH;
2915 verboseArgParse("none");
2916 }
2917
2918 bool loglong = toBool("loglong");
2919
2920 int facility = GetSyslogFacility();
2921#if CONFIG_SYSTEMD_JOURNAL
2922 bool journal = toBool("systemd-journal");
2923 if (journal)
2924 {
2925 if (facility >= 0)
2927 facility = SYSTEMD_JOURNAL_FACILITY;
2928 }
2929#endif
2930 LogLevel_t level = GetLogLevel();
2931 if (level == LOG_UNKNOWN)
2933
2934 LOG(VB_GENERAL, LOG_CRIT,
2935 QString("%1 version: %2 [%3] www.mythtv.org")
2936 .arg(QCoreApplication::applicationName(),
2938 LOG(VB_GENERAL, LOG_CRIT, QString("Qt version: compile: %1, runtime: %2")
2939 .arg(QT_VERSION_STR, qVersion()));
2940 LOG(VB_GENERAL, LOG_INFO, QString("%1 (%2)")
2941 .arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture()));
2942 LOG(VB_GENERAL, LOG_NOTICE,
2943 QString("Enabled verbose msgs: %1").arg(verboseString));
2944
2945 QString logfile = GetLogFilePath();
2946 bool propagate = !logfile.isEmpty();
2947
2948 if (toBool("daemon"))
2949 quiet = std::max(quiet, 1);
2950
2951 logStart(logfile, progress, quiet, facility, level, propagate, loglong);
2952 qInstallMessageHandler([](QtMsgType /*unused*/, const QMessageLogContext& /*unused*/, const QString &Msg)
2953 { LOG(VB_GENERAL, LOG_INFO, "Qt: " + Msg); });
2954
2955 return GENERIC_EXIT_OK;
2956}
2957
2963{
2964 if (m_verbose)
2965 std::cerr << "Applying settings override\n";
2966
2967 QMap<QString, QString> override = GetSettingsOverride();
2968 if (!override.empty())
2969 {
2970 QMap<QString, QString>::iterator it;
2971 for (it = override.begin(); it != override.end(); ++it)
2972 {
2973 LOG(VB_GENERAL, LOG_NOTICE,
2974 QString("Setting '%1' being forced to '%2'")
2975 .arg(it.key(), *it));
2977 }
2978 }
2979}
2980
2981static bool openPidfile(std::ofstream &pidfs, const QString &pidfile)
2982{
2983 if (!pidfile.isEmpty())
2984 {
2985 pidfs.open(pidfile.toLatin1().constData());
2986 if (!pidfs)
2987 {
2988 std::cerr << "Could not open pid file: " << ENO_STR << '\n';
2989 return false;
2990 }
2991 }
2992 return true;
2993}
2994
2997static bool setUser(const QString &username)
2998{
2999 if (username.isEmpty())
3000 return true;
3001
3002#ifdef Q_OS_WINDOWS
3003 std::cerr << "--user option is not supported on Windows" << std::endl;
3004 return false;
3005#else // ! Q_OS_WINDOWS
3006#ifdef Q_OS_LINUX
3007 // Check the current dumpability of core dumps, which will be disabled
3008 // by setuid, so we can re-enable, if appropriate
3009 int dumpability = prctl(PR_GET_DUMPABLE);
3010#endif
3011 struct passwd *user_info = getpwnam(username.toLocal8Bit().constData());
3012 const uid_t user_id = geteuid();
3013
3014 if (user_id && (!user_info || user_id != user_info->pw_uid))
3015 {
3016 std::cerr << "You must be running as root to use the --user switch.\n";
3017 return false;
3018 }
3019 if (user_info && user_id == user_info->pw_uid)
3020 {
3021 LOG(VB_GENERAL, LOG_WARNING,
3022 QString("Already running as '%1'").arg(username));
3023 }
3024 else if (!user_id && user_info)
3025 {
3026 if (!qputenv("HOME", user_info->pw_dir))
3027 {
3028 std::cerr << "Error setting home directory.\n";
3029 return false;
3030 }
3031 if (setgid(user_info->pw_gid) == -1)
3032 {
3033 std::cerr << "Error setting effective group.\n";
3034 return false;
3035 }
3036 if (initgroups(user_info->pw_name, user_info->pw_gid) == -1)
3037 {
3038 std::cerr << "Error setting groups.\n";
3039 return false;
3040 }
3041 if (setuid(user_info->pw_uid) == -1)
3042 {
3043 std::cerr << "Error setting effective user.\n";
3044 return false;
3045 }
3046#ifdef Q_OS_LINUX
3047 if (dumpability && (prctl(PR_SET_DUMPABLE, dumpability) == -1))
3048 {
3049 LOG(VB_GENERAL, LOG_WARNING, "Unable to re-enable core file "
3050 "creation. Run without the --user argument to use "
3051 "shell-specified limits.");
3052 }
3053#endif
3054 }
3055 else
3056 {
3057 std::cerr << QString("Invalid user '%1' specified with --user")
3058 .arg(username).toLocal8Bit().constData() << '\n';
3059 return false;
3060 }
3061 return true;
3062#endif // ! Q_OS_WINDOWS
3063}
3064
3065
3069{
3070 std::ofstream pidfs;
3071 if (!openPidfile(pidfs, toString("pidfile")))
3073
3074 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
3075 LOG(VB_GENERAL, LOG_WARNING, "Unable to ignore SIGPIPE");
3076
3077#ifdef Q_OS_DARWIN
3078 if (toBool("daemon"))
3079 {
3080 std::cerr << "Daemonizing is unavailable in OSX\n";
3081 LOG(VB_GENERAL, LOG_WARNING, "Unable to daemonize");
3082 }
3083#else
3084 if (toBool("daemon") && (daemon(0, 1) < 0))
3085 {
3086 std::cerr << "Failed to daemonize: " << ENO_STR << '\n';
3088 }
3089#endif
3090
3091 QString username = toString("username");
3092 if (!username.isEmpty() && !setUser(username))
3094
3095 if (pidfs)
3096 {
3097 pidfs << getpid() << '\n';
3098 pidfs.close();
3099 }
3100
3101 return GENERIC_EXIT_OK;
3102}
Definition for a single command line option.
CommandLineArg * SetRequires(const QString &opt)
Set argument as requiring given option.
static void AllowOneOf(const QList< CommandLineArg * > &args)
Mark a list of arguments as mutually exclusive.
QList< CommandLineArg * > m_requiredby
CommandLineArg * SetParent(const QString &opt)
Set argument as child of given parent.
int GetKeywordLength(void) const
Return length of full keyword string for use in determining indent of help text.
CommandLineArg * SetRequiredChild(const QString &opt)
Set argument as parent of given child and mark as required.
CommandLineArg * SetChildOf(const QString &opt)
Set argument as child of given parent.
CommandLineArg(const QString &name, QMetaType::Type type, QVariant def, QString help, QString longhelp)
Default constructor for CommandLineArg class.
void PrintVerbose(void) const
Internal use.
QString GetKeywordString(void) const
Return string containing all possible keyword triggers for this argument.
CommandLineArg * SetRemoved(QString remstr="", QString remver="")
Set option as removed.
bool TestLinks(void) const
Test all related arguments to make sure specified requirements are fulfilled.
CommandLineArg * SetDeprecated(QString depstr="")
Set option as deprecated.
void PrintDeprecatedWarning(QString &keyword) const
Internal use.
bool Set(const QString &opt)
Set option as provided on command line with no value.
CommandLineArg * SetChild(const QString &opt)
Set argument as parent of given child.
CommandLineArg * SetGroup(const QString &group)
CommandLineArg * SetParentOf(const QString &opt)
Set argument as parent of given child.
CommandLineArg * SetBlocks(const QString &opt)
Set argument as incompatible with given option.
void CleanupLinks(void)
Clear out references to other arguments in preparation for deletion.
QList< CommandLineArg * > m_children
QList< CommandLineArg * > m_blocks
QList< CommandLineArg * > m_requires
QMetaType::Type m_type
void PrintRemovedWarning(QString &keyword) const
Internal use.
QString GetName(void) const
void AddKeyword(const QString &keyword)
QString GetPreferredKeyword(void) const
Return the longest keyword for the argument.
QString GetLongHelpString(QString keyword) const
Return string containing extended help text.
QString GetHelpString(int off, const QString &group="", bool force=false) const
Return string containing help text with desired offset.
void Convert(void)
Convert stored string value from QByteArray to QString.
QList< CommandLineArg * > m_parents
CommandLineArg * SetRequiredChildOf(const QString &opt)
Set argument as child required by given parent.
static const char * NamedOptType(Result type)
void addVersion(void)
Canned argument definition for –version.
QMap< QString, CommandLineArg * > m_optionedArgs
void addPlatform(void)
Pass through the platform argument to Qt for GUI applications.
bool toBool(const QString &key) const
Returns stored QVariant as a boolean.
int toInt(const QString &key) const
Returns stored QVariant as an integer, falling to default if not provided.
static QStringList MythSplitCommandString(const QString &line)
Parse a string into separate tokens.
Result getOpt(int argc, const char *const *argv, int &argpos, QString &opt, QByteArray &val)
Internal use.
MythCommandLineParser(QString appname)
Default constructor for MythCommandLineArg class.
virtual bool Parse(int argc, const char *const *argv)
Loop through argv and populate arguments with values.
double toDouble(const QString &key) const
Returns stored QVariant as double floating point value, falling to default if not provided.
int GetSyslogFacility(void) const
Helper utility for logging interface to return syslog facility.
void ApplySettingsOverride(void)
Apply all overrides to the global context.
QSize toSize(const QString &key) const
Returns stored QVariant as a QSize value, falling to default if not provided.
void addWindowed(void)
Canned argument definition for –windowed and -no-windowed.
void addPIDFile(void)
Canned argument definition for –pidfile.
void addSettingsOverride(void)
Canned argument definition for –override-setting and –override-settings-file.
int Daemonize(void) const
Fork application into background, and detatch from terminal.
void addDisplay(void)
Canned argument definition for -display.
void addRecording(void)
Canned argument definition for –chanid and –starttime.
int ConfigureLogging(const QString &mask="general", bool progress=false)
Read in logging options and initialize the logging interface.
void addLogging(const QString &defaultVerbosity="general", LogLevel_t defaultLogLevel=LOG_INFO)
Canned argument definition for all logging options, including –verbose, –logpath, –quiet,...
QMap< QString, QString > GetExtra(void) const
Return map of additional key/value pairs provided on the command line independent of any registered a...
void addDVBv3(void)
Canned argument definition for –dvbv3.
QString toString(const QString &key) const
Returns stored QVariant as a QString, falling to default if not provided.
QString GetPassthrough(void) const
Return any text supplied on the command line after a bare '–'.
long long toLongLong(const QString &key) const
Returns stored QVariant as a long integer, falling to default if not provided.
QMap< QString, CommandLineArg * > m_namedArgs
QMap< QString, QString > GetSettingsOverride(void)
Return map of key/value pairs provided to override database options.
static void PrintVersion(void)
Print application version information.
QString GetLogFilePath(void)
Helper utility for logging interface to pull path from –logpath.
CommandLineArg * add(const QString &arg, const QString &name, bool def, QString help, QString longhelp)
bool SetValue(const QString &key, const QVariant &value)
Set a new stored value for an existing argument definition, or spawn a new definition store value in.
void addMouse(void)
Canned argument definition for –mouse-cursor and –no-mouse-cursor.
LogLevel_t GetLogLevel(void) const
Helper utility for logging interface to filtering level.
void addHelp(void)
Canned argument definition for –help.
QVariant operator[](const QString &name)
Returned stored QVariant for given argument, or default value if not used.
QString GetHelpString(void) const
Generate command line option help text.
QDateTime toDateTime(const QString &key) const
Returns stored QVariant as a QDateTime, falling to default if not provided.
QMap< QString, QString > toMap(const QString &key) const
Returns stored QVariant as a QMap, falling to default if not provided.
void addGeometry(void)
Canned argument definition for –geometry.
void allowPassthrough(bool allow=true)
Specify that parser should allow a bare '–', and collect all subsequent text as a QString.
void addUPnP(void)
Canned argument definition for –noupnp.
void addDaemon(void)
Canned argument definition for –daemon.
void addInFile(bool addOutFile=false)
Canned argument definition for –infile and –outfile.
virtual QString GetHelpHeader(void) const
QStringList toStringList(const QString &key, const QString &sep="") const
Returns stored QVariant as a QStringList, falling to default if not provided.
virtual void LoadArguments(void)
void allowArgs(bool allow=true)
Specify that parser should allow and collect values provided independent of any keyword.
void allowExtras(bool allow=true)
Specify that parser should allow and collect additional key/value pairs not explicitly defined for pr...
uint toUInt(const QString &key) const
Returns stored QVariant as an unsigned integer, falling to default if not provided.
QStringList GetArgs(void) const
Return list of additional values provided on the command line independent of any keyword.
void PrintHelp(void) const
Print command line option help.
bool ReconcileLinks(void)
Replace dummy arguments used to define interdependency with pointers to their real counterparts.
void addJob(void)
Canned argument definition for –jobid.
void OverrideSettingForSession(const QString &key, const QString &value)
General purpose reference counter.
virtual int IncrRef(void)
Increments reference count.
#define geteuid()
Definition: compat.h:120
#define daemon(x, y)
Definition: compat.h:126
#define SIGPIPE
Definition: compat.h:80
#define setuid(x)
Definition: compat.h:121
unsigned int uint
Definition: compat.h:60
@ GENERIC_EXIT_PERMISSIONS_ERROR
File permissions error.
Definition: exitcodes.h:22
@ GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:13
@ GENERIC_EXIT_DAEMONIZING_ERROR
Error daemonizing or execl.
Definition: exitcodes.h:31
@ GENERIC_EXIT_INVALID_CMDLINE
Command line parse error.
Definition: exitcodes.h:18
static constexpr uint8_t START
int verboseArgParse(const QString &arg)
Parse the –verbose commandline argument and set the verbose level.
Definition: logging.cpp:915
uint64_t verboseMask
Definition: logging.cpp:102
QString verboseString
Definition: logging.cpp:103
QString logLevelGetName(LogLevel_t level)
Map a log level enumerated value back to the name.
Definition: logging.cpp:787
LogLevel_t logLevelGet(const QString &level)
Map a log level name back to the enumerated value.
Definition: logging.cpp:765
void logStart(const QString &logfile, bool progress, int quiet, int facility, LogLevel_t level, bool propagate, bool loglong, bool testHarness)
Entry point to start logging for the application.
Definition: logging.cpp:651
int syslogGetFacility(const QString &facility)
Map a syslog facility name back to the enumerated value.
Definition: logging.cpp:740
static int GetTermWidth(void)
returns terminal width, or 79 on error
static constexpr int k_defaultWidth
static QByteArray strip_quotes(const QByteArray &array)
static bool setUser(const QString &username)
Drop permissions to the specified user.
static bool openPidfile(std::ofstream &pidfs, const QString &pidfile)
static void wrapList(QStringList &list, int width)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define ENO_STR
Definition: mythlogging.h:75
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
const char * GetMythSourceVersion()
Definition: mythversion.cpp:7
const char * GetMythSourcePath()
Definition: mythversion.cpp:12
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:93
@ kFilename
Default UTC, "yyyyMMddhhmmss".
Definition: mythdate.h:19
@ ISODate
Default UTC.
Definition: mythdate.h:18
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:39
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
def user_info(user, format="xml")
Definition: vimeo_api.py:517