mirror of
https://github.com/aseprite/aseprite.git
synced 2025-02-21 03:40:57 +00:00
Add ProgramOptions class.
This commit is contained in:
parent
408f54e509
commit
8962c6dbe7
@ -26,6 +26,7 @@ add_library(base-lib
|
||||
memory_dump.cpp
|
||||
mutex.cpp
|
||||
path.cpp
|
||||
program_options.cpp
|
||||
serialization.cpp
|
||||
sha1.cpp
|
||||
sha1_rfc3174.c
|
||||
|
213
src/base/program_options.cpp
Normal file
213
src/base/program_options.cpp
Normal file
@ -0,0 +1,213 @@
|
||||
// ASEPRITE base library
|
||||
// Copyright (C) 2001-2012 David Capello
|
||||
//
|
||||
// This source file is ditributed under a BSD-like license, please
|
||||
// read LICENSE.txt for more information.
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "base/program_options.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace base {
|
||||
|
||||
struct same_name {
|
||||
const string& name;
|
||||
same_name(const string& name) : name(name) { }
|
||||
bool operator()(const ProgramOptions::Option* a) {
|
||||
return a->name() == name;
|
||||
}
|
||||
};
|
||||
|
||||
struct same_mnemonic {
|
||||
char mnemonic;
|
||||
same_mnemonic(char mnemonic) : mnemonic(mnemonic) { }
|
||||
bool operator()(const ProgramOptions::Option* a) {
|
||||
return a->mnemonic() == mnemonic;
|
||||
}
|
||||
};
|
||||
|
||||
ProgramOptions::ProgramOptions()
|
||||
{
|
||||
}
|
||||
|
||||
ProgramOptions::~ProgramOptions()
|
||||
{
|
||||
for (OptionList::const_iterator
|
||||
it=m_options.begin(), end=m_options.end(); it != end; ++it)
|
||||
delete *it;
|
||||
}
|
||||
|
||||
ProgramOptions::Option& ProgramOptions::add(const string& name)
|
||||
{
|
||||
Option* option = new Option(name);
|
||||
m_options.push_back(option);
|
||||
return *option;
|
||||
}
|
||||
|
||||
void ProgramOptions::parse(int argc, const char* argv[])
|
||||
{
|
||||
for (int i=1; i<argc; ++i) {
|
||||
string arg(argv[i]);
|
||||
|
||||
// n = number of dashes ('-') at the beginning of the argument.
|
||||
size_t n = 0;
|
||||
for (; arg[n] == '-'; ++n)
|
||||
;
|
||||
size_t len = arg.size()-n;
|
||||
|
||||
if ((n > 0) && (len > 0)) {
|
||||
// Use mnemonics
|
||||
if (n == 1) {
|
||||
char usedBy = 0;
|
||||
|
||||
for (size_t j=1; j<arg.size(); ++j) {
|
||||
OptionList::iterator it =
|
||||
find_if(m_options.begin(), m_options.end(), same_mnemonic(arg[j]));
|
||||
|
||||
if (it == m_options.end()) {
|
||||
stringstream msg;
|
||||
msg << "Invalid option '-" << arg[j] << "'";
|
||||
throw InvalidProgramOption(msg.str());
|
||||
}
|
||||
|
||||
Option* option = *it;
|
||||
option->setEnabled(true);
|
||||
|
||||
if (option->doesRequireValue()) {
|
||||
if (usedBy != 0) {
|
||||
stringstream msg;
|
||||
msg << "You cannot use '-" << option->mnemonic()
|
||||
<< "' and '-" << usedBy << "' "
|
||||
<< "together, both options need one extra argument";
|
||||
throw InvalidProgramOptionsCombination(msg.str());
|
||||
}
|
||||
|
||||
if (i+1 >= argc) {
|
||||
stringstream msg;
|
||||
msg << "Option '-" << option->mnemonic()
|
||||
<< "' needs one extra argument";
|
||||
throw ProgramOptionNeedsValue(msg.str());
|
||||
}
|
||||
|
||||
// Set the value specified for this argument
|
||||
option->setValue(argv[++i]);
|
||||
usedBy = option->mnemonic();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Use name
|
||||
else {
|
||||
string optionName;
|
||||
string optionValue;
|
||||
size_t equalSignPos = arg.find('=', n);
|
||||
|
||||
if (equalSignPos != string::npos) {
|
||||
optionName = arg.substr(n, equalSignPos-n);
|
||||
optionValue = arg.substr(equalSignPos+1);
|
||||
}
|
||||
else {
|
||||
optionName = arg.substr(n);
|
||||
}
|
||||
|
||||
OptionList::iterator it =
|
||||
find_if(m_options.begin(), m_options.end(), same_name(optionName));
|
||||
|
||||
if (it == m_options.end()) {
|
||||
stringstream msg;
|
||||
msg << "Invalid option '--" << optionName << "'";
|
||||
throw InvalidProgramOption(msg.str());
|
||||
}
|
||||
|
||||
Option* option = *it;
|
||||
option->setEnabled(true);
|
||||
|
||||
if (option->doesRequireValue()) {
|
||||
// If the option was specified without '=', we can get the
|
||||
// value from the next argument.
|
||||
if (equalSignPos == string::npos) {
|
||||
if (i+1 >= argc) {
|
||||
stringstream msg;
|
||||
msg << "Missing value in '--" << optionName
|
||||
<< "'=VALUE option specification";
|
||||
throw ProgramOptionNeedsValue(msg.str());
|
||||
}
|
||||
optionValue = argv[++i];
|
||||
}
|
||||
|
||||
// Set the value specified for this argument
|
||||
option->setValue(optionValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add values
|
||||
else {
|
||||
m_values.push_back(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProgramOptions::reset()
|
||||
{
|
||||
m_values.clear();
|
||||
for_each(m_options.begin(), m_options.end(), &ProgramOptions::resetOption);
|
||||
}
|
||||
|
||||
// static
|
||||
void ProgramOptions::resetOption(Option* option)
|
||||
{
|
||||
option->setEnabled(false);
|
||||
option->setValue("");
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const base::ProgramOptions& po)
|
||||
{
|
||||
size_t maxOptionWidth = 0;
|
||||
for (base::ProgramOptions::OptionList::const_iterator
|
||||
it=po.options().begin(), end=po.options().end(); it != end; ++it) {
|
||||
const base::ProgramOptions::Option* option = *it;
|
||||
size_t optionWidth = std::min<int>(26, 6+option->name().size()+1);
|
||||
if (maxOptionWidth < optionWidth)
|
||||
maxOptionWidth = optionWidth;
|
||||
}
|
||||
|
||||
for (base::ProgramOptions::OptionList::const_iterator
|
||||
it=po.options().begin(), end=po.options().end(); it != end; ++it) {
|
||||
const base::ProgramOptions::Option* option = *it;
|
||||
size_t optionWidth = 6+option->name().size()+1;
|
||||
|
||||
if (option->mnemonic() != 0)
|
||||
os << setw(3) << '-' << option->mnemonic() << ", ";
|
||||
else
|
||||
os << setw(6) << ' ';
|
||||
os << "--" << option->name();
|
||||
|
||||
if (!option->description().empty()) {
|
||||
bool multilines = (option->description().find('\n') != string::npos);
|
||||
|
||||
if (!multilines) {
|
||||
os << setw(maxOptionWidth - optionWidth + 1) << ' ' << option->description();
|
||||
}
|
||||
else {
|
||||
istringstream s(option->description());
|
||||
std::string line;
|
||||
if (std::getline(s, line)) {
|
||||
os << setw(maxOptionWidth - optionWidth + 1) << ' ' << line << '\n';
|
||||
while (std::getline(s, line)) {
|
||||
os << setw(maxOptionWidth+2) << ' ' << line << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
os << "\n";
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
114
src/base/program_options.h
Normal file
114
src/base/program_options.h
Normal file
@ -0,0 +1,114 @@
|
||||
// ASEPRITE base library
|
||||
// Copyright (C) 2001-2012 David Capello
|
||||
//
|
||||
// This source file is ditributed under a BSD-like license, please
|
||||
// read LICENSE.txt for more information.
|
||||
|
||||
#ifndef BASE_PROGRAM_OPTIONS_H_INCLUDED
|
||||
#define BASE_PROGRAM_OPTIONS_H_INCLUDED
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iosfwd>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace base {
|
||||
|
||||
class InvalidProgramOption : public std::runtime_error {
|
||||
public:
|
||||
InvalidProgramOption(const std::string& msg)
|
||||
: std::runtime_error(msg) { }
|
||||
};
|
||||
|
||||
class InvalidProgramOptionsCombination : public std::runtime_error {
|
||||
public:
|
||||
InvalidProgramOptionsCombination(const std::string& msg)
|
||||
: std::runtime_error(msg) { }
|
||||
};
|
||||
|
||||
class ProgramOptionNeedsValue : public std::runtime_error {
|
||||
public:
|
||||
ProgramOptionNeedsValue(const std::string& msg)
|
||||
: std::runtime_error(msg) { }
|
||||
};
|
||||
|
||||
class ProgramOptions {
|
||||
public:
|
||||
class Option {
|
||||
public:
|
||||
Option(const std::string& name)
|
||||
: m_name(name)
|
||||
, m_mnemonic(0)
|
||||
, m_enabled(false)
|
||||
, m_requiresValue(false) {
|
||||
}
|
||||
// Getters
|
||||
const std::string& name() const { return m_name; }
|
||||
const std::string& description() const { return m_description; }
|
||||
const std::string& value() const { return m_value; }
|
||||
char mnemonic() const { return m_mnemonic; }
|
||||
bool enabled() const { return m_enabled; }
|
||||
bool doesRequireValue() const { return m_requiresValue; }
|
||||
// Setters
|
||||
Option& description(const std::string& desc) { m_description = desc; return *this; }
|
||||
Option& mnemonic(char mnemonic) { m_mnemonic = mnemonic; return *this; }
|
||||
Option& requiresValue() { m_requiresValue = true; return *this; }
|
||||
private:
|
||||
void setValue(const std::string& value) { m_value = value; }
|
||||
void setEnabled(bool enabled) { m_enabled = enabled; }
|
||||
|
||||
std::string m_name; // Name of the option (e.g. "help" for "--help")
|
||||
std::string m_description; // Description of the option (this can be used when the help is printed).
|
||||
std::string m_value; // The value specified by the user in the command line.
|
||||
char m_mnemonic; // One character that can be used in the command line to use this option.
|
||||
bool m_enabled; // True if the user specified this argument.
|
||||
bool m_requiresValue; // True if this option needs another argument.
|
||||
|
||||
friend class ProgramOptions;
|
||||
};
|
||||
|
||||
typedef std::vector<Option*> OptionList;
|
||||
typedef std::vector<std::string> ValueList;
|
||||
|
||||
ProgramOptions();
|
||||
|
||||
// After destructing the ProgramOptions, you cannot continue using
|
||||
// references to "Option" instances obtained through add() function.
|
||||
~ProgramOptions();
|
||||
|
||||
// Adds a option for the program. The options must be specified
|
||||
// before calling parse(). The returned reference must be used in
|
||||
// the ProgramOptions lifetime.
|
||||
Option& add(const std::string& name);
|
||||
|
||||
// Detects which options where specified in the command line.
|
||||
void parse(int argc, const char* argv[]);
|
||||
|
||||
// Reset all options values/flags.
|
||||
void reset();
|
||||
|
||||
// Returns the list of available options. To know the list of
|
||||
// specified options you can iterate this list asking for
|
||||
// Option::enabled() flag to know if the option was specified by
|
||||
// the user in the command line.
|
||||
const OptionList& options() const { return m_options; }
|
||||
|
||||
// Returns the list of values that are not associated to any
|
||||
// options. E.g. a list of files specified in the command line to
|
||||
// be opened.
|
||||
const ValueList& values() const { return m_values; }
|
||||
|
||||
private:
|
||||
static void resetOption(Option* option);
|
||||
|
||||
OptionList m_options;
|
||||
ValueList m_values;
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
// Prints the program options correctly formatted to be read by
|
||||
// the user. E.g. This can be used in a --help option.
|
||||
std::ostream& operator<<(std::ostream& os, const base::ProgramOptions& po);
|
||||
|
||||
#endif
|
147
src/base/program_options_unittest.cpp
Normal file
147
src/base/program_options_unittest.cpp
Normal file
@ -0,0 +1,147 @@
|
||||
// ASEPRITE base library
|
||||
// Copyright (C) 2001-2012 David Capello
|
||||
//
|
||||
// This source file is ditributed under a BSD-like license, please
|
||||
// read LICENSE.txt for more information.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "base/program_options.h"
|
||||
|
||||
using namespace base;
|
||||
|
||||
TEST(ProgramOptions, OptionMembers)
|
||||
{
|
||||
ProgramOptions po;
|
||||
ProgramOptions::Option& help =
|
||||
po.add("help").mnemonic('h').description("Show the help");
|
||||
ProgramOptions::Option& output =
|
||||
po.add("output").mnemonic('O').requiresValue();
|
||||
|
||||
EXPECT_EQ("help", help.name());
|
||||
EXPECT_EQ("Show the help", help.description());
|
||||
EXPECT_EQ('h', help.mnemonic());
|
||||
EXPECT_FALSE(help.enabled());
|
||||
EXPECT_FALSE(help.doesRequireValue());
|
||||
|
||||
EXPECT_EQ("output", output.name());
|
||||
EXPECT_EQ("", output.description());
|
||||
EXPECT_EQ('O', output.mnemonic());
|
||||
EXPECT_FALSE(output.enabled());
|
||||
EXPECT_TRUE(output.doesRequireValue());
|
||||
}
|
||||
|
||||
TEST(ProgramOptions, Reset)
|
||||
{
|
||||
ProgramOptions po;
|
||||
ProgramOptions::Option& help = po.add("help");
|
||||
ProgramOptions::Option& file = po.add("file").requiresValue();
|
||||
EXPECT_FALSE(help.enabled());
|
||||
EXPECT_FALSE(file.enabled());
|
||||
EXPECT_EQ("", file.value());
|
||||
|
||||
const char* argv[] = { "program.exe", "--help", "--file=readme.txt" };
|
||||
po.parse(3, argv);
|
||||
EXPECT_TRUE(help.enabled());
|
||||
EXPECT_TRUE(file.enabled());
|
||||
EXPECT_EQ("readme.txt", file.value());
|
||||
|
||||
po.reset();
|
||||
EXPECT_FALSE(help.enabled());
|
||||
EXPECT_FALSE(file.enabled());
|
||||
EXPECT_EQ("", file.value());
|
||||
}
|
||||
|
||||
TEST(ProgramOptions, Parse)
|
||||
{
|
||||
ProgramOptions po;
|
||||
ProgramOptions::Option& help = po.add("help").mnemonic('?');
|
||||
ProgramOptions::Option& input = po.add("input").mnemonic('i').requiresValue();
|
||||
ProgramOptions::Option& output = po.add("output").mnemonic('o').requiresValue();
|
||||
|
||||
const char* argv1[] = { "program.exe", "-?" };
|
||||
po.parse(2, argv1);
|
||||
EXPECT_TRUE(help.enabled());
|
||||
|
||||
const char* argv2[] = { "program.exe", "--help" };
|
||||
po.reset();
|
||||
po.parse(2, argv2);
|
||||
EXPECT_TRUE(help.enabled());
|
||||
|
||||
const char* argv3[] = { "program.exe", "--input", "hello.cpp", "--output", "hello.exe" };
|
||||
po.reset();
|
||||
po.parse(5, argv3);
|
||||
EXPECT_FALSE(help.enabled());
|
||||
EXPECT_TRUE(input.enabled());
|
||||
EXPECT_TRUE(output.enabled());
|
||||
EXPECT_EQ("hello.cpp", input.value());
|
||||
EXPECT_EQ("hello.exe", output.value());
|
||||
|
||||
const char* argv4[] = { "program.exe", "--input=hi.c", "--output=out.exe" };
|
||||
po.reset();
|
||||
po.parse(3, argv4);
|
||||
EXPECT_FALSE(help.enabled());
|
||||
EXPECT_TRUE(input.enabled());
|
||||
EXPECT_TRUE(output.enabled());
|
||||
EXPECT_EQ("hi.c", input.value());
|
||||
EXPECT_EQ("out.exe", output.value());
|
||||
|
||||
const char* argv5[] = { "program.exe", "-?i", "input.md", "-o", "output.html", "extra-file.txt" };
|
||||
po.reset();
|
||||
po.parse(6, argv5);
|
||||
EXPECT_TRUE(help.enabled());
|
||||
EXPECT_TRUE(input.enabled());
|
||||
EXPECT_TRUE(output.enabled());
|
||||
EXPECT_EQ("input.md", input.value());
|
||||
EXPECT_EQ("output.html", output.value());
|
||||
ASSERT_EQ(1, po.values().size());
|
||||
EXPECT_EQ("extra-file.txt", po.values()[0]);
|
||||
|
||||
const char* argv6[] = { "program.exe", "value1", "value2", "-o", "output", "value3", "--input=input", "value4" };
|
||||
po.reset();
|
||||
po.parse(8, argv6);
|
||||
ASSERT_EQ(4, po.values().size());
|
||||
EXPECT_EQ("value1", po.values()[0]);
|
||||
EXPECT_EQ("value2", po.values()[1]);
|
||||
EXPECT_EQ("value3", po.values()[2]);
|
||||
EXPECT_EQ("value4", po.values()[3]);
|
||||
}
|
||||
|
||||
TEST(ProgramOptions, ParseErrors)
|
||||
{
|
||||
ProgramOptions po;
|
||||
ProgramOptions::Option& help = po.add("help").mnemonic('?');
|
||||
ProgramOptions::Option& input = po.add("input").mnemonic('i').requiresValue();
|
||||
ProgramOptions::Option& output = po.add("output").mnemonic('o').requiresValue();
|
||||
|
||||
const char* argv1[] = { "program.exe", "--input" };
|
||||
EXPECT_THROW(po.parse(2, argv1), ProgramOptionNeedsValue);
|
||||
|
||||
const char* argv2[] = { "program.exe", "-i" };
|
||||
EXPECT_THROW(po.parse(2, argv2), ProgramOptionNeedsValue);
|
||||
|
||||
const char* argv3[] = { "program.exe", "--test" };
|
||||
EXPECT_THROW(po.parse(2, argv3), InvalidProgramOption);
|
||||
|
||||
const char* argv4[] = { "program.exe", "-?a" };
|
||||
po.reset();
|
||||
EXPECT_FALSE(help.enabled());
|
||||
EXPECT_THROW(po.parse(2, argv4), InvalidProgramOption);
|
||||
EXPECT_TRUE(help.enabled()); // -? is parsed anyway, -a is the invalid option
|
||||
|
||||
const char* argv5[] = { "program.exe", "-io", "input-and-output.txt" };
|
||||
po.reset();
|
||||
EXPECT_THROW(po.parse(2, argv5), ProgramOptionNeedsValue);
|
||||
po.reset();
|
||||
EXPECT_THROW(po.parse(3, argv5), InvalidProgramOptionsCombination);
|
||||
EXPECT_TRUE(input.enabled());
|
||||
EXPECT_TRUE(output.enabled());
|
||||
EXPECT_EQ("input-and-output.txt", input.value());
|
||||
EXPECT_EQ("", output.value());
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user