summaryrefslogblamecommitdiffstats
path: root/shargs.hpp
blob: 317ab1e664767b23bf205dff105911cb0c5eb948 (plain) (tree)












































































































































































































































































                                                                                 
#ifndef SHARGS_HPP
#define SHARGS_HPP 1

#include <string>
#include <vector>
#include <memory>
#include <optional>
#include <string_view>
#include <sstream>
#include <functional>

class OptionParser
{
private:
    using arg_iterator = std::vector<std::string_view>::iterator;
    using option_return = std::optional<arg_iterator>;
    using unknown_callback = std::function<void(std::string_view)>;

    struct Option {
        const std::string lform;
        const char sform;

        Option(const std::string &l, const char s) : lform(l), sform(s) {}

        /** Given an iterator to the arg after this, fetch arguments and stop
         * when another option switch is encountered.
         * When fetch fails, return the current it.
         * When fetch encounters the end, return nullopt.
         * @return Iterator to next argument
         * */
        virtual option_return parse(arg_iterator it, arg_iterator end) = 0;
    };

    template<typename T>
    struct TypedOption : public Option {
        T &flag;
        TypedOption(const std::string &l, const char s, T &f) :
            Option(l, s), flag(f) {}

         /** Given an iterator to the arg after this, fetch an argument or stop
         * when an option switch is encountered.
         * When fetch is stopped prematurely, return the current it.
         * When fetch encounters the end, return nullopt.
         * @return Iterator to next argument
         * */
        virtual option_return parse(arg_iterator it, arg_iterator end) {
            if(it == end) return std::nullopt;
            std::string_view itval = *it;

            if(itval[0] == '-') return it;

            std::stringstream flagstream;
            flagstream << itval;
            flagstream >> flag;
            return ++it;
        }
    };

    template<typename T>
    struct MultipleOption : public Option {
        std::vector<T> &args;
        MultipleOption(const std::string &l, const char s, std::vector<T> &a) :
            Option(l, s), args(a) {}

         /** Given an iterator to the arg after this, fetch multiple arguments
         * and stop when an option switch is encountered.
         * When fetch is stopped prematurely, return the current it.
         * When fetch encounters the end, return nullopt.
         * @return Iterator to next argument
         * */
        option_return parse(arg_iterator it, arg_iterator end)
        {
            if(it == end) return end;
            while(it != end && (*it)[0] != '-') {
                std::string_view itval = *it;
                std::stringstream argstream;
                T val;

                argstream << itval;
                argstream >> val;
                args.emplace_back(val);
                it++;
            }
            return it;
        }
    };

    template<typename T>
    struct FlagOption : public TypedOption<T> {
        const T set;
        FlagOption(const std::string &l, const char s, T &f, const T set) :
            TypedOption<T>(l, s, f), set(set) {}

         /** Given an iterator to the arg after this, set the flag and return it.
         * @return Iterator to next argument.
         * */
        option_return parse(arg_iterator it, arg_iterator end) 
        {
            this->flag = set;
            return it;
        }
    };

    std::vector<std::unique_ptr<Option>> m_options;
    unknown_callback m_unknown_callback;
public:

    /** Register an option that takes 1 naked argument.
     * @param longform Full name of the option. preceeded by '--'.
     * @param shortform Short name of the option. preceeded by '-'.
     * @param flag Variable to fill when option is encountered.
     * @return self for chaining.
     * */
    template<typename T>
    inline OptionParser &
    add_option_argument(const std::string &longform,
                        char shortform,
                        T &flag)
    {
        m_options.emplace_back(
                std::make_unique<TypedOption<T>>(longform, shortform, flag));
        return *this;
    }

    /** Register an option that sets a variable when encountered.
     * @param longform Full name of the option. preceeded by '--'.
     * @param shortform Short name of the option. preceeded by '-'.
     * @param flag Variable to fill when option is encountered.
     * @param set Value to fill into flag.
     * @return self for chaining.
     * */
    template<typename T>
    inline OptionParser &
    add_option_flag(const std::string &longform, 
                    char shortform, 
                    T &flag,
                    const T set)
    {
        m_options.emplace_back(
                std::make_unique<FlagOption<T>>(longform, shortform, flag, set));
        return *this;
    }

    /** Register an option that takes multiple naked arguments
     * @param longform Full name of the option. preceeded by '--'.
     * @param shortform Short name of the option. preceeded by '-'.
     * @param flag Vector fill with naked argumens.
     * @return self for chaining.
     * */
    template<typename T>
    inline OptionParser &
    add_option_multiple(const std::string &longform,
                        char shortform,
                        std::vector<T> &args)
    {
        m_options.emplace_back(
                std::make_unique<MultipleOption<T>>(longform, shortform, args));
        return *this;
    }

    /** Add a callback function when parser encounters a flagged argument without
     * a known option.
     * @param func Callback to call.
     * */
    inline void
    on_unknown(unknown_callback func)
    {
        m_unknown_callback = func;
    }

    /** Loop through argv, isolating any naked args and parsing those with an
     * option switch ('-' or '--').
     * If the args list is empty, it will return an empty vector.
     * @param argc Number of arguments passed to the program (provided by main)
     * @param argv NULL-terminated array of string pointers. (provided by main)
     * @return Vector of all arguments which were not eaten by options.
     * */
    std::vector<std::string> parse(int argc, char **argv)
    {
        /* Convert argc, argv into vector of string_view. */
        if(argc <= 1) return std::vector<std::string>();
        std::vector<std::string_view> args(argc - 1);
        for(size_t i = 1; i < argc; i++) {
            args[i - 1] = std::string_view(argv[i]);
        }

        /* Fetch iterator for looping through passed values */
        arg_iterator argit = args.begin();

        /* We want to notify the program if the user input any naked values or
         * passed arguments we don't know. */
        std::vector<std::string> naked;
        std::vector<std::string_view> unknown;

        while(argit != args.end()) {
            std::vector<std::vector<std::unique_ptr<Option>>::iterator> to_parse;
            std::string_view arg = *argit;
            if(arg[0] != '-') {
                naked.emplace_back(arg);
                argit++;
                continue;
            }

            auto optionsit = m_options.begin();
            if(arg[1] == '-') {
                /* Look through longform names. */
                arg.remove_prefix(2);
                for(; optionsit != m_options.end(); optionsit++) {
                    if((*optionsit)->lform.empty()) continue;
                    if((*optionsit)->lform == arg) {
                        to_parse.emplace_back(optionsit);
                        break;
                    }
                }
                if(optionsit == m_options.end()) {
                    unknown.emplace_back(*argit);
                }
            }else{
                /* Look through shortform names.
                 * The user should be able to pass as many shortform args
                 * in one switch as they want. */
                arg.remove_prefix(1);
                for(size_t c = 0; c < arg.size(); c++) {

                    /* If the argument is unknown, add it to the unknown list. */
                    bool valid = false;
                    /* Shortform names have arbitrary flags, so we search
                     * through each character. */
                    for(optionsit = m_options.begin(); 
                        optionsit != m_options.end();
                        optionsit++) {
                        /* Shortform name matches, add opt to to_parse. */
                        if(arg[c] == (*optionsit)->sform) {
                            valid = true;
                            to_parse.emplace_back(optionsit);
                            break;
                        }
                    }

                    if(!valid) {
                        unknown.emplace_back((*argit).substr(c + 1, 1));
                    }
                }
            }

            /* Parse all options in to_parse. */
            argit = argit + 1;
            for(auto it = to_parse.begin(); it != to_parse.end(); it++) {
                auto parse_ret = (*(*it))->parse(argit, args.end());
                /* Parsing encountered args.end()*/
                if(parse_ret == std::nullopt) {
                    return naked;
                }
                argit = parse_ret.value();
            }
        }

        /* Go through all unknown args */
        if(m_unknown_callback) {
            for(auto it : unknown) {
                m_unknown_callback(it);
            }
        }

        return naked;
    }
};

#endif