Change the way ProgramOptions store specified args

Now we can parse command line arguments sequentially in the same order they
were specified.
This commit is contained in:
David Capello 2014-11-06 09:42:09 -03:00
parent ba73bcafed
commit 94a642cd36
9 changed files with 140 additions and 110 deletions

View File

@ -111,9 +111,7 @@ public:
App* App::m_instance = NULL;
// Initializes the application loading the modules, setting the
// graphics mode, loading the configuration and resources, etc.
App::App(int argc, const char* argv[])
App::App()
: m_modules(NULL)
, m_legacy(NULL)
, m_isGui(false)
@ -122,14 +120,18 @@ App::App(int argc, const char* argv[])
{
ASSERT(m_instance == NULL);
m_instance = this;
}
void App::initialize(int argc, const char* argv[])
{
AppOptions options(argc, argv);
// Initializes the application loading the modules, setting the
// graphics mode, loading the configuration and resources, etc.
m_modules = new Modules(!options.startUI(), options.verbose());
m_isGui = options.startUI();
m_isShell = options.startShell();
m_legacy = new LegacyModules(isGui() ? REQUIRE_INTERFACE: 0);
m_files = options.files();
if (options.hasExporterParams()) {
m_exporter.reset(new DocumentExporter);
@ -177,13 +179,9 @@ App::App(int argc, const char* argv[])
// Set system palette to the default one.
set_current_palette(NULL, true);
}
int App::run()
{
UIContext* context = UIContext::instance();
// Initialize GUI interface
UIContext* context = UIContext::instance();
if (isGui()) {
PRINTF("GUI mode\n");
@ -215,9 +213,15 @@ int App::run()
// Procress options
PRINTF("Processing options...\n");
// Open file specified in the command line
{
Console console;
for (const std::string& filename : m_files) {
for (const auto& value : options.values()) {
if (value.option() != NULL) // File names aren't associated to any option
continue;
const std::string& filename = value.value();
// Load the sprite
Document* document = load_document(context, filename.c_str());
if (!document) {
@ -245,7 +249,10 @@ int App::run()
m_exporter->exportSheet();
m_exporter.reset(NULL);
}
}
void App::run()
{
// Run the GUI
if (isGui()) {
#ifdef ENABLE_UPDATER
@ -301,8 +308,6 @@ int App::run()
std::cerr << "Your version of " PACKAGE " wasn't compiled with shell support.\n";
}
}
return 0;
}
// Finishes the Aseprite application.

View File

@ -51,7 +51,7 @@ namespace app {
class App {
public:
App(int argc, const char* argv[]);
App();
~App();
static App* instance() { return m_instance; }
@ -63,8 +63,10 @@ namespace app {
bool isPortable();
// Runs the Aseprite application. In GUI mode it's the top-level
// window, in console/scripting it just runs the specified scripts.
int run();
// window, in console/scripting it just runs the specified
// scripts.
void initialize(int argc, const char* argv[]);
void run();
tools::ToolBox* getToolBox() const;
RecentFiles* getRecentFiles() const;

View File

@ -57,27 +57,27 @@ AppOptions::AppOptions(int argc, const char* argv[])
try {
m_po.parse(argc, argv);
m_verbose = verbose.enabled();
m_paletteFileName = palette.value();
m_startShell = shell.enabled();
// m_dataFormat = dataFormat.value();
m_data = data.value();
// m_textureFormat = textureFormat.value();
m_sheet = sheet.value();
m_verbose = m_po.enabled(verbose);
m_paletteFileName = m_po.value_of(palette);
m_startShell = m_po.enabled(shell);
// m_dataFormat = m_po.value_of(dataFormat);
m_data = m_po.value_of(data);
// m_textureFormat = m_po.value_of(textureFormat);
m_sheet = m_po.value_of(sheet);
// if (scale.enabled())
// m_scale = std::strtod(scale.value().c_str(), NULL);
// m_scaleMode = scaleMode.value();
// m_scale = std::strtod(m_po.value_of(scale).c_str(), NULL);
// m_scaleMode = m_po.value_of(scaleMode);
if (help.enabled()) {
if (m_po.enabled(help)) {
showHelp();
m_startUI = false;
}
else if (version.enabled()) {
else if (m_po.enabled(version)) {
showVersion();
m_startUI = false;
}
if (shell.enabled() || batch.enabled()) {
if (m_po.enabled(shell) || m_po.enabled(batch)) {
m_startUI = false;
}
}

View File

@ -38,7 +38,7 @@ public:
const std::string& paletteFileName() const { return m_paletteFileName; }
const base::ProgramOptions::ValueList& files() const {
const base::ProgramOptions::ValueList& values() const {
return m_po.values();
}

View File

@ -85,7 +85,7 @@ void ProgramOptions::parse(int argc, const char* argv[])
}
Option* option = *it;
option->setEnabled(true);
std::string optionValue;
if (option->doesRequireValue()) {
if (usedBy != 0) {
@ -104,9 +104,11 @@ void ProgramOptions::parse(int argc, const char* argv[])
}
// Set the value specified for this argument
option->setValue(argv[++i]);
optionValue = argv[++i];
usedBy = option->mnemonic();
}
m_values.push_back(Value(option, optionValue));
}
}
// Use name
@ -133,7 +135,6 @@ void ProgramOptions::parse(int argc, const char* argv[])
}
Option* option = *it;
option->setEnabled(true);
if (option->doesRequireValue()) {
// If the option was specified without '=', we can get the
@ -147,15 +148,14 @@ void ProgramOptions::parse(int argc, const char* argv[])
}
optionValue = argv[++i];
}
// Set the value specified for this argument
option->setValue(optionValue);
}
m_values.push_back(Value(option, optionValue));
}
}
// Add values
// Add values without a related option.
else {
m_values.push_back(arg);
m_values.push_back(Value(NULL, arg));
}
}
}
@ -163,14 +163,24 @@ void ProgramOptions::parse(int argc, const char* argv[])
void ProgramOptions::reset()
{
m_values.clear();
for_each(m_options.begin(), m_options.end(), &ProgramOptions::resetOption);
}
// static
void ProgramOptions::resetOption(Option* option)
bool ProgramOptions::enabled(const Option& option) const
{
option->setEnabled(false);
option->setValue("");
for (const auto& value : m_values) {
if (value.option() == &option)
return true;
}
return false;
}
std::string ProgramOptions::value_of(const Option& option) const
{
for (const auto& value : m_values) {
if (value.option() == &option)
return value.value();
}
return "";
}
} // namespace base

View File

@ -39,16 +39,13 @@ namespace base {
public:
Option(const std::string& name)
: m_name(name)
, m_mnemonic(0)
, m_enabled(false) {
, m_mnemonic(0) {
}
// 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; }
const std::string& getValueName() const { return m_valueName; }
char mnemonic() const { return m_mnemonic; }
bool enabled() const { return m_enabled; }
bool doesRequireValue() const { return !m_valueName.empty(); }
// Setters
Option& description(const std::string& desc) { m_description = desc; return *this; }
@ -58,21 +55,29 @@ namespace base {
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.
std::string m_valueName; // Empty if this option doesn't require a value, or the name of the expected value.
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.
friend class ProgramOptions;
};
class Value {
public:
Value(Option* option, const std::string& value)
: m_option(option)
, m_value(value) {
}
const Option* option() const { return m_option; }
const std::string& value() const { return m_value; }
private:
Option* m_option;
std::string m_value;
};
typedef std::vector<Option*> OptionList;
typedef std::vector<std::string> ValueList;
typedef std::vector<Value> ValueList;
ProgramOptions();
@ -88,23 +93,19 @@ namespace base {
// Detects which options where specified in the command line.
void parse(int argc, const char* argv[]);
// Reset all options values/flags.
// Reset all option 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.
// Returns the list of available options for the user.
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.
// List of specified options/values in the command line.
const ValueList& values() const { return m_values; }
private:
static void resetOption(Option* option);
bool enabled(const Option& option) const;
std::string value_of(const Option& option) const;
private:
OptionList m_options;
ValueList m_values;
};

View File

@ -21,13 +21,13 @@ TEST(ProgramOptions, OptionMembers)
EXPECT_EQ("help", help.name());
EXPECT_EQ("Show the help", help.description());
EXPECT_EQ('h', help.mnemonic());
EXPECT_FALSE(help.enabled());
EXPECT_FALSE(po.enabled(help));
EXPECT_FALSE(help.doesRequireValue());
EXPECT_EQ("output", output.name());
EXPECT_EQ("", output.description());
EXPECT_EQ('O', output.mnemonic());
EXPECT_FALSE(output.enabled());
EXPECT_FALSE(po.enabled(output));
EXPECT_TRUE(output.doesRequireValue());
}
@ -36,20 +36,20 @@ TEST(ProgramOptions, Reset)
ProgramOptions po;
ProgramOptions::Option& help = po.add("help");
ProgramOptions::Option& file = po.add("file").requiresValue("FILE");
EXPECT_FALSE(help.enabled());
EXPECT_FALSE(file.enabled());
EXPECT_EQ("", file.value());
EXPECT_FALSE(po.enabled(help));
EXPECT_FALSE(po.enabled(file));
EXPECT_EQ("", po.value_of(file));
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());
EXPECT_TRUE(po.enabled(help));
EXPECT_TRUE(po.enabled(file));
EXPECT_EQ("readme.txt", po.value_of(file));
po.reset();
EXPECT_FALSE(help.enabled());
EXPECT_FALSE(file.enabled());
EXPECT_EQ("", file.value());
EXPECT_FALSE(po.enabled(help));
EXPECT_FALSE(po.enabled(file));
EXPECT_EQ("", po.value_of(file));
}
TEST(ProgramOptions, Parse)
@ -61,50 +61,59 @@ TEST(ProgramOptions, Parse)
const char* argv1[] = { "program.exe", "-?" };
po.parse(2, argv1);
EXPECT_TRUE(help.enabled());
EXPECT_TRUE(po.enabled(help));
const char* argv2[] = { "program.exe", "--help" };
po.reset();
po.parse(2, argv2);
EXPECT_TRUE(help.enabled());
EXPECT_TRUE(po.enabled(help));
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());
EXPECT_FALSE(po.enabled(help));
EXPECT_TRUE(po.enabled(input));
EXPECT_TRUE(po.enabled(output));
EXPECT_EQ("hello.cpp", po.value_of(input));
EXPECT_EQ("hello.exe", po.value_of(output));
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());
EXPECT_FALSE(po.enabled(help));
EXPECT_TRUE(po.enabled(input));
EXPECT_TRUE(po.enabled(output));
EXPECT_EQ("hi.c", po.value_of(input));
EXPECT_EQ("out.exe", po.value_of(output));
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]);
EXPECT_TRUE(po.enabled(help));
EXPECT_TRUE(po.enabled(input));
EXPECT_TRUE(po.enabled(output));
EXPECT_EQ("input.md", po.value_of(input));
EXPECT_EQ("output.html", po.value_of(output));
ASSERT_EQ(4, po.values().size());
EXPECT_EQ(&help, po.values()[0].option());
EXPECT_EQ(&input, po.values()[1].option());
EXPECT_EQ(&output, po.values()[2].option());
EXPECT_EQ(NULL, po.values()[3].option());
EXPECT_EQ("", po.values()[0].value());
EXPECT_EQ("input.md", po.values()[1].value());
EXPECT_EQ("output.html", po.values()[2].value());
EXPECT_EQ("extra-file.txt", po.values()[3].value());
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]);
ASSERT_EQ(6, po.values().size());
EXPECT_EQ("value1", po.values()[0].value());
EXPECT_EQ("value2", po.values()[1].value());
EXPECT_EQ("output", po.values()[2].value());
EXPECT_EQ("value3", po.values()[3].value());
EXPECT_EQ("input", po.values()[4].value());
EXPECT_EQ("value4", po.values()[5].value());
}
TEST(ProgramOptions, ParseErrors)
@ -125,19 +134,19 @@ TEST(ProgramOptions, ParseErrors)
const char* argv4[] = { "program.exe", "-?a" };
po.reset();
EXPECT_FALSE(help.enabled());
EXPECT_FALSE(po.enabled(help));
EXPECT_THROW(po.parse(2, argv4), InvalidProgramOption);
EXPECT_TRUE(help.enabled()); // -? is parsed anyway, -a is the invalid option
EXPECT_TRUE(po.enabled(help)); // -? 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());
EXPECT_TRUE(po.enabled(input));
EXPECT_FALSE(po.enabled(output));
EXPECT_EQ("input-and-output.txt", po.value_of(input));
EXPECT_EQ("", po.value_of(output));
}
int main(int argc, char** argv)

View File

@ -18,23 +18,24 @@ typedef base::ProgramOptions PO;
static void run(int argc, const char* argv[])
{
PO po;
PO::Option& inputFn = po.add("input").requiresValue("<filename>");
PO::Option& inputOpt = po.add("input").requiresValue("<filename>");
PO::Option& widgetId = po.add("widgetid").requiresValue("<filename>");
po.parse(argc, argv);
// Try to load the XML file
TiXmlDocument* doc = NULL;
if (inputFn.enabled()) {
base::FileHandle inputFile(base::open_file(inputFn.value(), "rb"));
std::string inputFilename = po.value_of(inputOpt);
if (!inputFilename.empty()) {
base::FileHandle inputFile(base::open_file(inputFilename, "rb"));
doc = new TiXmlDocument();
doc->SetValue(inputFn.value().c_str());
doc->SetValue(inputFilename.c_str());
if (!doc->LoadFile(inputFile))
throw std::runtime_error("invalid input file");
}
if (doc && widgetId.enabled())
gen_ui_class(doc, inputFn.value(), widgetId.value());
if (doc && po.enabled(widgetId))
gen_ui_class(doc, inputFilename, po.value_of(widgetId));
}
int main(int argc, const char* argv[])

View File

@ -70,7 +70,7 @@ int app_main(int argc, char* argv[])
she::ScopedHandle<she::System> system(she::create_system());
MemLeak memleak;
ui::GuiSystem guiSystem;
app::App app(argc, const_cast<const char**>(argv));
app::App app;
// Change the name of the memory dump file
{
@ -79,7 +79,9 @@ int app_main(int argc, char* argv[])
memoryDump.setFileName(filename);
}
return app.run();
app.initialize(argc, const_cast<const char**>(argv));
app.run();
return 0;
}
catch (std::exception& e) {
std::cerr << e.what() << '\n';