From db238cc23f3bb5c9768d30ae24b99e67a5795203 Mon Sep 17 00:00:00 2001 From: Denis Krjuchkov Date: Sun, 24 Nov 2013 17:19:51 +0600 Subject: CommandLine: new command line parser This implementation behaves mostly identical to old parser. Few observable differences: - There are no option groups (single group is used for all options) - Option --stdout is hidden (it has been obsolete for a long time) - MPD executable name (mpd) is hardcoded for simplicity --- src/CommandLine.cxx | 220 +++++++++++++++++++++++++++++----------------- src/CommandLine.hxx | 10 +-- src/util/OptionDef.hxx | 63 +++++++++++++ src/util/OptionParser.cxx | 59 +++++++++++++ src/util/OptionParser.hxx | 88 +++++++++++++++++++ 5 files changed, 353 insertions(+), 87 deletions(-) create mode 100644 src/util/OptionDef.hxx create mode 100644 src/util/OptionParser.cxx create mode 100644 src/util/OptionParser.hxx (limited to 'src') diff --git a/src/CommandLine.cxx b/src/CommandLine.cxx index 05f0a358c..6c0f7e6a1 100644 --- a/src/CommandLine.cxx +++ b/src/CommandLine.cxx @@ -36,6 +36,8 @@ #include "fs/FileSystem.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" +#include "util/OptionDef.hxx" +#include "util/OptionParser.hxx" #include "system/FatalError.hxx" #ifdef ENABLE_ENCODER @@ -61,6 +63,25 @@ #define USER_CONFIG_FILE_LOCATION_XDG "mpd/mpd.conf" #endif +static const OptionDef opt_kill( + "kill", "kill the currently running mpd session"); +static const OptionDef opt_no_config( + "no-config", "don't read from config"); +static const OptionDef opt_no_daemon( + "no-daemon", "don't detach from console"); +static const OptionDef opt_stdout( + "stdout", nullptr); // hidden, compatibility with old versions +static const OptionDef opt_stderr( + "stderr", "print messages to stderr"); +static const OptionDef opt_verbose( + "verbose", 'v', "verbose logging"); +static const OptionDef opt_version( + "version", 'V', "print version number"); +static const OptionDef opt_help( + "help", 'h', "show help options"); +static const OptionDef opt_help_alt( + nullptr, '?', nullptr); // hidden, standard alias for --help + static constexpr Domain cmdline_domain("cmdline"); gcc_noreturn @@ -132,8 +153,39 @@ static void version(void) exit(EXIT_SUCCESS); } -static const char *summary = - "Music Player Daemon - a daemon for playing music."; +static void PrintOption(const OptionDef &opt) +{ + if (opt.HasShortOption()) + printf(" -%c, --%-12s%s\n", + opt.GetShortOption(), + opt.GetLongOption(), + opt.GetDescription()); + else + printf(" --%-16s%s\n", + opt.GetLongOption(), + opt.GetDescription()); +} + +gcc_noreturn +static void help(void) +{ + puts("Usage:\n" + " mpd [OPTION...] [path/to/mpd.conf]\n" + "\n" + "Music Player Daemon - a daemon for playing music.\n" + "\n" + "Options:"); + + PrintOption(opt_help); + PrintOption(opt_kill); + PrintOption(opt_no_config); + PrintOption(opt_no_daemon); + PrintOption(opt_stderr); + PrintOption(opt_verbose); + PrintOption(opt_version); + + exit(EXIT_SUCCESS); +} gcc_pure static AllocatedPath @@ -149,105 +201,111 @@ bool parse_cmdline(int argc, char **argv, struct options *options, Error &error) { - GOptionContext *context; - bool ret; - static gboolean option_version, - option_no_daemon, - option_no_config; - const GOptionEntry entries[] = { - { "kill", 0, 0, G_OPTION_ARG_NONE, &options->kill, - "kill the currently running mpd session", nullptr }, - { "no-config", 0, 0, G_OPTION_ARG_NONE, &option_no_config, - "don't read from config", nullptr }, - { "no-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon, - "don't detach from console", nullptr }, - { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr, - nullptr, nullptr }, - { "stderr", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr, - "print messages to stderr", nullptr }, - { "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose, - "verbose logging", nullptr }, - { "version", 'V', 0, G_OPTION_ARG_NONE, &option_version, - "print version number", nullptr }, - { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr } - }; - + bool use_config_file = true; options->kill = false; options->daemon = true; options->log_stderr = false; options->verbose = false; - context = g_option_context_new("[path/to/mpd.conf]"); - g_option_context_add_main_entries(context, entries, nullptr); - - g_option_context_set_summary(context, summary); - - GError *gerror = nullptr; - ret = g_option_context_parse(context, &argc, &argv, &gerror); - g_option_context_free(context); - - if (!ret) - FatalError("option parsing failed", gerror); + // First pass: handle command line options + OptionParser parser(argc, argv); + while (parser.HasEntries()) { + if (!parser.ParseNext()) + continue; + if (parser.CheckOption(opt_kill)) { + options->kill = true; + continue; + } + if (parser.CheckOption(opt_no_config)) { + use_config_file = false; + continue; + } + if (parser.CheckOption(opt_no_daemon)) { + options->daemon = false; + continue; + } + if (parser.CheckOption(opt_stderr, opt_stdout)) { + options->log_stderr = true; + continue; + } + if (parser.CheckOption(opt_verbose)) { + options->verbose = true; + continue; + } + if (parser.CheckOption(opt_version)) + version(); + if (parser.CheckOption(opt_help, opt_help_alt)) + help(); - if (option_version) - version(); + error.Format(cmdline_domain, "invalid option: %s", + parser.GetOption()); + return false; + } /* initialize the logging library, so the configuration file parser can use it already */ log_early_init(options->verbose); - options->daemon = !option_no_daemon; - - if (option_no_config) { + if (!use_config_file) { LogDebug(cmdline_domain, "Ignoring config, using daemon defaults"); return true; - } else if (argc <= 1) { - /* default configuration file path */ - -#ifdef WIN32 - AllocatedPath path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_user_config_dir()), - CONFIG_FILE_LOCATION); - if (!path.IsNull() && FileExists(path)) - return ReadConfigFile(path, error); - - const char *const*system_config_dirs = - g_get_system_config_dirs(); + } - for (unsigned i = 0; system_config_dirs[i] != nullptr; ++i) { - path = PathBuildChecked(AllocatedPath::FromUTF8(system_config_dirs[i]), - CONFIG_FILE_LOCATION); - if (!path.IsNull() && FileExists(path)) - return ReadConfigFile(path, error); + // Second pass: find non-option parameters (i.e. config file) + const char *config_file = nullptr; + for (int i = 1; i < argc; ++i) { + if (OptionParser::IsOption(argv[i])) + continue; + if (config_file == nullptr) { + config_file = argv[i]; + continue; } -#else - AllocatedPath path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_user_config_dir()), - USER_CONFIG_FILE_LOCATION_XDG); - if (!path.IsNull() && FileExists(path)) - return ReadConfigFile(path, error); + error.Set(cmdline_domain, "too many arguments"); + return false; + } - path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_home_dir()), - USER_CONFIG_FILE_LOCATION1); - if (!path.IsNull() && FileExists(path)) - return ReadConfigFile(path, error); + if (config_file != nullptr) { + /* use specified configuration file */ + return ReadConfigFile(Path::FromFS(config_file), error); + } - path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_home_dir()), - USER_CONFIG_FILE_LOCATION2); - if (!path.IsNull() && FileExists(path)) - return ReadConfigFile(path, error); + /* use default configuration file path */ +#ifdef WIN32 + AllocatedPath path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_user_config_dir()), + CONFIG_FILE_LOCATION); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error); + + const char *const*system_config_dirs = + g_get_system_config_dirs(); - path = AllocatedPath::FromUTF8(SYSTEM_CONFIG_FILE_LOCATION); + for (unsigned i = 0; system_config_dirs[i] != nullptr; ++i) { + path = PathBuildChecked(AllocatedPath::FromUTF8(system_config_dirs[i]), + CONFIG_FILE_LOCATION); if (!path.IsNull() && FileExists(path)) return ReadConfigFile(path, error); -#endif - - error.Set(cmdline_domain, "No configuration file found"); - return false; - } else if (argc == 2) { - /* specified configuration file */ - return ReadConfigFile(Path::FromFS(argv[1]), error); - } else { - error.Set(cmdline_domain, "too many arguments"); - return false; } +#else + AllocatedPath path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_user_config_dir()), + USER_CONFIG_FILE_LOCATION_XDG); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error); + + path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_home_dir()), + USER_CONFIG_FILE_LOCATION1); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error); + + path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_home_dir()), + USER_CONFIG_FILE_LOCATION2); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error); + + path = AllocatedPath::FromUTF8(SYSTEM_CONFIG_FILE_LOCATION); + if (!path.IsNull() && FileExists(path)) + return ReadConfigFile(path, error); +#endif + error.Set(cmdline_domain, "No configuration file found"); + return false; } diff --git a/src/CommandLine.hxx b/src/CommandLine.hxx index 214150eae..3b24a0d77 100644 --- a/src/CommandLine.hxx +++ b/src/CommandLine.hxx @@ -20,15 +20,13 @@ #ifndef MPD_COMMAND_LINE_HXX #define MPD_COMMAND_LINE_HXX -#include - class Error; struct options { - gboolean kill; - gboolean daemon; - gboolean log_stderr; - gboolean verbose; + bool kill; + bool daemon; + bool log_stderr; + bool verbose; }; bool diff --git a/src/util/OptionDef.hxx b/src/util/OptionDef.hxx new file mode 100644 index 000000000..ca9e6083f --- /dev/null +++ b/src/util/OptionDef.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UTIL_OPTIONDEF_HXX +#define MPD_UTIL_OPTIONDEF_HXX + +/** + * Command line option definition. + */ +class OptionDef +{ + const char *long_option; + char short_option; + const char *desc; +public: + constexpr OptionDef(const char *_long_option, const char *_desc) + : long_option(_long_option), + short_option(0), + desc(_desc) { } + + constexpr OptionDef(const char *_long_option, + char _short_option, const char *_desc) + : long_option(_long_option), + short_option(_short_option), + desc(_desc) { } + + bool HasLongOption() const { return long_option != nullptr; } + bool HasShortOption() const { return short_option != 0; } + bool HasDescription() const { return desc != nullptr; } + + const char *GetLongOption() const { + assert(HasLongOption()); + return long_option; + } + + char GetShortOption() const { + assert(HasShortOption()); + return short_option; + } + + const char *GetDescription() const { + assert(HasDescription()); + return desc; + } +}; + +#endif diff --git a/src/util/OptionParser.cxx b/src/util/OptionParser.cxx new file mode 100644 index 000000000..b8addb9a2 --- /dev/null +++ b/src/util/OptionParser.cxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "OptionParser.hxx" +#include "OptionDef.hxx" + +#include + +bool OptionParser::CheckOption(const OptionDef &opt) +{ + assert(option != nullptr); + + if (is_long) + return opt.HasLongOption() && + strcmp(option, opt.GetLongOption()) == 0; + + return opt.HasShortOption() && + option[0] == opt.GetShortOption() && + option[1] == '\0'; +} + +bool OptionParser::ParseNext() +{ + assert(HasEntries()); + char *arg = *argv; + ++argv; + --argc; + if (arg[0] == '-') { + if (arg[1] == '-') { + option = arg + 2; + is_long = true; + } + else { + option = arg + 1; + is_long = false; + } + option_raw = arg; + return true; + } + option = nullptr; + option_raw = nullptr; + return false; +} diff --git a/src/util/OptionParser.hxx b/src/util/OptionParser.hxx new file mode 100644 index 000000000..74091bc29 --- /dev/null +++ b/src/util/OptionParser.hxx @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UTIL_OPTIONPARSER_HXX +#define MPD_UTIL_OPTIONPARSER_HXX + +#include + +class OptionDef; + +/** + * Command line option parser. + */ +class OptionParser +{ + int argc; + char **argv; + char *option; + char *option_raw; + bool is_long; +public: + /** + * Constructs #OptionParser. + */ + OptionParser(int _argc, char **_argv) + : argc(_argc - 1), argv(_argv + 1), + option(nullptr), option_raw(nullptr), is_long(false) { } + + /** + * Checks if there are command line entries to process. + */ + bool HasEntries() const { return argc > 0; } + + /** + * Gets the last parsed option. + */ + char *GetOption() { + assert(option_raw != nullptr); + return option_raw; + } + + /** + * Checks if current option is a specified option. + */ + bool CheckOption(const OptionDef& opt); + + /** + * Checks if current option is a specified option + * or specified alternative option. + */ + bool CheckOption(const OptionDef& opt, const OptionDef &alt_opt) { + return CheckOption(opt) || CheckOption(alt_opt); + } + + /** + * Parses current command line entry. + * Returns true on success, false otherwise. + * Regardless of result, advances current position to the next + * command line entry. + */ + bool ParseNext(); + + /** + * Checks if specified string is a command line option. + */ + static bool IsOption(const char *s) { + assert(s != nullptr); + return s[0] == '-'; + } +}; + +#endif -- cgit v1.2.3