From 0583e0b72b6bf7064c2e8af498b18f0bc168485f Mon Sep 17 00:00:00 2001 From: David Robillard Date: Wed, 21 Nov 2012 03:49:32 +0000 Subject: Move Configuration from Raul to Ingen. git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@4836 a436a847-0d15-0410-975c-d299462d15a1 --- ingen/Configuration.hpp | 159 +++++++++++++++++++++++++++++++++++++- src/Configuration.cpp | 179 +++++++++++++++++++++++++++++++++++++++---- src/ingen/main.cpp | 9 +-- src/serialisation/Parser.cpp | 7 +- src/server/ingen_jack.cpp | 6 +- tests/ingen_test.cpp | 10 +-- 6 files changed, 340 insertions(+), 30 deletions(-) diff --git a/ingen/Configuration.hpp b/ingen/Configuration.hpp index f45f3869..fac798d1 100644 --- a/ingen/Configuration.hpp +++ b/ingen/Configuration.hpp @@ -17,19 +17,172 @@ #ifndef INGEN_CONFIGURATION_HPP #define INGEN_CONFIGURATION_HPP -#include "raul/Configuration.hpp" +#include +#include +#include +#include + +#include +#include +#include + +#include "raul/Exception.hpp" namespace Ingen { -/** Ingen configuration (command line options). +/** Ingen configuration (command line options and/or configuration file). * @ingroup IngenShared */ -class Configuration : public Raul::Configuration { +class Configuration { public: Configuration(); + + enum OptionType { + NOTHING, + BOOL, + INT, + STRING + }; + + class Value { + public: + Value() : _type(NOTHING) { _val._string = NULL; } + Value(bool v) : _type(BOOL) { _val._bool = v; } + Value(int v) : _type(INT) { _val._int = v; } + + Value(const char* v) : _type(STRING) { + const size_t len = strlen(v); + _val._string = (char*)calloc(len + 1, 1); + memcpy(_val._string, v, len + 1); + } + + Value(const std::string& v) : _type(STRING) { + _val._string = (char*)calloc(v.length() + 1, 1); + memcpy(_val._string, v.c_str(), v.length() + 1); + } + + Value(const Value& copy) + : _type(copy._type) + , _val(copy._val) + { + if (_type == STRING) { + const size_t len = strlen(copy.get_string()); + _val._string = (char*)malloc(len + 1); + memcpy(_val._string, copy.get_string(), len + 1); + } + } + + Value& operator=(const Value& other) + { + if (&other == this) { + return *this; + } + if (_type == STRING) { + free(_val._string); + } + _type = other._type; + _val = other._val; + if (_type == STRING) { + const size_t len = strlen(other.get_string()); + _val._string = (char*)malloc(len + 1); + memcpy(_val._string, other.get_string(), len + 1); + } + return *this; + } + + ~Value() { + if (_type == STRING) { + free(_val._string); + } + } + + inline OptionType type() const { return _type; } + inline bool is_valid() const { return _type != NOTHING; } + + inline int32_t get_int() const { assert(_type == INT); return _val._int; } + inline bool get_bool() const { assert(_type == BOOL); return _val._bool; } + inline const char* get_string() const { assert(_type == STRING); return _val._string; } + + private: + OptionType _type; + union { + bool _bool; + int32_t _int; + char* _string; + } _val; + }; + + Configuration& add(const std::string& name, + char letter, + const std::string& desc, + OptionType type, + const Value& value); + + void print_usage(const std::string& program, std::ostream& os); + + struct CommandLineError : public Exception { + explicit CommandLineError(const std::string& m) : Exception(m) {} + }; + + void parse(int argc, char** argv) throw (CommandLineError); + + void print(std::ostream& os, const std::string mime_type="text/plain") const; + + const Value& option(const std::string& long_name) const; + bool set(const std::string& long_name, const Value& value); + + const std::list& files() const { return _files; } + +private: + struct Option { + public: + Option(const std::string& n, char l, const std::string& d, + const OptionType type, const Value& def) + : name(n), letter(l), desc(d), type(type), default_value(def), value(def) + {} + + std::string name; + char letter; + std::string desc; + OptionType type; + Value default_value; + Value value; + }; + + struct OptionNameOrder { + inline bool operator()(const Option& a, const Option& b) { + return a.name < b.name; + } + }; + + typedef std::map Options; + typedef std::map ShortNames; + typedef std::list Files; + + int set_value_from_string(Configuration::Option& option, const std::string& value) + throw (Configuration::CommandLineError); + + const std::string _shortdesc; + const std::string _desc; + Options _options; + ShortNames _short_names; + Files _files; + size_t _max_name_length; }; } // namespace Ingen +static inline std::ostream& +operator<<(std::ostream& os, const Ingen::Configuration::Value& value) +{ + switch (value.type()) { + case Ingen::Configuration::NOTHING: return os << "(nil)"; + case Ingen::Configuration::INT: return os << value.get_int(); + case Ingen::Configuration::BOOL: return os << (value.get_bool() ? "true" : "false"); + case Ingen::Configuration::STRING: return os << value.get_string(); + } + return os; +} + #endif // INGEN_CONFIGURATION_HPP diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 2a5e69f7..9c9402c0 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -14,24 +14,27 @@ along with Ingen. If not, see . */ +#include + #include "ingen/Configuration.hpp" namespace Ingen { Configuration::Configuration() - : Raul::Configuration( - "A realtime modular audio processor.", - "Ingen is a flexible modular system that be used in various ways.\n" - "The engine can run as a stand-alone server controlled via network protocol,\n" - "or internal to another process (e.g. the GUI). The GUI, or other\n" - "clients, can communicate with the engine via any supported protocol, or host the\n" - "engine in the same process. Many clients can connect to an engine at once.\n\n" - "Examples:\n" - " ingen -e # Run an engine, listen for connections\n" - " ingen -g # Run a GUI, connect to running engine\n" - " ingen -eg # Run an engine and a GUI in one process\n" - " ingen -egl foo.ttl # Run an engine and a GUI and load a graph\n" - " ingen -egl foo.ingen # Run an engine and a GUI and load a graph") + : _shortdesc("A realtime modular audio processor.") + , _desc( +"Ingen is a flexible modular system that be used in various ways.\n" +"The engine can run as a stand-alone server controlled via network protocol,\n" +"or internal to another process (e.g. the GUI). The GUI, or other\n" +"clients, can communicate with the engine via any supported protocol, or host the\n" +"engine in the same process. Many clients can connect to an engine at once.\n\n" +"Examples:\n" +" ingen -e # Run an engine, listen for connections\n" +" ingen -g # Run a GUI, connect to running engine\n" +" ingen -eg # Run an engine and a GUI in one process\n" +" ingen -egl foo.ttl # Run an engine and a GUI and load a graph\n" +" ingen -egl foo.ingen # Run an engine and a GUI and load a graph") + , _max_name_length(0) { add("client-port", 'C', "Client port", INT, Value()); add("connect", 'c', "Connect to engine URI", STRING, Value("unix:///tmp/ingen.sock")); @@ -50,4 +53,154 @@ Configuration::Configuration() add("run", 'r', "Run script", STRING, Value()); } +/** Add a configuration option. + * + * @param name Long name (without leading "--") + * @param letter Short name (without leading "-") + * @param desc Description + * @param type Type + * @param value Default value + */ +Configuration& +Configuration::add( + const std::string& name, + char letter, + const std::string& desc, + const OptionType type, + const Value& value) +{ + assert(value.type() == type || value.type() == 0); + _max_name_length = std::max(_max_name_length, name.length()); + _options.insert(make_pair(name, Option(name, letter, desc, type, value))); + if (letter != '\0') { + _short_names.insert(make_pair(letter, name)); + } + return *this; +} + +void +Configuration::print_usage(const std::string& program, std::ostream& os) +{ + os << "Usage: " << program << " [OPTION]..." << std::endl; + os << _shortdesc << std::endl << std::endl; + os << _desc << std::endl << std::endl; + os << "Options:" << std::endl; + for (Options::iterator o = _options.begin(); o != _options.end(); ++o) { + Option& option = o->second; + os << " "; + if (option.letter != '\0') + os << "-" << option.letter << ", "; + else + os << " "; + os.width(_max_name_length + 4); + os << std::left << (std::string("--") + o->first); + os << option.desc << std::endl; + } +} + +int +Configuration::set_value_from_string(Configuration::Option& option, + const std::string& value) + throw (Configuration::CommandLineError) +{ + int intval = 0; + char* endptr = NULL; + switch (option.type) { + case INT: + intval = static_cast(strtol(value.c_str(), &endptr, 10)); + if (endptr && *endptr == '\0') { + option.value = Value(intval); + } else { + throw CommandLineError("option `" + option.name + + "' has non-integer value `" + value + "'"); + } + break; + case STRING: + option.value = Value(value.c_str()); + assert(option.value.type() == STRING); + break; + default: + throw CommandLineError(std::string("bad option type `--") + option.name + "'"); + } + return EXIT_SUCCESS; +} + +/** Parse command line arguments. */ +void +Configuration::parse(int argc, char** argv) throw (Configuration::CommandLineError) +{ + for (int i = 1; i < argc; ++i) { + if (argv[i][0] != '-' || !strcmp(argv[i], "-")) { + _files.push_back(argv[i]); + } else if (argv[i][1] == '-') { + const std::string name = std::string(argv[i]).substr(2); + Options::iterator o = _options.find(name); + if (o == _options.end()) { + throw CommandLineError(std::string("unrecognized option `--") + name + "'"); + } + if (o->second.type == BOOL) { + o->second.value = Value(true); + } else { + if (++i >= argc) + throw CommandLineError("missing value for `--" + name + "'"); + set_value_from_string(o->second, argv[i]); + } + } else { + const size_t len = strlen(argv[i]); + for (size_t j = 1; j < len; ++j) { + char letter = argv[i][j]; + ShortNames::iterator n = _short_names.find(letter); + if (n == _short_names.end()) + throw CommandLineError(std::string("unrecognized option `-") + letter + "'"); + Options::iterator o = _options.find(n->second); + if (j < len - 1) { + if (o->second.type != BOOL) + throw CommandLineError(std::string("missing value for `-") + letter + "'"); + o->second.value = Value(true); + } else { + if (o->second.type == BOOL) { + o->second.value = Value(true); + } else { + if (++i >= argc) + throw CommandLineError(std::string("missing value for `-") + letter + "'"); + set_value_from_string(o->second, argv[i]); + } + } + } + } + } +} + +void +Configuration::print(std::ostream& os, const std::string mime_type) const +{ + for (Options::const_iterator o = _options.begin(); o != _options.end(); ++o) { + const Option& option = o->second; + os << o->first << " = " << option.value << std::endl; + } +} + +const Configuration::Value& +Configuration::option(const std::string& long_name) const +{ + static const Value nil; + Options::const_iterator o = _options.find(long_name); + if (o == _options.end()) { + return nil; + } else { + return o->second.value; + } +} + +bool +Configuration::set(const std::string& long_name, const Value& value) +{ + Options::iterator o = _options.find(long_name); + if (o != _options.end()) { + o->second.value = value; + return true; + } + return false; +} + } // namespace Ingen diff --git a/src/ingen/main.cpp b/src/ingen/main.cpp index 670cd514..99fa079a 100644 --- a/src/ingen/main.cpp +++ b/src/ingen/main.cpp @@ -23,20 +23,19 @@ #include #include -#include "raul/Configuration.hpp" #include "raul/Path.hpp" #include "raul/SharedPtr.hpp" #include "raul/fmt.hpp" #include "ingen_config.h" +#include "ingen/Configuration.hpp" #include "ingen/EngineBase.hpp" #include "ingen/Interface.hpp" -#include "ingen/serialisation/Parser.hpp" -#include "ingen/Configuration.hpp" #include "ingen/World.hpp" -#include "ingen/runtime_paths.hpp" #include "ingen/client/ThreadedSigClientInterface.hpp" +#include "ingen/runtime_paths.hpp" +#include "ingen/serialisation/Parser.hpp" #ifdef WITH_BINDINGS #include "bindings/ingen_bindings.hpp" #endif @@ -156,7 +155,7 @@ main(int argc, char** argv) boost::optional parent; boost::optional symbol; - const Raul::Configuration::Value& path_option = conf.option("path"); + const Configuration::Value& path_option = conf.option("path"); if (path_option.is_valid()) { if (Raul::Path::is_valid(path_option.get_string())) { const Raul::Path p(path_option.get_string()); diff --git a/src/serialisation/Parser.cpp b/src/serialisation/Parser.cpp index a6036c32..0c15b0b4 100644 --- a/src/serialisation/Parser.cpp +++ b/src/serialisation/Parser.cpp @@ -229,7 +229,12 @@ parse_block(Ingen::World* world, Sord::Iter i = model.find(subject, ingen_prototype, nil); if (i.end() || i.get_object().type() != Sord::Node::URI) { - world->log().error("Block missing mandatory ingen:prototype\n"); + if (!i.end()) { + std::cerr << "type: " << i.get_object().type() << std::endl; + } + world->log().error( + Raul::fmt("Block %1% (%2%) missing mandatory ingen:prototype\n") % + subject.to_string() % path); return boost::optional(); } diff --git a/src/server/ingen_jack.cpp b/src/server/ingen_jack.cpp index ec690756..dd713385 100644 --- a/src/server/ingen_jack.cpp +++ b/src/server/ingen_jack.cpp @@ -17,10 +17,10 @@ #include #include "ingen/Configuration.hpp" +#include "ingen/Configuration.hpp" +#include "ingen/Log.hpp" #include "ingen/Module.hpp" #include "ingen/World.hpp" -#include "ingen/Log.hpp" -#include "raul/Configuration.hpp" #include "JackDriver.hpp" #include "Engine.hpp" @@ -37,7 +37,7 @@ struct IngenJackModule : public Ingen::Module { Server::JackDriver* driver = new Server::JackDriver( *(Server::Engine*)world->engine().get()); - const Raul::Configuration::Value& s = world->conf().option("jack-server"); + const Configuration::Value& s = world->conf().option("jack-server"); const std::string server_name = s.is_valid() ? s.get_string() : ""; driver->attach(server_name, world->conf().option("jack-client").get_string(), diff --git a/tests/ingen_test.cpp b/tests/ingen_test.cpp index 62ee502a..4958142b 100644 --- a/tests/ingen_test.cpp +++ b/tests/ingen_test.cpp @@ -27,7 +27,6 @@ #include #include -#include "raul/Configuration.hpp" #include "raul/Path.hpp" #include "raul/SharedPtr.hpp" #include "raul/Thread.hpp" @@ -38,16 +37,17 @@ #include "ingen_config.h" -#include "ingen/EngineBase.hpp" -#include "ingen/Interface.hpp" -#include "ingen/serialisation/Parser.hpp" #include "ingen/AtomReader.hpp" #include "ingen/AtomWriter.hpp" #include "ingen/Configuration.hpp" +#include "ingen/Configuration.hpp" +#include "ingen/EngineBase.hpp" +#include "ingen/Interface.hpp" #include "ingen/URIMap.hpp" #include "ingen/World.hpp" -#include "ingen/runtime_paths.hpp" #include "ingen/client/ThreadedSigClientInterface.hpp" +#include "ingen/runtime_paths.hpp" +#include "ingen/serialisation/Parser.hpp" #ifdef WITH_BINDINGS #include "bindings/ingen_bindings.hpp" #endif -- cgit v1.2.1