2

When using cv::CommandLineParser, is it possible to parse an arbitrarily long list of non-flag args?
For example:

> app -a -b=2 a.txt b.txt c.txt ... 

I want to get access to all the non-flag "positional" args, without having to define a predefined number of them in the keys specification. This number is only determined by the caller of the app.

Adi Shavit
  • 15,030
  • 2
  • 55
  • 121
  • Will be an option for you to provide a parameter (e.g. `-N`) that specify the number of positional arguments? i.e. `app -a -b=2 -N=3 a.txt b.txt c.txt`? – Miki Oct 14 '15 at 15:28
  • No. That's not an option. The thing is that it seems like they are never parsed and by the time I may have the actual number it is too late since the positional args are specified at compile time. – Adi Shavit Oct 14 '15 at 16:28
  • Ok, I came up with a potential solution. Both options should fit your requirements. Option 2 doesn't require the extra parameter. Please let me know. – Miki Oct 14 '15 at 16:33

1 Answers1

1

OpenCV CommandLineParser cannot handle a variable number of positional arguments. You can:

  1. Pass an extra argument (e.g. -N) specifying the number of positional arguments;
  2. Consider every argument that doesn't start with - to be a positional argument.

You can then make a custom command line parser with the same interface as cv::CommandLineParser able to handle a variable number of positional arguments.

Option 1

struct CustomCLP
{
    CustomCLP(int argc, const char* const argv[], const cv::String& keys, const String& positional_id)
    {
        String pos_key = "{" + positional_id + "|0|}";
        cv::CommandLineParser pos_clp(argc, argv, pos_key);
        _N = pos_clp.get<int>(positional_id);

        cv::String pos_keys = keys;
        for (int i=0; i<_N; ++i)
        {
            pos_keys += "{@pos" + to_string(i) + "||}";
        }

        _clp = new CommandLineParser(argc, argv, pos_keys);
    }

    ~CustomCLP()
    {
        delete _clp;
    }

    bool check() const {return _clp->check();}
    bool has(const cv::String& name) const {return _clp->has(name);}

    template<typename T>
    T get(const String& name, bool space_delete = true) const
    {
        return _clp->get<T>(name, space_delete);
    }
    template<typename T>
    T get(int index, bool space_delete = true) const
    {
        return _clp->get<T>(index, space_delete);
    }

    int n_positional_args() const { return _N;}

private:
    CommandLineParser* _clp;
    int _N;
};

You can specify the extra argument (e.g. -N) that specify the number of positional arguments. In the constructor, you parse this number, and create a key for each positional argument. Then you can use it as always.

Option 2

// SO: http://stackoverflow.com/a/17976541/5008845
inline std::string trim(const std::string &s)
{
    auto  wsfront = std::find_if_not(s.begin(), s.end(), std::isspace);
    return std::string(wsfront, std::find_if_not(s.rbegin(), std::string::const_reverse_iterator(wsfront), std::isspace).base());
}

struct CustomCLP2
{
    CommandLineParser _clp;
    vector<std::string> pos_args;

public:
    CustomCLP2(int argc, const char* const argv[], const cv::String& keys) :
        _clp(argc, argv, keys)
    {
        for (int i = 1; i < argc; ++i)
        {
            std::string s(argv[i]);
            s = trim(s);
            if (s[0] == '-') continue;

            pos_args.push_back(s);
        }
    }

    bool check() const { return _clp.check(); }
    bool has(const cv::String& name) const { return _clp.has(name); }

    template<typename T>
    T get(const String& name, bool space_delete = true) const
    {
        return _clp.get<T>(name, space_delete);
    }

    template<typename T>
    T get(int index, bool space_delete = true) const
    {
        stringstream ss;
        ss << pos_args[index];
        T t;
        ss >> t;
        return t;
    }

    template<>
    cv::String get(int index, bool space_delete) const
    {
        return cv::String(pos_args[index]);
    }

    int n_positional_args() const { return pos_args.size(); }
};

In the constructor you save every argument that doesn't start with -. Then you can retrieve it as usual.

Usage

Note that the interface is consistent with CommandLineParser (a few methods like about, getPathToApplication are not available in this example, but it's easy to add them to the custom class).

Command line arguments are:

Option 1: -a -b=2 -N=3 a.txt b.txt c.txt
Option 2: -a -b=2 a.txt b.txt c.txt

Code:

#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;


struct CustomCLP
{
    CustomCLP(int argc, const char* const argv[], const cv::String& keys, const String& positional_id)
    {
        String pos_key = "{" + positional_id + "|0|}";
        cv::CommandLineParser pos_clp(argc, argv, pos_key);
        _N = pos_clp.get<int>(positional_id);

        cv::String pos_keys = keys;
        for (int i=0; i<_N; ++i)
        {
            pos_keys += "{@pos" + to_string(i) + "||}";
        }

        _clp = new CommandLineParser(argc, argv, pos_keys);
    }

    ~CustomCLP()
    {
        delete _clp;
    }

    bool check() const {return _clp->check();}
    bool has(const cv::String& name) const {return _clp->has(name);}

    template<typename T>
    T get(const String& name, bool space_delete = true) const
    {
        return _clp->get<T>(name, space_delete);
    }
    template<typename T>
    T get(int index, bool space_delete = true) const
    {
        return _clp->get<T>(index, space_delete);
    }

    int n_positional_args() const { return _N;}

private:
    CommandLineParser* _clp;
    int _N;
};

// SO: http://stackoverflow.com/a/17976541/5008845
inline std::string trim(const std::string &s)
{
    auto  wsfront = std::find_if_not(s.begin(), s.end(), std::isspace);
    return std::string(wsfront, std::find_if_not(s.rbegin(), std::string::const_reverse_iterator(wsfront), std::isspace).base());
}

struct CustomCLP2
{
    CommandLineParser _clp;
    vector<std::string> pos_args;

public:
    CustomCLP2(int argc, const char* const argv[], const cv::String& keys) :
        _clp(argc, argv, keys)
    {
        for (int i = 1; i < argc; ++i)
        {
            std::string s(argv[i]);
            s = trim(s);
            if (s[0] == '-') continue;

            pos_args.push_back(s);
        }
    }

    bool check() const { return _clp.check(); }
    bool has(const cv::String& name) const { return _clp.has(name); }

    template<typename T>
    T get(const String& name, bool space_delete = true) const
    {
        return _clp.get<T>(name, space_delete);
    }

    template<typename T>
    T get(int index, bool space_delete = true) const
    {
        stringstream ss;
        ss << pos_args[index];
        T t;
        ss >> t;
        return t;
    }

    template<>
    cv::String get(int index, bool space_delete) const
    {
        return cv::String(pos_args[index]);
    }

    int n_positional_args() const { return pos_args.size(); }
};


int main(int argc, char* argv[])
{
    String keys =   
        "{a | | whatever a}"
        "{b | 1 | whatever b}";

    //CustomCLP clp(argc, argv, keys, "N");
    CustomCLP2 clp(argc, argv, keys);

    if (clp.has("a")) {
        cout << "Has <a>" << endl;
    }
    else {
        cout << "Doesn't have <a>";
    }

    int b = clp.get<int>("b");
    cout << "<b> : " << b << endl;

    int N = clp.n_positional_args();

    for (int i = 0; i < N; ++i)
    {
        cout << to_string(i) << ": " << clp.get<cv::String>(i) << endl;
    }

    return 0;
}
Miki
  • 37,220
  • 12
  • 98
  • 183
  • Thanks! I like option 2 better, because it doesn't change `keys` which would require saving the usage string before modifying it. – Adi Shavit Oct 15 '15 at 05:54