diff options
author | Kjell Ahlstedt <kjell.ahlstedt@bredband.net> | 2011-02-14 16:24:27 +0100 |
---|---|---|
committer | Murray Cumming <murrayc@murrayc.com> | 2011-02-15 12:41:59 +0100 |
commit | 94a1f3d568decbde9c3fab84c34db5741ffcad62 (patch) | |
tree | 7b25ba17b002e06ea7d8642a4ebe61cfe3038be9 | |
parent | efa5be64159016d2b9cc4ac8d1cc5a6176703ce7 (diff) | |
download | glibmm-94a1f3d568decbde9c3fab84c34db5741ffcad62.tar.gz |
OptionGroup: Add add_entry() that takes a slot with callback function.
* glib/src/optiongroup.[hg|ccg]: Add add_entry() and add_entry_filename()
that take a slot. Add protected option_arg_callback().
An exception thrown by on_pre_parse() or on_post_parse() is propagated
to the error argument of g_callback_pre_parse() or post_parse_callback().
* glib/src/optionentry.hg: Add description of set_flags().
* examples/options/main.cc: Add more OptionEntries and callback functions
for parsing command option values.
Bug 589197 (Hubert Figuiere)
-rw-r--r-- | ChangeLog | 13 | ||||
-rw-r--r-- | examples/options/main.cc | 94 | ||||
-rw-r--r-- | glib/src/optionentry.hg | 8 | ||||
-rw-r--r-- | glib/src/optiongroup.ccg | 273 | ||||
-rw-r--r-- | glib/src/optiongroup.hg | 15 |
5 files changed, 365 insertions, 38 deletions
@@ -1,3 +1,16 @@ +2011-02-14 Kjell Ahlstedt <kjell.ahlstedt@bredband.net> + + OptionGroup: Add add_entry() that takes a slot with callback function. + + * glib/src/optiongroup.[hg|ccg]: Add add_entry() and add_entry_filename() + that take a slot. Add protected option_arg_callback(). + An exception thrown by on_pre_parse() or on_post_parse() is propagated + to the error argument of g_callback_pre_parse() or post_parse_callback(). + * glib/src/optionentry.hg: Add description of set_flags(). + * examples/options/main.cc: Add more OptionEntries and callback functions + for parsing command option values. + Bug #589197 (Hubert Figuiere) + 2011-02-15 Murray Cumming <murrayc@murrayc.com> Dealt with several TODOs. diff --git a/examples/options/main.cc b/examples/options/main.cc index 0ba5372b..ee524dac 100644 --- a/examples/options/main.cc +++ b/examples/options/main.cc @@ -28,7 +28,12 @@ public: virtual bool on_pre_parse(Glib::OptionContext& context, Glib::OptionGroup& group); virtual bool on_post_parse(Glib::OptionContext& context, Glib::OptionGroup& group); virtual void on_error(Glib::OptionContext& context, Glib::OptionGroup& group); - + + bool on_option_arg_string(const Glib::ustring& option_name, + const Glib::ustring& value, bool has_value); + bool on_option_arg_filename(const Glib::ustring& option_name, + const std::string& value, bool has_value); + //These members should live as long as the OptionGroup to which they are added, //and as long as the OptionContext to which that OptionGroup is added. int m_arg_foo; @@ -37,6 +42,8 @@ public: bool m_arg_boolean; Glib::OptionGroup::vecustrings m_arg_list; Glib::OptionGroup::vecustrings m_remaining_list; + Glib::ustring m_arg_x_string; + std::string m_arg_x_filename; }; ExampleOptionGroup::ExampleOptionGroup() @@ -73,6 +80,22 @@ ExampleOptionGroup::ExampleOptionGroup() entry5.set_description("A List"); add_entry(entry5, m_arg_list); + Glib::OptionEntry entry6; + entry6.set_long_name("x-string"); + entry6.set_short_name('x'); + entry6.set_description("A string with custom parsing"); + entry6.set_flags(Glib::OptionEntry::FLAG_OPTIONAL_ARG); + m_arg_x_string = "not specified"; + add_entry(entry6, sigc::mem_fun(*this, &ExampleOptionGroup::on_option_arg_string)); + + Glib::OptionEntry entry7; + entry7.set_long_name("x-filename"); + entry7.set_short_name('X'); + entry7.set_description("A filename with custom parsing"); + entry7.set_flags(Glib::OptionEntry::FLAG_OPTIONAL_ARG); + m_arg_x_filename = "not specified"; + add_entry_filename(entry7, sigc::mem_fun(*this, &ExampleOptionGroup::on_option_arg_filename)); + Glib::OptionEntry entry_remaining; entry_remaining.set_long_name(G_OPTION_REMAINING); entry_remaining.set_arg_description(G_OPTION_REMAINING); @@ -103,6 +126,66 @@ void ExampleOptionGroup::on_error(Glib::OptionContext& /* context */, Glib::Opti std::cout << "on_error called" << std::endl; } +bool ExampleOptionGroup::on_option_arg_string(const Glib::ustring& option_name, + const Glib::ustring& value, bool has_value) +{ + if(option_name != "-x" && option_name != "--x-string") + { + m_arg_x_string = "on_option_arg_string called with unexpected option_name: " + option_name; + throw Glib::OptionError(Glib::OptionError::UNKNOWN_OPTION, m_arg_x_string); + } + + if(!has_value) + { + m_arg_x_string = "no value"; + return true; + } + + if(value.empty()) + { + m_arg_x_string = "empty string"; + return true; + } + + m_arg_x_string = value; + if(value == "error") + { + throw Glib::OptionError(Glib::OptionError::BAD_VALUE, + "on_option_arg_string called with value = " + m_arg_x_string); + } + return value != "false"; +} + +bool ExampleOptionGroup::on_option_arg_filename(const Glib::ustring& option_name, + const std::string& value, bool has_value) +{ + if(option_name != "-X" && option_name != "--x-filename") + { + m_arg_x_filename = "on_option_arg_filename called with unexpected option_name: " + option_name; + throw Glib::OptionError(Glib::OptionError::UNKNOWN_OPTION, m_arg_x_filename); + } + + if(!has_value) + { + m_arg_x_filename = "no value"; + return true; + } + + if(value.empty()) + { + m_arg_x_filename = "empty string"; + return true; + } + + m_arg_x_filename = value; + if(value == "error") + { + throw Glib::OptionError(Glib::OptionError::BAD_VALUE, + "on_option_arg_filename called with value = " + m_arg_x_filename); + } + return value != "false"; +} + int main(int argc, char** argv) { @@ -111,7 +194,10 @@ int main(int argc, char** argv) //./example --help Glib::init(); - + + //Set up the current locale. + setlocale(LC_ALL, ""); + Glib::OptionContext context; ExampleOptionGroup group; @@ -130,7 +216,9 @@ int main(int argc, char** argv) " foo = " << group.m_arg_foo << std::endl << " filename = " << group.m_arg_filename << std::endl << " activate_something = " << (group.m_arg_boolean ? "enabled" : "disabled") << std::endl << - " goo = " << group.m_arg_goo << std::endl; + " goo = " << group.m_arg_goo << std::endl << + " x-string = " << group.m_arg_x_string << std::endl << + " x-filename = " << group.m_arg_x_filename << std::endl; //This one shows the results of multiple instance of the same option, such as --list=1 --list=a --list=b std::cout << " list = "; diff --git a/glib/src/optionentry.hg b/glib/src/optionentry.hg index 74d74ded..5bab0e5c 100644 --- a/glib/src/optionentry.hg +++ b/glib/src/optionentry.hg @@ -75,20 +75,20 @@ public: _MEMBER_SET(short_name, short_name, gchar, gchar) _MEMBER_GET(flags, flags, int, int) + /** Set one or more OptionEntry::Flags. + * Do not set FLAG_FILENAME. Character encoding is chosen when the OptionEntry + * is added to an OptionGroup. + */ _MEMBER_SET(flags, flags, int, int) - //TODO: G_OPTION_ARG_CALLBACK, - _MEMBER_GET(description, description, Glib::ustring, const char*) void set_description(const Glib::ustring& value); - _MEMBER_GET(arg_description, arg_description, Glib::ustring, const char*) void set_arg_description(const Glib::ustring& value); - GOptionEntry* gobj() { return gobject_; } const GOptionEntry* gobj() const { return gobject_; } diff --git a/glib/src/optiongroup.ccg b/glib/src/optiongroup.ccg index ed5e45a6..358d8661 100644 --- a/glib/src/optiongroup.ccg +++ b/glib/src/optiongroup.ccg @@ -31,25 +31,79 @@ namespace Glib namespace //anonymous { +//A pointer to an OptionArgCallback instance is stored in CppOptionEntry::cpparg_ +//when a callback function shall parse the command option's value. +class OptionArgCallback +{ +public: + OptionArgCallback(const OptionGroup::SlotOptionArgString& slot) + : slot_string(new OptionGroup::SlotOptionArgString(slot)), slot_filename(0) + { } + + OptionArgCallback(const OptionGroup::SlotOptionArgFilename& slot) + : slot_string(0), slot_filename(new OptionGroup::SlotOptionArgFilename(slot)) + { } + + bool is_filename_option() const { return slot_filename != 0; } + const OptionGroup::SlotOptionArgString* get_slot_string() const { return slot_string; } + const OptionGroup::SlotOptionArgFilename* get_slot_filename() const { return slot_filename; } + + ~OptionArgCallback() + { + delete slot_string; + delete slot_filename; + } + +private: + //One of these slot pointers is 0 and the other one points to a slot. + OptionGroup::SlotOptionArgString* slot_string; + OptionGroup::SlotOptionArgFilename* slot_filename; + + //Not copyable + OptionArgCallback(const OptionArgCallback&); + OptionArgCallback& operator=(const OptionArgCallback&); +}; + extern "C" { -static gboolean g_callback_pre_parse(GOptionContext* context, GOptionGroup* /* group */, gpointer data, GError** /* TODO error */) +static gboolean g_callback_pre_parse(GOptionContext* context, + GOptionGroup* /* group */, gpointer data, GError** error) { OptionContext cppContext(context, false /* take_ownership */); - //OptionGroup cppGroup(group, true /* take_copy */); //Maybe this should be option_group. OptionGroup* option_group = static_cast<OptionGroup*>(data); - if(option_group) - return option_group->on_pre_parse(cppContext, *option_group); - else + if(!option_group) + { + OptionError(OptionError::FAILED, "Glib::OptionGroup: g_callback_pre_parse(): " + "No OptionGroup pointer available").propagate(error); return false; + } + + try + { + return option_group->on_pre_parse(cppContext, *option_group); + } + catch(Glib::Error& err) + { + err.propagate(error); + } + catch(...) + { + Glib::exception_handlers_invoke(); + } + return false; } -static void g_callback_error(GOptionContext* context, GOptionGroup* /* group */, gpointer data, GError** /* TODO error*/) +static void g_callback_error(GOptionContext* context, + GOptionGroup* /* group */, gpointer data, GError** /* TODO error */) { + // TODO GError** error is input data containing information on an error that + // has occurred before this function is called. When API can be broken, + // the function prototype of on_error ought to be changed to + // void on_error(OptionContext& context, Error& error). + OptionContext cppContext(context, false /* take_ownership */); - //OptionGroup cppGroup(group); //Maybe this should be option_group. OptionGroup* option_group = static_cast<OptionGroup*>(data); if(option_group) @@ -86,33 +140,133 @@ static void OptionGroup_Translate_glibmm_callback_destroy(void* data) //static gboolean OptionGroup::post_parse_callback(GOptionContext* context, - GOptionGroup* /* group */, gpointer data, GError** /* TODO error */) + GOptionGroup* /* group */, gpointer data, GError** error) { OptionContext cppContext(context, false /* take_ownership */); - //OptionGroup cppGroup(group, true /* take_copy */); //Maybe this should be option_group. OptionGroup* option_group = static_cast<OptionGroup*>(data); - if(option_group) + if(!option_group) { - //The C args have now been given values by g_option_context_parse(). - //Convert C values to C++ values: + OptionError(OptionError::FAILED, "Glib::OptionGroup::post_parse_callback(): " + "No OptionGroup pointer available").propagate(error); + return false; + } - for(type_map_entries::iterator iter = option_group->map_entries_.begin(); - iter != option_group->map_entries_.end(); ++iter) - { - CppOptionEntry& cpp_entry = iter->second; - cpp_entry.convert_c_to_cpp(); - } + //The C args have now been given values by g_option_context_parse(). + //Convert C values to C++ values: + for(type_map_entries::iterator iter = option_group->map_entries_.begin(); + iter != option_group->map_entries_.end(); ++iter) + { + CppOptionEntry& cpp_entry = iter->second; + cpp_entry.convert_c_to_cpp(); + } + + try + { return option_group->on_post_parse(cppContext, *option_group); } + catch(Glib::Error& err) + { + err.propagate(error); + } + catch(...) + { + Glib::exception_handlers_invoke(); + } + return false; +} + +//static +gboolean OptionGroup::option_arg_callback(const gchar* option_name, const gchar* value, + gpointer data, GError** error) +{ + const Glib::ustring cpp_option_name(option_name); + const OptionGroup* const option_group = static_cast<const OptionGroup*>(data); + if(!option_group) + { + OptionError(OptionError::FAILED, "Glib::OptionGroup::option_arg_callback(): " + "No OptionGroup pointer available for option " + cpp_option_name).propagate(error); + return false; + } + + //option_name is either a single dash followed by a single letter (for a + //short name) or two dashes followed by a long option name. + OptionGroup::type_map_entries::const_iterator iterFind = option_group->map_entries_.end(); + if(option_name[1] == '-') + { + //Long option name. + const Glib::ustring long_option_name = Glib::ustring(option_name+2); + iterFind = option_group->map_entries_.find(long_option_name); + } else + { + //Short option name. + const gchar short_option_name = option_name[1]; + for(iterFind = option_group->map_entries_.begin(); + iterFind != option_group->map_entries_.end(); ++iterFind) + { + const OptionGroup::CppOptionEntry& cppOptionEntry = iterFind->second; + if (cppOptionEntry.entry_ && + cppOptionEntry.entry_->get_short_name() == short_option_name) + break; + } + } + + if(iterFind == option_group->map_entries_.end()) + { + OptionError(OptionError::UNKNOWN_OPTION, "Glib::OptionGroup::option_arg_callback(): " + "Unknown option " + cpp_option_name).propagate(error); + return false; + } + + const OptionGroup::CppOptionEntry& cppOptionEntry = iterFind->second; + if (cppOptionEntry.carg_type_ != G_OPTION_ARG_CALLBACK) + { + OptionError(OptionError::FAILED, "Glib::OptionGroup::option_arg_callback() " + "called for non-callback option " + cpp_option_name).propagate(error); return false; + } + + const bool has_value = (value != 0); + const OptionArgCallback* const option_arg = + static_cast<const OptionArgCallback*>(cppOptionEntry.cpparg_); + try + { + if (option_arg->is_filename_option()) + { + const OptionGroup::SlotOptionArgFilename* the_slot = option_arg->get_slot_filename(); + const std::string cpp_value(value ? value : ""); + return (*the_slot)(cpp_option_name, cpp_value, has_value); + } + else + { + const OptionGroup::SlotOptionArgString* the_slot = option_arg->get_slot_string(); + const Glib::ustring cpp_value(value ? value : ""); + return (*the_slot)(cpp_option_name, cpp_value, has_value); + } + } + catch(Glib::Error& err) + { + err.propagate(error); + } + catch(...) + { + Glib::exception_handlers_invoke(); + } + return false; } OptionGroup::OptionGroup(const Glib::ustring& name, const Glib::ustring& description, const Glib::ustring& help_description) -: gobject_( g_option_group_new(name.c_str(), description.c_str(), help_description.c_str(), this, 0 /* destroy_func */) ), +: gobject_( g_option_group_new(name.c_str(), description.c_str(), help_description.c_str(), + this /* user_data */, 0 /* destroy_func */) ), has_ownership_(true) { + //g_callback_pre_parse(), post_parse_callback(), g_callback_error(), and + //option_arg_callback() depend on user_data being this. The first three + //functions get a GOptionGroup*, but it would not be correct to use it for + //creating a new OptionGroup. They must call their virtual functions in the + //original OptionGroup instance. + //Connect callbacks, so that derived classes can override the virtual methods: g_option_group_set_parse_hooks(gobj(), &g_callback_pre_parse, &post_parse_callback); g_option_group_set_error_hook(gobj(), &g_callback_error); @@ -191,7 +345,30 @@ void OptionGroup::add_entry_filename(const OptionEntry& entry, vecstrings& arg) { add_entry_with_wrapper(entry, G_OPTION_ARG_FILENAME_ARRAY, &arg); } - + +// When the command argument value is to be parsed by a user-supplied function +// (indicated by G_OPTION_ARG_CALLBACK), the FLAG_FILENAME in 'entry' is ignored. +// set_c_arg_default() clears or sets it as required in a copy of 'entry'. +// +// The glib API is inconsistent here. The choice between UTF-8 and filename +// encoding is done with G_OPTION_ARG_STRING, G_OPTION_ARG_FILENAME, +// G_OPTION_ARG_STRING_ARRAY, and G_OPTION_ARG_FILENAME_ARRAY, which in glibmm +// are set by OptionGroup::add_entry[_filename]. But when a callback function +// is chosen, there is only G_OPTION_ARG_CALLBACK, and the encoding is chosen +// with G_OPTION_FLAG_FILENAME. Other option flags are set by OptionEntry::set_flags(). + +void OptionGroup::add_entry(const OptionEntry& entry, const SlotOptionArgString& slot) +{ + //The OptionArgCallback is deleted in release_c_arg(). + add_entry_with_wrapper(entry, G_OPTION_ARG_CALLBACK, new OptionArgCallback(slot)); +} + +void OptionGroup::add_entry_filename(const OptionEntry& entry, const SlotOptionArgFilename& slot) +{ + //The OptionArgCallback is deleted in release_c_arg(). + add_entry_with_wrapper(entry, G_OPTION_ARG_CALLBACK, new OptionArgCallback(slot)); +} + void OptionGroup::add_entry_with_wrapper(const OptionEntry& entry, GOptionArg arg_type, void* cpp_arg) { const Glib::ustring name = entry.get_long_name(); @@ -199,6 +376,10 @@ void OptionGroup::add_entry_with_wrapper(const OptionEntry& entry, GOptionArg ar if( iterFind == map_entries_.end() ) //If we have not added this entry already { CppOptionEntry cppEntry; + //g_option_group_add_entry() does not take its own copy, so we must keep the instance alive. + cppEntry.entry_ = new OptionEntry(entry); + //cppEntry.entry_ is deleted in release_c_arg(), via the destructor. + cppEntry.carg_type_ = arg_type; cppEntry.allocate_c_arg(); cppEntry.set_c_arg_default(cpp_arg); @@ -206,10 +387,6 @@ void OptionGroup::add_entry_with_wrapper(const OptionEntry& entry, GOptionArg ar cppEntry.cpparg_ = cpp_arg; //Give the information to the C API: - - cppEntry.entry_ = new OptionEntry(entry); //g_option_group_add_entry() does not take its own copy, so we must keep the instance alive. */ - //cppEntry.entry_ is deleted in release_c_arg(), via the destructor. - cppEntry.entry_->gobj()->arg = arg_type; cppEntry.entry_->gobj()->arg_data = cppEntry.carg_; @@ -256,7 +433,8 @@ void OptionGroup::CppOptionEntry::allocate_c_arg() //Create an instance of the appropriate C type. //This will be destroyed in the OptionGroup destructor. // - //We must also call set_c_arg_default() to give these C types the specified defaults based on the C++-typed arguments. + //We must also call set_c_arg_default() to give these C types the specified + //defaults based on the C++-typed arguments. switch(carg_type_) { case G_OPTION_ARG_STRING: //The char* will be for UTF8 strins. @@ -303,6 +481,13 @@ void OptionGroup::CppOptionEntry::allocate_c_arg() break; } + case G_OPTION_ARG_CALLBACK: + { + //The C arg pointer is a function pointer. + carg_ = reinterpret_cast<void*>(&OptionGroup::option_arg_callback); + + break; + } default: { break; @@ -389,6 +574,22 @@ void OptionGroup::CppOptionEntry::set_c_arg_default(void* cpp_arg) } break; } + case G_OPTION_ARG_CALLBACK: + { + //No value to set here. The arg pointer is a function pointer. + + //Set or clear FLAG_FILENAME in *entry_. + const OptionArgCallback* const option_arg = static_cast<const OptionArgCallback*>(cpp_arg); + if (option_arg->is_filename_option()) + { + entry_->set_flags(entry_->get_flags() | OptionEntry::FLAG_FILENAME); + } + else + { + entry_->set_flags(entry_->get_flags() & ~OptionEntry::FLAG_FILENAME); + } + break; + } default: { break; @@ -440,11 +641,18 @@ void OptionGroup::CppOptionEntry::release_c_arg() break; } + case G_OPTION_ARG_CALLBACK: + { + //Delete the OptionArgCallback instance that was allocated by add_entry() + //or add_entry_filename(). + OptionArgCallback* option_arg = static_cast<OptionArgCallback*>(cpparg_); + delete option_arg; + cpparg_ = 0; + + break; + } default: { - /* TODO: - G_OPTION_ARG_CALLBACK, - */ break; } } @@ -560,11 +768,14 @@ void OptionGroup::CppOptionEntry::convert_c_to_cpp() *(static_cast<bool*>(cpparg_)) = *(static_cast<gboolean*>(carg_)); break; } + case G_OPTION_ARG_CALLBACK: + { + //Nothing to convert here. That's a task for the callback function + //(the SlotOptionArgString or SlotOptionArgFilename). + break; + } default: { - /* TODO: - G_OPTION_ARG_CALLBACK, - */ break; } } diff --git a/glib/src/optiongroup.hg b/glib/src/optiongroup.hg index 8d687b25..f31015dc 100644 --- a/glib/src/optiongroup.hg +++ b/glib/src/optiongroup.hg @@ -51,6 +51,16 @@ public: */ typedef sigc::slot<Glib::ustring, const Glib::ustring&> SlotTranslate; + /** For example bool on_option_arg_string(const Glib::ustring& option_name, + * const Glib::ustring& value, bool has_value);. + */ + typedef sigc::slot<bool, const Glib::ustring&, const Glib::ustring&, bool> SlotOptionArgString; + + /** For example bool on_option_arg_filename(const Glib::ustring& option_name, + * const std::string& value, bool has_value);. + */ + typedef sigc::slot<bool, const Glib::ustring&, const std::string&, bool> SlotOptionArgFilename; + OptionGroup(const Glib::ustring& name, const Glib::ustring& description, const Glib::ustring& help_description = Glib::ustring()); /** This always takes ownership of the underlying GOptionGroup, @@ -81,6 +91,8 @@ public: void add_entry_filename(const OptionEntry& entry, std::string& arg); void add_entry(const OptionEntry& entry, vecustrings& arg); void add_entry_filename(const OptionEntry& entry, vecstrings& arg); + void add_entry(const OptionEntry& entry, const SlotOptionArgString& slot); + void add_entry_filename(const OptionEntry& entry, const SlotOptionArgFilename& slot); /** Sets the function which is used to translate user-visible strings, for * --help output. Different groups can use a different SlotTranslate. If a @@ -128,6 +140,9 @@ protected: static gboolean post_parse_callback(GOptionContext* context, GOptionGroup* group, gpointer data, GError** error); + static gboolean option_arg_callback(const gchar* option_name, const gchar* value, + gpointer data, GError** error); + //Map of entry names to CppOptionEntry: typedef std::map<Glib::ustring, CppOptionEntry> type_map_entries; type_map_entries map_entries_; |