commandoptions.cpp 38.5 KB
Newer Older
1 2 3
/*
 *  commandoptions.cpp  -  extract command line options
 *  Program:  kalarm
4
 *  SPDX-FileCopyrightText: 2001-2020 David Jarvie <djarvie@kde.org>
5
 *
6
 *  SPDX-License-Identifier: GPL-2.0-or-later
7 8 9
 */

#include "commandoptions.h"
10

11
#include "functions.h"
12 13
#include "kalarmapp.h"
#include "kamail.h"
David Jarvie's avatar
David Jarvie committed
14
#include "kalarm_debug.h"
15

David Jarvie's avatar
David Jarvie committed
16 17
#include <KAlarmCal/Identities>
#include <KPIMTextEdit/TextToSpeech>
David Jarvie's avatar
David Jarvie committed
18
#include <KLocalizedString>
19

David Jarvie's avatar
David Jarvie committed
20
#include <QCommandLineParser>
21 22 23

#include <iostream>

David Jarvie's avatar
David Jarvie committed
24 25 26 27 28
namespace
{
bool convInterval(const QString& timeParam, KARecurrence::Type&, int& timeInterval, bool allowMonthYear = false);
}

29
CommandOptions* CommandOptions::mFirstInstance = nullptr;
30

31
CommandOptions::CommandOptions()
32
    : mOptions(Num_Options, nullptr)
David Jarvie's avatar
David Jarvie committed
33 34 35
    , mBgColour(Preferences::defaultBgColour())
    , mFgColour(Preferences::defaultFgColour())
    , mFlags(KAEvent::DEFAULT_FONT)
36
{
37 38
    if (!mFirstInstance)
        mFirstInstance = this;
39 40
}

David Jarvie's avatar
David Jarvie committed
41 42 43 44 45 46 47 48 49 50
/******************************************************************************
* Set the command line options for the parser, and remove any arguments
* following --exec or --exec-display (since the parser can't handle this).
* Reply = command line arguments for parser.
*/
QStringList CommandOptions::setOptions(QCommandLineParser* parser, const QStringList& args)
{
    mParser = parser;

    mOptions[ACK_CONFIRM]
David Jarvie's avatar
David Jarvie committed
51
              = new QCommandLineOption(QStringList{QStringLiteral("a"), QStringLiteral("ack-confirm")},
David Jarvie's avatar
David Jarvie committed
52 53
                                       i18n("Prompt for confirmation when alarm is acknowledged"));
    mOptions[ATTACH]
David Jarvie's avatar
David Jarvie committed
54
              = new QCommandLineOption(QStringList{QStringLiteral("A"), QStringLiteral("attach")},
David Jarvie's avatar
David Jarvie committed
55 56 57 58 59 60 61 62 63
                                       i18n("Attach file to email (repeat as needed)"),
                                       QStringLiteral("url"));
    mOptions[AUTO_CLOSE]
              = new QCommandLineOption(QStringLiteral("auto-close"),
                                       i18n("Auto-close alarm window after --late-cancel period"));
    mOptions[BCC]
              = new QCommandLineOption(QStringLiteral("bcc"),
                                       i18n("Blind copy email to self"));
    mOptions[BEEP]
David Jarvie's avatar
David Jarvie committed
64
              = new QCommandLineOption(QStringList{QStringLiteral("b"), QStringLiteral("beep")},
David Jarvie's avatar
David Jarvie committed
65 66
                                       i18n("Beep when message is displayed"));
    mOptions[COLOUR]
David Jarvie's avatar
David Jarvie committed
67
              = new QCommandLineOption(QStringList{QStringLiteral("c"), QStringLiteral("colour"), QStringLiteral("color")},
David Jarvie's avatar
David Jarvie committed
68 69 70
                                       i18n("Message background color (name or hex 0xRRGGBB)"),
                                       QStringLiteral("color"));
    mOptions[COLOURFG]
David Jarvie's avatar
David Jarvie committed
71
              = new QCommandLineOption(QStringList{QStringLiteral("C"), QStringLiteral("colourfg"), QStringLiteral("colorfg")},
David Jarvie's avatar
David Jarvie committed
72 73 74 75 76 77 78
                                       i18n("Message foreground color (name or hex 0xRRGGBB)"),
                                       QStringLiteral("color"));
    mOptions[OptCANCEL_EVENT]
              = new QCommandLineOption(QStringLiteral("cancelEvent"),
                                       i18n("Cancel alarm with the specified event ID"),
                                       QStringLiteral("eventID"));
    mOptions[DISABLE]
David Jarvie's avatar
David Jarvie committed
79
              = new QCommandLineOption(QStringList{QStringLiteral("d"), QStringLiteral("disable")},
David Jarvie's avatar
David Jarvie committed
80 81 82 83 84
                                       i18n("Disable the alarm"));
    mOptions[DISABLE_ALL]
              = new QCommandLineOption(QStringLiteral("disable-all"),
                                       i18n("Disable monitoring of all alarms"));
    mOptions[EXEC]
David Jarvie's avatar
David Jarvie committed
85
              = new QCommandLineOption(QStringList{QStringLiteral("e"), QStringLiteral("exec")},
David Jarvie's avatar
David Jarvie committed
86 87 88
                                       i18n("Execute a shell command line"),
                                       QStringLiteral("commandLine"));
    mOptions[EXEC_DISPLAY]
David Jarvie's avatar
David Jarvie committed
89
              = new QCommandLineOption(QStringList{QStringLiteral("E"), QStringLiteral("exec-display")},
David Jarvie's avatar
David Jarvie committed
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
                                       i18n("Command line to generate alarm message text"),
                                       QStringLiteral("commandLine"));
    mOptions[OptEDIT]
              = new QCommandLineOption(QStringLiteral("edit"),
                                       i18n("Display the alarm edit dialog to edit the specified alarm"),
                                       QStringLiteral("eventID"));
    mOptions[EDIT_NEW_DISPLAY]
              = new QCommandLineOption(QStringLiteral("edit-new-display"),
                                       i18n("Display the alarm edit dialog to edit a new display alarm"));
    mOptions[EDIT_NEW_COMMAND]
              = new QCommandLineOption(QStringLiteral("edit-new-command"),
                                       i18n("Display the alarm edit dialog to edit a new command alarm"));
    mOptions[EDIT_NEW_EMAIL]
              = new QCommandLineOption(QStringLiteral("edit-new-email"),
                                       i18n("Display the alarm edit dialog to edit a new email alarm"));
    mOptions[EDIT_NEW_AUDIO]
              = new QCommandLineOption(QStringLiteral("edit-new-audio"),
                                       i18n("Display the alarm edit dialog to edit a new audio alarm"));
    mOptions[OptEDIT_NEW_PRESET]
              = new QCommandLineOption(QStringLiteral("edit-new-preset"),
                                       i18n("Display the alarm edit dialog, preset with a template"),
                                       QStringLiteral("templateName"));
    mOptions[FILE]
David Jarvie's avatar
David Jarvie committed
113
              = new QCommandLineOption(QStringList{QStringLiteral("f"), QStringLiteral("file")},
David Jarvie's avatar
David Jarvie committed
114 115 116
                                       i18n("File to display"),
                                       QStringLiteral("url"));
    mOptions[FROM_ID]
David Jarvie's avatar
David Jarvie committed
117
              = new QCommandLineOption(QStringList{QStringLiteral("F"), QStringLiteral("from-id")},
David Jarvie's avatar
David Jarvie committed
118 119 120
                                       i18n("KMail identity to use as sender of email"),
                                       QStringLiteral("ID"));
    mOptions[INTERVAL]
David Jarvie's avatar
David Jarvie committed
121
              = new QCommandLineOption(QStringList{QStringLiteral("i"), QStringLiteral("interval")},
David Jarvie's avatar
David Jarvie committed
122 123 124
                                       i18n("Interval between alarm repetitions"),
                                       QStringLiteral("period"));
    mOptions[KORGANIZER]
David Jarvie's avatar
David Jarvie committed
125
              = new QCommandLineOption(QStringList{QStringLiteral("k"), QStringLiteral("korganizer")},
David Jarvie's avatar
David Jarvie committed
126 127
                                       i18n("Show alarm as an event in KOrganizer"));
    mOptions[LATE_CANCEL]
David Jarvie's avatar
David Jarvie committed
128
              = new QCommandLineOption(QStringList{QStringLiteral("l"), QStringLiteral("late-cancel")},
David Jarvie's avatar
David Jarvie committed
129 130 131 132 133 134 135
                                       i18n("Cancel alarm if more than 'period' late when triggered"),
                                       QStringLiteral("period"),
                                       QStringLiteral("1"));
    mOptions[OptLIST]
              = new QCommandLineOption(QStringLiteral("list"),
                                       i18n("Output list of scheduled alarms to stdout"));
    mOptions[LOGIN]
David Jarvie's avatar
David Jarvie committed
136
              = new QCommandLineOption(QStringList{QStringLiteral("L"), QStringLiteral("login")},
David Jarvie's avatar
David Jarvie committed
137 138
                                       i18n("Repeat alarm at every login"));
    mOptions[MAIL]
David Jarvie's avatar
David Jarvie committed
139
              = new QCommandLineOption(QStringList{QStringLiteral("m"), QStringLiteral("mail")},
David Jarvie's avatar
David Jarvie committed
140 141
                                       i18n("Send an email to the given address (repeat as needed)"),
                                       QStringLiteral("address"));
142 143 144
    mOptions[NOTIFY]
              = new QCommandLineOption(QStringList{QStringLiteral("n"), QStringLiteral("notify")},
                                       i18n("Display alarm message as a notification"));
David Jarvie's avatar
David Jarvie committed
145
    mOptions[PLAY]
David Jarvie's avatar
David Jarvie committed
146
              = new QCommandLineOption(QStringList{QStringLiteral("p"), QStringLiteral("play")},
David Jarvie's avatar
David Jarvie committed
147 148 149
                                       i18n("Audio file to play once"),
                                       QStringLiteral("url"));
    mOptions[PLAY_REPEAT]
David Jarvie's avatar
David Jarvie committed
150
              = new QCommandLineOption(QStringList{QStringLiteral("P"), QStringLiteral("play-repeat")},
David Jarvie's avatar
David Jarvie committed
151 152 153 154 155 156 157
                                       i18n("Audio file to play repeatedly"),
                                       QStringLiteral("url"));
    mOptions[RECURRENCE]
              = new QCommandLineOption(QStringLiteral("recurrence"),
                                       i18n("Specify alarm recurrence using iCalendar syntax"),
                                       QStringLiteral("spec"));
    mOptions[REMINDER]
David Jarvie's avatar
David Jarvie committed
158
              = new QCommandLineOption(QStringList{QStringLiteral("R"), QStringLiteral("reminder")},
David Jarvie's avatar
David Jarvie committed
159 160 161 162 163 164 165
                                       i18n("Display reminder before or after alarm"),
                                       QStringLiteral("period"));
    mOptions[REMINDER_ONCE]
              = new QCommandLineOption(QStringLiteral("reminder-once"),
                                       i18n("Display reminder once, before or after first alarm recurrence"),
                                       QStringLiteral("period"));
    mOptions[REPEAT]
David Jarvie's avatar
David Jarvie committed
166
              = new QCommandLineOption(QStringList{QStringLiteral("r"), QStringLiteral("repeat")},
David Jarvie's avatar
David Jarvie committed
167 168 169
                                       i18n("Number of times to repeat alarm (including initial occasion)"),
                                       QStringLiteral("count"));
    mOptions[SPEAK]
David Jarvie's avatar
David Jarvie committed
170
              = new QCommandLineOption(QStringList{QStringLiteral("s"), QStringLiteral("speak")},
David Jarvie's avatar
David Jarvie committed
171 172
                                       i18n("Speak the message when it is displayed"));
    mOptions[SUBJECT]
David Jarvie's avatar
David Jarvie committed
173
              = new QCommandLineOption(QStringList{QStringLiteral("S"), QStringLiteral("subject")},
David Jarvie's avatar
David Jarvie committed
174 175 176 177 178 179 180 181 182
                                       i18n("Email subject line"),
                                       QStringLiteral("text"));
#ifndef NDEBUG
    mOptions[TEST_SET_TIME]
              = new QCommandLineOption(QStringLiteral("test-set-time"),
                                       i18n("Simulate system time [[[yyyy-]mm-]dd-]hh:mm [TZ] (debug mode)"),
                                       QStringLiteral("time"));
#endif
    mOptions[TIME]
David Jarvie's avatar
David Jarvie committed
183
              = new QCommandLineOption(QStringList{QStringLiteral("t"), QStringLiteral("time")},
David Jarvie's avatar
David Jarvie committed
184 185 186 187 188 189 190 191 192 193
                                       i18n("Trigger alarm at time [[[yyyy-]mm-]dd-]hh:mm [TZ], or date yyyy-mm-dd [TZ]"),
                                       QStringLiteral("time"));
    mOptions[OptTRAY]
              = new QCommandLineOption(QStringLiteral("tray"),
                                       i18n("Display system tray icon"));
    mOptions[OptTRIGGER_EVENT]
              = new QCommandLineOption(QStringLiteral("triggerEvent"),
                                       i18n("Trigger alarm with the specified event ID"),
                                       QStringLiteral("eventID"));
    mOptions[UNTIL]
David Jarvie's avatar
David Jarvie committed
194
              = new QCommandLineOption(QStringList{QStringLiteral("u"), QStringLiteral("until")},
David Jarvie's avatar
David Jarvie committed
195 196 197
                                       i18n("Repeat until time [[[yyyy-]mm-]dd-]hh:mm [TZ], or date yyyy-mm-dd [TZ]"),
                                       QStringLiteral("time"));
    mOptions[VOLUME]
David Jarvie's avatar
David Jarvie committed
198
              = new QCommandLineOption(QStringList{QStringLiteral("V"), QStringLiteral("volume")},
David Jarvie's avatar
David Jarvie committed
199 200 201 202 203
                                       i18n("Volume to play audio file"),
                                       QStringLiteral("percent"));

    for (int i = 0; i < mOptions.size(); ++i)
    {
David Jarvie's avatar
David Jarvie committed
204
        if (!mOptions.at(i))
205
            qCCritical(KALARM_LOG) << "CommandOptions::setOptions: Command option" << i << "not initialised";
David Jarvie's avatar
David Jarvie committed
206
        else
David Jarvie's avatar
David Jarvie committed
207
            mParser->addOption(*(mOptions.at(i)));
David Jarvie's avatar
David Jarvie committed
208 209 210 211 212 213
    }
    mParser->addPositionalArgument(QStringLiteral("message"),
                                   i18n("Message text to display"),
                                   QStringLiteral("[message]"));

    // Check for any options which eat up all following arguments.
214
    mNonExecArguments.clear();
David Jarvie's avatar
David Jarvie committed
215 216 217
    for (int i = 0;  i < args.size();  ++i)
    {
        const QString arg = args[i];
218
        if (arg == QLatin1String("--nofork"))
219
            continue;     // Ignore debugging option
220
        mNonExecArguments << arg;
David Jarvie's avatar
David Jarvie committed
221 222 223 224 225 226 227 228 229 230 231
        if (arg == optionName(EXEC)  ||  arg == optionName(EXEC, true)
        ||  arg == optionName(EXEC_DISPLAY)  ||  arg == optionName(EXEC_DISPLAY, true))
        {
            // All following arguments (including ones beginning with '-')
            // belong to this option. QCommandLineParser can't handle this, so
            // remove them from the command line.
            ++i;   // leave the first argument, which is the command to be executed
            while (++i < args.size())
                mExecArguments << args[i];
        }
    }
232
    return mNonExecArguments;
David Jarvie's avatar
David Jarvie committed
233 234
}

235
void CommandOptions::parse()
David Jarvie's avatar
David Jarvie committed
236
{
237
    mParser->process(mNonExecArguments);
David Jarvie's avatar
David Jarvie committed
238 239
}

240
void CommandOptions::process()
241
{
242
    if (mCommand == CMD_ERROR)
243 244
        return;

245
#ifndef NDEBUG
David Jarvie's avatar
David Jarvie committed
246
    if (mParser->isSet(*mOptions.at(TEST_SET_TIME)))
247
    {
David Jarvie's avatar
David Jarvie committed
248
        const QString time = mParser->value(*mOptions.at(TEST_SET_TIME));
249
        if (!KAlarm::convertTimeString(time.toLatin1(), mSimulationTime, KADateTime::realCurrentLocalDateTime(), true))
David Jarvie's avatar
David Jarvie committed
250
            setErrorParameter(TEST_SET_TIME);
251
    }
252
#endif
David Jarvie's avatar
David Jarvie committed
253
    if (checkCommand(OptTRAY, TRAY))
254 255
    {
    }
David Jarvie's avatar
David Jarvie committed
256
    if (checkCommand(OptLIST, LIST))
257
    {
David Jarvie's avatar
David Jarvie committed
258 259
        if (!mParser->positionalArguments().empty())
            setErrorParameter(OptLIST);
260
    }
David Jarvie's avatar
David Jarvie committed
261 262 263
    if (checkCommand(OptTRIGGER_EVENT, TRIGGER_EVENT)
    ||  checkCommand(OptCANCEL_EVENT, CANCEL_EVENT)
    ||  checkCommand(OptEDIT, EDIT))
264
    {
265 266 267
        // Fetch the resource and event IDs. The supplied ID is the event ID,
        // optionally prefixed by the resource ID followed by a colon delimiter.
        mResourceId = EventId::extractIDs(mParser->value(*mOptions.at(mCommandOpt)), mEventId);
268
    }
David Jarvie's avatar
David Jarvie committed
269
    if (checkCommand(OptEDIT_NEW_PRESET, EDIT_NEW_PRESET))
270
    {
David Jarvie's avatar
David Jarvie committed
271
        mTemplateName = mParser->value(*mOptions.at(mCommandOpt));
272
    }
David Jarvie's avatar
David Jarvie committed
273
    if (checkCommand(FILE, NEW))
274 275 276 277
    {
        mEditType      = EditAlarmDlg::DISPLAY;
        mEditAction    = KAEvent::FILE;
        mEditActionSet = true;
David Jarvie's avatar
David Jarvie committed
278
        mText          = mParser->value(*mOptions.at(mCommandOpt));
279
    }
David Jarvie's avatar
David Jarvie committed
280
    if (checkCommand(EXEC_DISPLAY, NEW))
281 282 283 284 285
    {
        mEditType      = EditAlarmDlg::DISPLAY;
        mEditAction    = KAEvent::COMMAND;
        mEditActionSet = true;
        mFlags        |= KAEvent::DISPLAY_COMMAND;
286
        mText          = mParser->value(*mOptions.at(mCommandOpt)) + QLatin1String(" ") + mExecArguments.join(QLatin1Char(' '));
287
    }
David Jarvie's avatar
David Jarvie committed
288
    if (checkCommand(EXEC, NEW))
289 290 291 292
    {
        mEditType      = EditAlarmDlg::COMMAND;
        mEditAction    = KAEvent::COMMAND;
        mEditActionSet = true;
293
        mText          = mParser->value(*mOptions.at(mCommandOpt)) + QLatin1String(" ") + mExecArguments.join(QLatin1Char(' '));
294
    }
David Jarvie's avatar
David Jarvie committed
295
    if (checkCommand(MAIL, NEW))
296 297 298 299 300
    {
        mEditType      = EditAlarmDlg::EMAIL;
        mEditAction    = KAEvent::EMAIL;
        mEditActionSet = true;
    }
David Jarvie's avatar
David Jarvie committed
301
    if (checkCommand(EDIT_NEW_DISPLAY, EDIT_NEW, EditAlarmDlg::DISPLAY))
302 303 304 305 306 307 308
    {
        mEditType = EditAlarmDlg::DISPLAY;
        if (!mEditActionSet  ||  (mEditAction != KAEvent::COMMAND && mEditAction != KAEvent::FILE))
        {
            mEditAction    = KAEvent::MESSAGE;
            mEditActionSet = true;
        }
David Jarvie's avatar
David Jarvie committed
309 310 311
        const QStringList args = mParser->positionalArguments();
        if (!args.empty())
            mText = args[0];
312
    }
David Jarvie's avatar
David Jarvie committed
313
    if (checkCommand(EDIT_NEW_COMMAND, EDIT_NEW))
314 315 316 317 318
    {
        mEditType      = EditAlarmDlg::COMMAND;
        mEditAction    = KAEvent::COMMAND;
        mEditActionSet = true;
    }
David Jarvie's avatar
David Jarvie committed
319
    if (checkCommand(EDIT_NEW_EMAIL, EDIT_NEW, EditAlarmDlg::EMAIL))
320 321 322 323 324
    {
        mEditType      = EditAlarmDlg::EMAIL;
        mEditAction    = KAEvent::EMAIL;
        mEditActionSet = true;
    }
David Jarvie's avatar
David Jarvie committed
325
    if (checkCommand(EDIT_NEW_AUDIO, EDIT_NEW, EditAlarmDlg::AUDIO))
326 327 328 329 330 331 332
    {
        mEditType      = EditAlarmDlg::AUDIO;
        mEditAction    = KAEvent::AUDIO;
        mEditActionSet = true;
    }
    if (mError.isEmpty()  &&  mCommand == NONE)
    {
David Jarvie's avatar
David Jarvie committed
333
        if (mParser->positionalArguments().empty())
334
        {
David Jarvie's avatar
David Jarvie committed
335
            if (checkCommand(PLAY, NEW) || checkCommand(PLAY_REPEAT, NEW))
336 337 338 339 340 341 342 343
            {
                mEditType      = EditAlarmDlg::AUDIO;
                mEditAction    = KAEvent::AUDIO;
                mEditActionSet = true;
            }
        }
        else
        {
344
            qCDebug(KALARM_LOG) << "CommandOptions::process: Message";
345
            mCommand       = NEW;
David Jarvie's avatar
David Jarvie committed
346
            mCommandOpt    = Opt_Message;
347 348 349
            mEditType      = EditAlarmDlg::DISPLAY;
            mEditAction    = KAEvent::MESSAGE;
            mEditActionSet = true;
David Jarvie's avatar
David Jarvie committed
350
            mText          = arg(0);
351 352 353 354
        }
    }
    if (mEditActionSet  &&  mEditAction == KAEvent::EMAIL)
    {
David Jarvie's avatar
David Jarvie committed
355 356 357 358 359 360
        if (mParser->isSet(*mOptions.at(SUBJECT)))
            mSubject = mParser->value(*mOptions.at(SUBJECT));
        if (mParser->isSet(*mOptions.at(FROM_ID)))
            mFromID = Identities::identityUoid(mParser->value(*mOptions.at(FROM_ID)));
        const QStringList mailParams = mParser->values(*mOptions.at(MAIL));
        for (const QString& addr : mailParams)
361
        {
David Jarvie's avatar
David Jarvie committed
362 363
            QString a(addr);
            if (!KAMail::checkAddress(a))
David Jarvie's avatar
David Jarvie committed
364
                setError(xi18nc("@info:shell", "<icode>%1</icode>: invalid email address", optionName(MAIL)));
365
            KCalendarCore::Person person(QString(), addr);
366 367
            mAddressees += person;
        }
David Jarvie's avatar
David Jarvie committed
368 369 370
        const QStringList attParams = mParser->values(*mOptions.at(ATTACH));
        for (const QString& att : attParams)
            mAttachments += att;
David Jarvie's avatar
David Jarvie committed
371 372 373
        const QStringList args = mParser->positionalArguments();
        if (!args.empty())
            mText = args[0];
374
    }
David Jarvie's avatar
David Jarvie committed
375
    if (mParser->isSet(*mOptions.at(DISABLE_ALL)))
376
    {
377
        if (mCommand == TRIGGER_EVENT  ||  mCommand == LIST)
David Jarvie's avatar
David Jarvie committed
378
            setErrorIncompatible(DISABLE_ALL, mCommandOpt);
379 380
        mDisableAll = true;
    }
381

382 383
    // Check that other options are only specified for the
    // correct main command options.
David Jarvie's avatar
David Jarvie committed
384 385 386 387 388 389 390 391 392 393 394
    checkEditType(EditAlarmDlg::DISPLAY, COLOUR);
    checkEditType(EditAlarmDlg::DISPLAY, COLOURFG);
    checkEditType(EditAlarmDlg::DISPLAY, EditAlarmDlg::AUDIO, PLAY);
    checkEditType(EditAlarmDlg::DISPLAY, EditAlarmDlg::AUDIO, PLAY_REPEAT);
    checkEditType(EditAlarmDlg::DISPLAY, EditAlarmDlg::AUDIO, VOLUME);
    checkEditType(EditAlarmDlg::DISPLAY, SPEAK);
    checkEditType(EditAlarmDlg::DISPLAY, BEEP);
    checkEditType(EditAlarmDlg::DISPLAY, REMINDER);
    checkEditType(EditAlarmDlg::DISPLAY, REMINDER_ONCE);
    checkEditType(EditAlarmDlg::DISPLAY, ACK_CONFIRM);
    checkEditType(EditAlarmDlg::DISPLAY, AUTO_CLOSE);
395
    checkEditType(EditAlarmDlg::DISPLAY, NOTIFY);
David Jarvie's avatar
David Jarvie committed
396 397 398 399
    checkEditType(EditAlarmDlg::EMAIL, SUBJECT);
    checkEditType(EditAlarmDlg::EMAIL, FROM_ID);
    checkEditType(EditAlarmDlg::EMAIL, ATTACH);
    checkEditType(EditAlarmDlg::EMAIL, BCC);
400

401 402 403
    switch (mCommand)
    {
        case EDIT_NEW:
David Jarvie's avatar
David Jarvie committed
404
            if (mParser->isSet(*mOptions.at(DISABLE)))
David Jarvie's avatar
David Jarvie committed
405
                setErrorIncompatible(DISABLE, mCommandOpt);
406
            // Fall through to NEW
407
            Q_FALLTHROUGH();
408 409 410
        case NEW:
        {
            // Display a message or file, execute a command, or send an email
David Jarvie's avatar
David Jarvie committed
411
            if (mParser->isSet(*mOptions.at(COLOUR)))
412 413
            {
                // Background colour is specified
David Jarvie's avatar
David Jarvie committed
414 415 416
                QString colourText = mParser->value(*mOptions.at(COLOUR));
                if (colourText.at(0) == QLatin1Char('0')
                &&  colourText.at(1).toLower() == QLatin1Char('x'))
Laurent Montel's avatar
Laurent Montel committed
417
                    colourText.replace(0, 2, QStringLiteral("#"));
418 419
                mBgColour.setNamedColor(colourText);
                if (!mBgColour.isValid())
David Jarvie's avatar
David Jarvie committed
420
                    setErrorParameter(COLOUR);
421
            }
David Jarvie's avatar
David Jarvie committed
422
            if (mParser->isSet(*mOptions.at(COLOURFG)))
423 424
            {
                // Foreground colour is specified
David Jarvie's avatar
David Jarvie committed
425 426 427
                QString colourText = mParser->value(*mOptions.at(COLOURFG));
                if (colourText.at(0) == QLatin1Char('0')
                &&  colourText.at(1).toLower() == QLatin1Char('x'))
Laurent Montel's avatar
Laurent Montel committed
428
                    colourText.replace(0, 2, QStringLiteral("#"));
429 430
                mFgColour.setNamedColor(colourText);
                if (!mFgColour.isValid())
David Jarvie's avatar
David Jarvie committed
431
                    setErrorParameter(COLOURFG);
432
            }
433

David Jarvie's avatar
David Jarvie committed
434
            if (mParser->isSet(*mOptions.at(TIME)))
435
            {
David Jarvie's avatar
David Jarvie committed
436
                const QByteArray dateTime = mParser->value(*mOptions.at(TIME)).toLocal8Bit();
437
                if (!KAlarm::convertTimeString(dateTime, mAlarmTime))
David Jarvie's avatar
David Jarvie committed
438
                    setErrorParameter(TIME);
439 440
            }
            else
441
                mAlarmTime = KADateTime::currentLocalDateTime();
442

David Jarvie's avatar
David Jarvie committed
443
            const bool haveRecurrence = mParser->isSet(*mOptions.at(RECURRENCE));
444 445
            if (haveRecurrence)
            {
David Jarvie's avatar
David Jarvie committed
446
                if (mParser->isSet(*mOptions.at(LOGIN)))
David Jarvie's avatar
David Jarvie committed
447
                    setErrorIncompatible(LOGIN, RECURRENCE);
David Jarvie's avatar
David Jarvie committed
448
                else if (mParser->isSet(*mOptions.at(UNTIL)))
David Jarvie's avatar
David Jarvie committed
449
                    setErrorIncompatible(UNTIL, RECURRENCE);
David Jarvie's avatar
David Jarvie committed
450
                const QString rule = mParser->value(*mOptions.at(RECURRENCE));
451 452 453
                mRecurrence = new KARecurrence;
                mRecurrence->set(rule);
            }
David Jarvie's avatar
David Jarvie committed
454
            if (mParser->isSet(*mOptions.at(INTERVAL)))
455 456 457
            {
                // Repeat count is specified
                int count = 0;
458
                KADateTime endTime;
David Jarvie's avatar
David Jarvie committed
459
                if (mParser->isSet(*mOptions.at(LOGIN)))
David Jarvie's avatar
David Jarvie committed
460
                    setErrorIncompatible(LOGIN, INTERVAL);
461
                bool ok;
David Jarvie's avatar
David Jarvie committed
462
                if (mParser->isSet(*mOptions.at(REPEAT)))
463
                {
David Jarvie's avatar
David Jarvie committed
464
                    count = mParser->value(*mOptions.at(REPEAT)).toInt(&ok);
465
                    if (!ok || !count || count < -1 || (count < 0 && haveRecurrence))
David Jarvie's avatar
David Jarvie committed
466
                        setErrorParameter(REPEAT);
467 468
                }
                else if (haveRecurrence)
David Jarvie's avatar
David Jarvie committed
469
                    setErrorRequires(INTERVAL, REPEAT);
David Jarvie's avatar
David Jarvie committed
470
                else if (mParser->isSet(*mOptions.at(UNTIL)))
471 472
                {
                    count = 0;
David Jarvie's avatar
David Jarvie committed
473
                    const QByteArray dateTime = mParser->value(*mOptions.at(UNTIL)).toLocal8Bit();
474
                    bool ok;
David Jarvie's avatar
David Jarvie committed
475
                    if (mParser->isSet(*mOptions.at(TIME)))
476
                        ok = KAlarm::convertTimeString(dateTime, endTime, mAlarmTime);
477
                    else
478
                        ok = KAlarm::convertTimeString(dateTime, endTime);
479
                    if (!ok)
David Jarvie's avatar
David Jarvie committed
480
                        setErrorParameter(UNTIL);
481
                    else if (mAlarmTime.isDateOnly()  &&  !endTime.isDateOnly())
David Jarvie's avatar
David Jarvie committed
482
                        setError(xi18nc("@info:shell", "Invalid <icode>%1</icode> parameter for date-only alarm", optionName(UNTIL)));
483 484 485
                    if (!mAlarmTime.isDateOnly()  &&  endTime.isDateOnly())
                        endTime.setTime(QTime(23,59,59));
                    if (endTime < mAlarmTime)
David Jarvie's avatar
David Jarvie committed
486
                        setError(xi18nc("@info:shell", "<icode>%1</icode> earlier than <icode>%2</icode>", optionName(UNTIL), optionName(TIME)));
487 488 489
                }
                else
                    count = -1;
490

491
                // Get the recurrence interval
492
                int intervalOfType;
493
                KARecurrence::Type recurType;
David Jarvie's avatar
David Jarvie committed
494
                if (!convInterval(mParser->value(*mOptions.at(INTERVAL)), recurType, intervalOfType, !haveRecurrence))
David Jarvie's avatar
David Jarvie committed
495
                    setErrorParameter(INTERVAL);
496
                else if (mAlarmTime.isDateOnly()  &&  recurType == KARecurrence::MINUTELY)
David Jarvie's avatar
David Jarvie committed
497
                    setError(xi18nc("@info:shell", "Invalid <icode>%1</icode> parameter for date-only alarm", optionName(INTERVAL)));
498

499 500 501 502
                if (haveRecurrence)
                {
                    if (mRecurrence)
                    {
503 504
                        // There is a also a recurrence specified, so set up a sub-repetition.
                        // In this case, 'intervalOfType' is in minutes.
505
                        mRepeatInterval = KCalendarCore::Duration(intervalOfType * 60);
David Jarvie's avatar
David Jarvie committed
506
                        const KCalendarCore::Duration longestInterval = mRecurrence->longestInterval();
507
                        if (mRepeatInterval * count > longestInterval)
Laurent Montel's avatar
Laurent Montel committed
508
                            setError(xi18nc("@info:shell", "Invalid <icode>%1</icode> and <icode>%2</icode> parameters: repetition is longer than <icode>%3</icode> interval",
David Jarvie's avatar
David Jarvie committed
509
                                           optionName(INTERVAL), optionName(REPEAT), optionName(RECURRENCE)));
510
                        mRepeatCount = count;
511 512 513 514 515 516 517
                    }
                }
                else
                {
                    // There is no other recurrence specified, so convert the repetition
                    // parameters into a KCal::Recurrence
                    mRecurrence = new KARecurrence;
518
                    mRecurrence->set(recurType, intervalOfType, count, mAlarmTime, endTime);
519 520 521 522
                }
            }
            else
            {
David Jarvie's avatar
David Jarvie committed
523
                if (mParser->isSet(*mOptions.at(REPEAT)))
David Jarvie's avatar
David Jarvie committed
524
                    setErrorRequires(REPEAT, INTERVAL);
David Jarvie's avatar
David Jarvie committed
525
                else if (mParser->isSet(*mOptions.at(UNTIL)))
David Jarvie's avatar
David Jarvie committed
526
                    setErrorRequires(UNTIL, INTERVAL);
527
            }
528

David Jarvie's avatar
David Jarvie committed
529 530
            const bool audioRepeat = mParser->isSet(*mOptions.at(PLAY_REPEAT));
            if (audioRepeat  ||  mParser->isSet(*mOptions.at(PLAY)))
531 532
            {
                // Play a sound with the alarm
David Jarvie's avatar
David Jarvie committed
533 534
                const Option opt = audioRepeat ? PLAY_REPEAT : PLAY;
                if (audioRepeat  &&  mParser->isSet(*mOptions.at(PLAY)))
David Jarvie's avatar
David Jarvie committed
535
                    setErrorIncompatible(PLAY, PLAY_REPEAT);
David Jarvie's avatar
David Jarvie committed
536
                if (mParser->isSet(*mOptions.at(BEEP)))
David Jarvie's avatar
David Jarvie committed
537
                    setErrorIncompatible(BEEP, opt);
David Jarvie's avatar
David Jarvie committed
538
                else if (mParser->isSet(*mOptions.at(SPEAK)))
David Jarvie's avatar
David Jarvie committed
539
                    setErrorIncompatible(SPEAK, opt);
David Jarvie's avatar
David Jarvie committed
540 541
                mAudioFile = mParser->value(*mOptions.at(audioRepeat ? PLAY_REPEAT : PLAY));
                if (mParser->isSet(*mOptions.at(VOLUME)))
542 543
                {
                    bool ok;
David Jarvie's avatar
David Jarvie committed
544
                    const int volumepc = mParser->value(*mOptions.at(VOLUME)).toInt(&ok);
545
                    if (!ok  ||  volumepc < 0  ||  volumepc > 100)
David Jarvie's avatar
David Jarvie committed
546
                        setErrorParameter(VOLUME);
547 548 549
                    mAudioVolume = static_cast<float>(volumepc) / 100;
                }
            }
David Jarvie's avatar
David Jarvie committed
550
            else if (mParser->isSet(*mOptions.at(VOLUME)))
David Jarvie's avatar
David Jarvie committed
551
                setErrorRequires(VOLUME, PLAY, PLAY_REPEAT);
David Jarvie's avatar
David Jarvie committed
552
            if (mParser->isSet(*mOptions.at(SPEAK)))
553
            {
David Jarvie's avatar
David Jarvie committed
554
                if (mParser->isSet(*mOptions.at(BEEP)))
David Jarvie's avatar
David Jarvie committed
555
                    setErrorIncompatible(BEEP, SPEAK);
Laurent Montel's avatar
Laurent Montel committed
556
                else if (!KPIMTextEdit::TextToSpeech::self()->isReady())
David Jarvie's avatar
David Jarvie committed
557
                    setError(xi18nc("@info:shell", "<icode>%1</icode> requires KAlarm to be compiled with QTextToSpeech support", optionName(SPEAK)));
558
            }
David Jarvie's avatar
David Jarvie committed
559 560
            const bool onceOnly = mParser->isSet(*mOptions.at(REMINDER_ONCE));
            if (mParser->isSet(*mOptions.at(REMINDER))  ||  onceOnly)
561
            {
562
                // Issue a reminder alarm in advance of or after the main alarm
David Jarvie's avatar
David Jarvie committed
563
                if (onceOnly  &&  mParser->isSet(*mOptions.at(REMINDER)))
David Jarvie's avatar
David Jarvie committed
564
                    setErrorIncompatible(REMINDER, REMINDER_ONCE);
David Jarvie's avatar
David Jarvie committed
565
                const Option opt = onceOnly ? REMINDER_ONCE : REMINDER;
566
                KARecurrence::Type recurType;
David Jarvie's avatar
David Jarvie committed
567 568
                QString optval = mParser->value(*mOptions.at(onceOnly ? REMINDER_ONCE : REMINDER));
                const bool after = (optval.at(0) == QLatin1Char('+'));
569
                if (after)
570
                    optval.remove(0, 1);   // it's a reminder after the main alarm
571
                if (!convInterval(optval, recurType, mReminderMinutes))
572 573
                    setErrorParameter(opt);
                else if (recurType == KARecurrence::MINUTELY  &&  mAlarmTime.isDateOnly())
David Jarvie's avatar
David Jarvie committed
574
                    setError(xi18nc("@info:shell", "Invalid <icode>%1</icode> parameter for date-only alarm", optionName(opt)));
575
                if (after)
576
                    mReminderMinutes = -mReminderMinutes;
577 578
                if (onceOnly)
                    mFlags |= KAEvent::REMINDER_ONCE;
579
            }
580

David Jarvie's avatar
David Jarvie committed
581
            if (mParser->isSet(*mOptions.at(LATE_CANCEL)))
582 583
            {
                KARecurrence::Type recurType;
David Jarvie's avatar
David Jarvie committed
584
                const bool ok = convInterval(mParser->value(*mOptions.at(LATE_CANCEL)), recurType, mLateCancel);
585
                if (!ok)
David Jarvie's avatar
David Jarvie committed
586
                    setErrorParameter(LATE_CANCEL);
587
            }
David Jarvie's avatar
David Jarvie committed
588
            else if (mParser->isSet(*mOptions.at(AUTO_CLOSE)))
David Jarvie's avatar
David Jarvie committed
589
                setErrorRequires(AUTO_CLOSE, LATE_CANCEL);
590

591 592 593 594 595 596 597 598 599 600 601 602 603 604
            if (mParser->isSet(*mOptions.at(NOTIFY)))
            {
                if (mParser->isSet(*mOptions.at(COLOUR)))
                    setErrorIncompatible(NOTIFY, COLOUR);
                if (mParser->isSet(*mOptions.at(COLOURFG)))
                    setErrorIncompatible(NOTIFY, COLOURFG);
                if (mParser->isSet(*mOptions.at(ACK_CONFIRM)))
                    setErrorIncompatible(NOTIFY, ACK_CONFIRM);
                if (mParser->isSet(*mOptions.at(PLAY)))
                    setErrorIncompatible(NOTIFY, PLAY);
                if (mParser->isSet(*mOptions.at(AUTO_CLOSE)))
                    setErrorIncompatible(NOTIFY, AUTO_CLOSE);
            }

David Jarvie's avatar
David Jarvie committed
605
            if (mParser->isSet(*mOptions.at(ACK_CONFIRM)))
606
                mFlags |= KAEvent::CONFIRM_ACK;
David Jarvie's avatar
David Jarvie committed
607
            if (mParser->isSet(*mOptions.at(AUTO_CLOSE)))
608
                mFlags |= KAEvent::AUTO_CLOSE;
David Jarvie's avatar
David Jarvie committed
609
            if (mParser->isSet(*mOptions.at(BEEP)))
610
                mFlags |= KAEvent::BEEP;
David Jarvie's avatar
David Jarvie committed
611
            if (mParser->isSet(*mOptions.at(SPEAK)))
612
                mFlags |= KAEvent::SPEAK;
613 614
            if (mParser->isSet(*mOptions.at(NOTIFY)))
                mFlags |= KAEvent::NOTIFY;
David Jarvie's avatar
David Jarvie committed
615
            if (mParser->isSet(*mOptions.at(KORGANIZER)))
616
                mFlags |= KAEvent::COPY_KORGANIZER;
David Jarvie's avatar
David Jarvie committed
617
            if (mParser->isSet(*mOptions.at(DISABLE)))
618 619 620
                mFlags |= KAEvent::DISABLED;
            if (audioRepeat)
                mFlags |= KAEvent::REPEAT_SOUND;
David Jarvie's avatar
David Jarvie committed
621
            if (mParser->isSet(*mOptions.at(LOGIN)))
622
                mFlags |= KAEvent::REPEAT_AT_LOGIN;
David Jarvie's avatar
David Jarvie committed
623
            if (mParser->isSet(*mOptions.at(BCC)))
624 625 626 627 628 629
                mFlags |= KAEvent::EMAIL_BCC;
            if (mAlarmTime.isDateOnly())
                mFlags |= KAEvent::ANY_TIME;
            break;
        }
        case NONE:
David Jarvie's avatar
David Jarvie committed
630
        {
631 632 633
            // No arguments - run interactively & display the main window
            if (!mError.isEmpty())
                break;
634
            qCDebug(KALARM_LOG) << "CommandOptions::process: Interactive";
David Jarvie's avatar
David Jarvie committed
635
            QStringList errors;
David Jarvie's avatar
David Jarvie committed
636
            if (mParser->isSet(*mOptions.at(ACK_CONFIRM)))
David Jarvie's avatar
David Jarvie committed
637
                errors << optionName(ACK_CONFIRM);
David Jarvie's avatar
David Jarvie committed
638
            if (mParser->isSet(*mOptions.at(ATTACH)))
David Jarvie's avatar
David Jarvie committed
639
                errors << optionName(ATTACH);
David Jarvie's avatar
David Jarvie committed
640
            if (mParser->isSet(*mOptions.at(AUTO_CLOSE)))
David Jarvie's avatar
David Jarvie committed
641
                errors << optionName(AUTO_CLOSE);
David Jarvie's avatar
David Jarvie committed
642
            if (mParser->isSet(*mOptions.at(BCC)))
David Jarvie's avatar
David Jarvie committed
643
                errors << optionName(BCC);
David Jarvie's avatar
David Jarvie committed
644
            if (mParser->isSet(*mOptions.at(BEEP)))
David Jarvie's avatar
David Jarvie committed
645
                errors << optionName(BEEP);
David Jarvie's avatar
David Jarvie committed
646
            if (mParser->isSet(*mOptions.at(COLOUR)))
David Jarvie's avatar
David Jarvie committed
647
                errors << optionName(COLOUR);
David Jarvie's avatar
David Jarvie committed
648
            if (mParser->isSet(*mOptions.at(COLOURFG)))
David Jarvie's avatar
David Jarvie committed
649
                errors << optionName(COLOURFG);
David Jarvie's avatar
David Jarvie committed
650
            if (mParser->isSet(*mOptions.at(DISABLE)))
David Jarvie's avatar
David Jarvie committed
651
                errors << optionName(DISABLE);
David Jarvie's avatar
David Jarvie committed
652
            if (mParser->isSet(*mOptions.at(FROM_ID)))
David Jarvie's avatar
David Jarvie committed
653
                errors << optionName(FROM_ID);
David Jarvie's avatar
David Jarvie committed
654
            if (mParser->isSet(*mOptions.at(KORGANIZER)))
David Jarvie's avatar
David Jarvie committed
655
                errors << optionName(KORGANIZER);
David Jarvie's avatar
David Jarvie committed
656
            if (mParser->isSet(*mOptions.at(LATE_CANCEL)))
David Jarvie's avatar
David Jarvie committed
657
                errors << optionName(LATE_CANCEL);
David Jarvie's avatar
David Jarvie committed
658
            if (mParser->isSet(*mOptions.at(LOGIN)))
David Jarvie's avatar
David Jarvie committed
659
                errors << optionName(LOGIN);
David Jarvie's avatar
David Jarvie committed
660
            if (mParser->isSet(*mOptions.at(PLAY)))
David Jarvie's avatar
David Jarvie committed
661
                errors << optionName(PLAY);
David Jarvie's avatar
David Jarvie committed
662
            if (mParser->isSet(*mOptions.at(PLAY_REPEAT)))
David Jarvie's avatar
David Jarvie committed
663
                errors << optionName(PLAY_REPEAT);
David Jarvie's avatar
David Jarvie committed
664
            if (mParser->isSet(*mOptions.at(REMINDER)))
David Jarvie's avatar
David Jarvie committed
665
                errors << optionName(REMINDER);
David Jarvie's avatar
David Jarvie committed
666
            if (mParser->isSet(*mOptions.at(REMINDER_ONCE)))
David Jarvie's avatar
David Jarvie committed
667
                errors << optionName(REMINDER_ONCE);
David Jarvie's avatar
David Jarvie committed
668
            if (mParser->isSet(*mOptions.at(SPEAK)))
David Jarvie's avatar
David Jarvie committed
669
                errors << optionName(SPEAK);
670 671
            if (mParser->isSet(*mOptions.at(NOTIFY)))
                errors << optionName(NOTIFY);
David Jarvie's avatar
David Jarvie committed
672
            if (mParser->isSet(*mOptions.at(SUBJECT)))
David Jarvie's avatar
David Jarvie committed
673
                errors << optionName(SUBJECT);
David Jarvie's avatar
David Jarvie committed
674
            if (mParser->isSet(*mOptions.at(TIME)))
David Jarvie's avatar
David Jarvie committed
675
                errors << optionName(TIME);
David Jarvie's avatar
David Jarvie committed
676
            if (mParser->isSet(*mOptions.at(VOLUME)))
David Jarvie's avatar
David Jarvie committed
677 678 679
                errors << optionName(VOLUME);
            if (!errors.isEmpty())
                mError = errors.join(QLatin1Char(' ')) + i18nc("@info:shell", ": option(s) only valid with an appropriate action option or message");
680
            break;
David Jarvie's avatar
David Jarvie committed
681
        }
682 683 684
        default:
            break;
    }
685

686
    if (!mError.isEmpty())
687 688 689 690 691
        setError(mError);
}

void CommandOptions::setError(const QString& errmsg)
{
692
    qCWarning(KALARM_LOG) << "CommandOptions::setError:" << errmsg;
693 694 695
    mCommand = CMD_ERROR;
    if (mError.isEmpty())
        mError = errmsg + i18nc("@info:shell", "\nUse --help to get a list of available command line options.\n");
696 697 698 699
}

void CommandOptions::printError(const QString& errmsg)
{
700 701 702
    // Note: we can't use mArgs->usage() since that also quits any other
    // running 'instances' of the program.
    std::cerr << errmsg.toLocal8Bit().data()
David Jarvie's avatar
David Jarvie committed
703
              << i18nc("@info:shell", "\nUse --help to get a list of available command line options.\n").toLocal8Bit().data();
704 705 706 707 708 709 710 711 712
}

/******************************************************************************
* Check if the given command option is specified, and if so set mCommand etc.
* If another command option has also been detected, issue an error.
* If 'allowedEditType' is set, supersede any previous specification of that
* edit type with the given command option - this allows, e.g., --mail to be
* used along with --edit-new-email so the user can specify addressees.
*/
David Jarvie's avatar
David Jarvie committed
713
bool CommandOptions::checkCommand(Option command, Command code, EditAlarmDlg::Type allowedEditType)
714
{
715
    if (!mError.isEmpty()
David Jarvie's avatar
David Jarvie committed
716
    ||  !mParser->isSet(*mOptions.at(command)))
717 718 719 720
        return false;
    if (mCommand != NONE
    &&  (allowedEditType == EditAlarmDlg::NO_TYPE
      || (allowedEditType != EditAlarmDlg::NO_TYPE  &&  (mCommand != NEW || mEditType != allowedEditType))))
David Jarvie's avatar
David Jarvie committed
721
        setErrorIncompatible(mCommandOpt, command);
722
    qCDebug(KALARM_LOG).nospace() << "CommandOptions::checkCommand: " << optionName(command);
723
    mCommand = code;
David Jarvie's avatar
David Jarvie committed
724
    mCommandOpt = command;
725
    return true;
726 727 728
}

// Set the error message to "--opt requires --opt2" or "--opt requires --opt2 or --opt3".
David Jarvie's avatar
David Jarvie committed
729
void CommandOptions::setErrorRequires(Option opt, Option opt2, Option opt3)
730
{
David Jarvie's avatar
David Jarvie committed
731 732
    if (opt3 == Num_Options)
        setError(xi18nc("@info:shell", "<icode>%1</icode> requires <icode>%2</icode>", optionName(opt), optionName(opt2)));
733
    else
David Jarvie's avatar
David Jarvie committed
734
        setError(xi18nc("@info:shell", "<icode>%1</icode> requires <icode>%2</icode> or <icode>%3</icode>", optionName(opt), optionName(opt2), optionName(opt3)));
735 736
}

David Jarvie's avatar
David Jarvie committed
737
void CommandOptions