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

View File

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

View File

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

View File

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

View File

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

View File

@ -39,16 +39,13 @@ namespace base {
public: public:
Option(const std::string& name) Option(const std::string& name)
: m_name(name) : m_name(name)
, m_mnemonic(0) , m_mnemonic(0) {
, m_enabled(false) {
} }
// Getters // Getters
const std::string& name() const { return m_name; } const std::string& name() const { return m_name; }
const std::string& description() const { return m_description; } const std::string& description() const { return m_description; }
const std::string& value() const { return m_value; }
const std::string& getValueName() const { return m_valueName; } const std::string& getValueName() const { return m_valueName; }
char mnemonic() const { return m_mnemonic; } char mnemonic() const { return m_mnemonic; }
bool enabled() const { return m_enabled; }
bool doesRequireValue() const { return !m_valueName.empty(); } bool doesRequireValue() const { return !m_valueName.empty(); }
// Setters // Setters
Option& description(const std::string& desc) { m_description = desc; return *this; } Option& description(const std::string& desc) { m_description = desc; return *this; }
@ -58,21 +55,29 @@ namespace base {
return *this; return *this;
} }
private: 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_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_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. 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. 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; 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<Option*> OptionList;
typedef std::vector<std::string> ValueList; typedef std::vector<Value> ValueList;
ProgramOptions(); ProgramOptions();
@ -88,23 +93,19 @@ namespace base {
// Detects which options where specified in the command line. // Detects which options where specified in the command line.
void parse(int argc, const char* argv[]); void parse(int argc, const char* argv[]);
// Reset all options values/flags. // Reset all option values/flags.
void reset(); void reset();
// Returns the list of available options. To know the list of // Returns the list of available options for the user.
// 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; } const OptionList& options() const { return m_options; }
// Returns the list of values that are not associated to any // List of specified options/values in the command line.
// options. E.g. a list of files specified in the command line to
// be opened.
const ValueList& values() const { return m_values; } const ValueList& values() const { return m_values; }
private: bool enabled(const Option& option) const;
static void resetOption(Option* option); std::string value_of(const Option& option) const;
private:
OptionList m_options; OptionList m_options;
ValueList m_values; ValueList m_values;
}; };

View File

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

View File

@ -18,23 +18,24 @@ typedef base::ProgramOptions PO;
static void run(int argc, const char* argv[]) static void run(int argc, const char* argv[])
{ {
PO po; 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::Option& widgetId = po.add("widgetid").requiresValue("<filename>");
po.parse(argc, argv); po.parse(argc, argv);
// Try to load the XML file // Try to load the XML file
TiXmlDocument* doc = NULL; TiXmlDocument* doc = NULL;
if (inputFn.enabled()) { std::string inputFilename = po.value_of(inputOpt);
base::FileHandle inputFile(base::open_file(inputFn.value(), "rb")); if (!inputFilename.empty()) {
base::FileHandle inputFile(base::open_file(inputFilename, "rb"));
doc = new TiXmlDocument(); doc = new TiXmlDocument();
doc->SetValue(inputFn.value().c_str()); doc->SetValue(inputFilename.c_str());
if (!doc->LoadFile(inputFile)) if (!doc->LoadFile(inputFile))
throw std::runtime_error("invalid input file"); throw std::runtime_error("invalid input file");
} }
if (doc && widgetId.enabled()) if (doc && po.enabled(widgetId))
gen_ui_class(doc, inputFn.value(), widgetId.value()); gen_ui_class(doc, inputFilename, po.value_of(widgetId));
} }
int main(int argc, const char* argv[]) 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()); she::ScopedHandle<she::System> system(she::create_system());
MemLeak memleak; MemLeak memleak;
ui::GuiSystem guiSystem; ui::GuiSystem guiSystem;
app::App app(argc, const_cast<const char**>(argv)); app::App app;
// Change the name of the memory dump file // Change the name of the memory dump file
{ {
@ -79,7 +79,9 @@ int app_main(int argc, char* argv[])
memoryDump.setFileName(filename); memoryDump.setFileName(filename);
} }
return app.run(); app.initialize(argc, const_cast<const char**>(argv));
app.run();
return 0;
} }
catch (std::exception& e) { catch (std::exception& e) {
std::cerr << e.what() << '\n'; std::cerr << e.what() << '\n';