PDA

View Full Version : Question on proper use of QCommandLineOption?



TorAn
10th November 2016, 18:18
#include <QCoreApplication>
#include <QCommandLineOption>
#include <QCommandLineParser>
#include <QDebug>

/*
How should I use CommandLineOption/Parser to process command line arguments
when the arguments are defined in the string?
For example, if the argument is "p ttt" I want to do one thing, if it is "p mmmm"
I need to do something else. My little test show nothing in debug.
Am I misunderstanding the usage of these classes?

Help/advise is appreciated. Thansk.
*/
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QCommandLineParser parser;

QCommandLineOption co({{"p","pp","t"}, "test option", "v1"});
parser.addOption(co);

parser.process ( (QStringList() << "p --v1"));
QString vv = parser.value("p");
qDebug() << vv;

return a.exec();
}

jefftee
11th November 2016, 08:40
I've only used QCommandLineParser once, so not sure if this is an optimal solution, but it works for me and I believe covers all scenarios:


QCommandLineOption c_opt({"c","csvfile"}, "The CSV file with desired fields.", "csvfile");
QCommandLineOption i_opt({"i","import"}, "Import the CSV file into tablename");
QCommandLineOption s_opt({"s","sync"}, "Sync tablename with the CSV file fields.");
QCommandLineOption t_opt({"t","table"}, "The target table name.", "tablename");
QCommandLineParser parser;

parser.addHelpOption();
parser.addVersionOption();
parser.addOption(c_opt);
parser.addOption(i_opt);
parser.addOption(s_opt);
parser.addOption(t_opt);
parser.addPositionalArgument("db", "The database file to process.");
parser.process(QCoreApplication::arguments());

QStringList args = parser.positionalArguments();

QString csv_filename = parser.value("csvfile");
QString table_name = parser.value("table");

if (!parser.isSet(i_opt) && !parser.isSet(s_opt))
{
qDebug("\nOne of import or sync options is required.\n");
parser.showHelp();
}

if (parser.isSet(i_opt) && parser.isSet(s_opt))
{
qDebug("\nOnly one of import or sync options is allowed.\n");
parser.showHelp();
}

if (!parser.isSet(c_opt) || csv_filename.isEmpty())
{
qDebug("\nThe CSV file to import is a required option.\n");
parser.showHelp();
}

if (!parser.isSet(t_opt) || table_name.isEmpty())
{
qDebug("\nThe table name is a required option.\n");
parser.showHelp(1);
}

QString db_filename;

if (args.count() == 1)
{
db_filename = args.at(0);
}

if (csv_filename.isEmpty() || db_filename.isEmpty())
{
qDebug("\nThe CSV file name and database name are required.\n");
parser.showHelp(1);
}

TorAn
11th November 2016, 15:13
Thank you so much! Using your code I figured out what was the main problem that I was dealing with. It turns out that the call


parser.process(QCoreApplication::arguments());
or

parser.process (QStringList&);

works by assuming that the the first element in QStringList must be the path to the process (argv[0]). Qt documentation does not mention it at all, and I was passing list that had two arguments (option and option value).
It also works if the first element in the list is just an empty string.

TorAn
11th November 2016, 19:41
jefftee: Would you be so kind to also publish a command of how you actually invoke your application with the options, specifically with "positionalArgument" db? I can't find out (in the docs) how it should be specified. db=smth? db "smth"?

jefftee
11th November 2016, 20:24
Sure, a typical usage would be:


tablesync -i -c test.csv -t incident_data ~/db/sqlite.db

Here's also the "tablesync -h" output that is automatically built:


mbp02:~/develop/tablesync[develop]$ tablesync -h
Usage: tablesync [options] db

Options:
-h, --help Displays this help.
-v, --version Displays version information.
-c, --csvfile <csvfile> The CSV file with desired fields.
-i, --import Import the CSV file into tablename
-s, --sync Sync tablename with the CSV file fields.
-t, --table <tablename> The target table name.

Arguments:
db The database file to process.


As shown above, the only positional argument is the last, so that requires that all cmd line options that start with a "-" must precede any positional arguments. The Qt behavior with QCommandLineParser seems pretty sane/normal to me, so I'm quite happy to use it rather than have to deal with the differences between getopt(s) on different OS platforms, etc.

TorAn
11th November 2016, 22:01
Thanks! What is still unclear to me is the case where there are more then one "positionalArgument". In your case it is "db" and you expect only one argument there. But what if there are more then one positionalArguments? Like, "db" and "openReadOnly" or something similar? How to distinguish between two or more positionalArguments?

jefftee
11th November 2016, 23:46
See line 16 in the code I posted. Positional args are stored in a QStringList named args, so args.size() will tell you how many positional args were present, and args[0] is the first, args[1] is the 2nd, etc. You can have as many positional args as you want, but they must follow any other options that start with "-" or "--" and you can't have a missing (default) positional argument unless it's the last one (and perhaps only).

Hope that helps.

TorAn
11th November 2016, 23:54
Right, I understood that from the code you posted. Let's say your command line has two positional args, "db1" and "db2" with values ~/db/sqlite1.db and ~/dbsqlite2.db. So, the command line will look like this, right?

tablesync -i -c test.csv -t incident_data ~/db/sqlite1.db ~/dbsqlite2.db.

How do you know which one is db1 and which one is db2?

jefftee
12th November 2016, 00:16
Yes, for the example you posted above, db1 would be ~/db/sqlite1.db and db2 would be ~/db/sqlite2.db. You would define the two positional arguments like so:


parser.addPositionalArgument("db1", "The source database file to process."); // first positional argument
parser.addPositionalArgument("db2", "The destination database file to process."); // second positional argument



Remember, positional args are just that, their usage is based solely in the order in which they appear, not based on their values or naming, etc.

In the example above, where you had two positional arguments: ~/db/sqlite1.db ~/dbsqlite2.db, if you screwed up and entered them in reverse order: ~/db/sqlite2.db ~/db/sqlite1.db, then your program will use ~/db/sqlite2.db as db1 and ~/db/sqlite1.db as db2. Think of it like the DOS copy command or Unix cp command where both take a source and target positional argument, i.e. cp source.txt target.txt, which would copy the source.txt file to a new file called target.txt, etc.

TorAn
12th November 2016, 00:32
Thank you so much for your help and patience. I could not believe my eyes and your explanations up until the very end. I still find it strange that the designer(s) of positional arguments would use argument order to determine the input value, even though the argument names are available. I kind of ignored the name "PositionalArgument", shame on me.
Thanks again.

anda_skoa
12th November 2016, 11:00
Thank you so much for your help and patience. I could not believe my eyes and your explanations up until the very end. I still find it strange that the designer(s) of positional arguments would use argument order to determine the input value, even though the argument names are available.

Positional means that the argument is identified by its position.
I.e. you can't have the second without having the first.

Btw, your original problem sounded more like passing a value to a specific commandline switch, i.e. from your description I would have assumed you have a "p" option that has a parameter that can either be "ttt" or "mmmm".

Cheers,
_

TorAn
12th November 2016, 18:20
Turned out I cannot even use these classes. As I discovered (3 days too late. Clearly spelled out in the documentation, though):

when an error happens (for instance an unknown option was passed), the current process will then stop, using the exit() function.

What a strange design...Hopefully this thread will be a warning to others who want to try using these two classes for something other then parsing command-line arguments.

jefftee
13th November 2016, 02:30
If you want control over error handling for invalid options, use QCommandLineParser::parse() instead of QCommandLineParser::process().