xaizek / uncov (License: AGPLv3+) (since 2018-12-07)
Uncov(er) is a tool that collects and processes code coverage reports.
<root> / src / Invocation.cpp (cad27a63de7fda4cf26ccb529ce9d1b006e6c7ab) (4,969B) (mode 100644) [raw]
// Copyright (C) 2016 xaizek <xaizek@posteo.net>
//
// This file is part of uncov.
//
// uncov is free software: you can redistribute it and/or modify
// it under the terms of version 3 of the GNU Affero General Public License as
// published by the Free Software Foundation.
//
// uncov is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with uncov.  If not, see <http://www.gnu.org/licenses/>.

#include "Invocation.hpp"

#include <boost/program_options.hpp>

#include <limits>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

namespace po = boost::program_options;

static po::variables_map parseOptions(const std::vector<std::string> &args);
static std::vector<po::option>
stopAtFirstPositional(std::vector<std::string> &args);

Invocation::Invocation(std::vector<std::string> args)
{
    if (args.empty()) {
        throw std::invalid_argument("Broken argument list.");
    }

    // Extract program name.
    programName = args[0];
    args.erase(args.cbegin());

    if (args.empty()) {
        error = "No arguments.";
        return;
    }

    po::variables_map varMap;
    try {
        varMap = parseOptions(args);
    } catch (const std::exception &e) {
        error = e.what();
        return;
    }

    printHelp = varMap.count("help");
    printVersion = varMap.count("version");
    args = varMap["positional"].as<std::vector<std::string>>();

    if (printHelp || printVersion) {
        return;
    }

    if (!args.empty()) {
        auto isPath = [](const std::string &s) {
            return s.substr(0, 1) == "." || s.find('/') != std::string::npos;
        };

        // Extract path to repository.
        if (isPath(args.front())) {
            repositoryPath = args.front();
            args.erase(args.cbegin());
        } else {
            repositoryPath = ".";
        }
    }

    if (args.empty()) {
        error = "No subcommand specified.";
        return;
    }

    // Extract subcommand and its arguments.
    subcommandName = args.front();
    args.erase(args.cbegin());
    subcommandArgs = std::move(args);
}

/**
 * @brief Parses command line-options.
 *
 * Positional arguments are returned in "positional" entry, which exists even
 * when there is no positional arguments.
 *
 * @param args Command-line arguments.
 *
 * @returns Variables map of option values.
 */
static po::variables_map
parseOptions(const std::vector<std::string> &args)
{
    po::options_description hiddenOpts;
    hiddenOpts.add_options()
        ("positional", po::value<std::vector<std::string>>()
                       ->default_value({}, ""),
         "positional args");

    po::positional_options_description positionalOptions;
    positionalOptions.add("positional", -1);

    po::options_description cmdlineOptions;

    cmdlineOptions.add_options()
        ("help,h", "display help message")
        ("version,v", "display version");

    po::options_description allOptions;
    allOptions.add(cmdlineOptions).add(hiddenOpts);

    auto parsed_from_cmdline =
        po::command_line_parser(args)
        .options(allOptions)
        .positional(positionalOptions)
        .extra_style_parser(&stopAtFirstPositional)
        .run();

    po::variables_map varMap;
    po::store(parsed_from_cmdline, varMap);
    return varMap;
}

/**
 * @brief Command-line option parser that captures as positional argument any
 *        element starting from the first positional argument.
 *
 * @param args Arguments.
 *
 * @returns Parsed arguments.
 */
static std::vector<po::option>
stopAtFirstPositional(std::vector<std::string> &args)
{
    std::vector<po::option> result;
    const std::string &tok = args[0];
    if (!tok.empty() && tok.front() != '-') {
        for (unsigned int i = 0U; i < args.size(); ++i) {
            po::option opt;
            opt.value.push_back(args[i]);
            opt.original_tokens.push_back(args[i]);
            opt.position_key = std::numeric_limits<int>::max();
            result.push_back(opt);
        }
        args.clear();
    }
    return result;
}

std::string
Invocation::getUsage() const
{
    return "Usage: " + programName
         + " [--help|-h] [--version|-v] [repo] subcommand [args...]";
}

const std::string &
Invocation::getError() const
{
    return error;
}

const std::string &
Invocation::getRepositoryPath() const
{
    return repositoryPath;
}

const std::string &
Invocation::getSubcommandName() const
{
    return subcommandName;
}

const std::vector<std::string> &
Invocation::getSubcommandArgs() const
{
    return subcommandArgs;
}

bool
Invocation::shouldPrintHelp() const
{
    return printHelp;
}

bool
Invocation::shouldPrintVersion() const
{
    return printVersion;
}
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/uncov

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/uncov

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master