diff options
| -rw-r--r-- | src/commandline.cpp | 55 | ||||
| -rw-r--r-- | src/commandline.h | 114 |
2 files changed, 150 insertions, 19 deletions
diff --git a/src/commandline.cpp b/src/commandline.cpp index 3f8a6b1a..ff1be764 100644 --- a/src/commandline.cpp +++ b/src/commandline.cpp @@ -13,6 +13,8 @@ std::string pad_right(std::string s, std::size_t n, char c=' ') return s; } +// formats the list of pairs in two columns +// std::string table( const std::vector<std::pair<std::string, std::string>>& v, std::size_t indent, std::size_t spacing) @@ -60,6 +62,9 @@ std::optional<int> CommandLine::run(const std::wstring& line) args.erase(args.begin()); } + // parsing the first part of the command line, including global options and + // command name, but not the rest, which will be collected below + auto parsed = po::wcommand_line_parser(args) .options(m_allOptions) .positional(m_positional) @@ -69,20 +74,29 @@ std::optional<int> CommandLine::run(const std::wstring& line) po::store(parsed, m_vm); po::notify(m_vm); + // collect options past the command name auto opts = po::collect_unrecognized( parsed.options, po::include_positional); if (m_vm.count("command")) { + // there's a word as the first argument; this may be a command name or + // an old style exe name/binary + const auto commandName = m_vm["command"].as<std::string>(); + // look for the command by name first for (auto&& c : m_commands) { if (c->name() == commandName) { + // this is a command + // remove the command name itself opts.erase(opts.begin()); try { + // parse the the remainder of the command line according to the + // command's options po::wcommand_line_parser parser(opts); auto co = c->allOptions(); @@ -106,6 +120,7 @@ std::optional<int> CommandLine::run(const std::wstring& line) return 0; } + // run the command return c->run(line, m_vm, opts); } catch(po::error& e) @@ -122,6 +137,11 @@ std::optional<int> CommandLine::run(const std::wstring& line) } } + + // the first word on the command line is not a valid command, try the other + // stuff; this is handled in main.cpp + + // look for help if (m_vm.count("help")) { env::Console console; std::cout << usage() << "\n"; @@ -130,12 +150,25 @@ std::optional<int> CommandLine::run(const std::wstring& line) if (!opts.empty()) { const auto qs = QString::fromStdWString(opts[0]); + + if (qs.startsWith("--")) { + // assume that for something like `ModOrganizer.exe --bleh`, it's just + // a bad option instead of an executable that starts with "--" + env::Console console; + std::cerr << "\nUnrecognized option " << qs.toStdString() << "\n"; + + return 1; + } + + // try as an moshorcut:// m_shortcut = qs; if (!m_shortcut.isValid()) { + // not a shortcut, try a link if (isNxmLink(qs)) { m_nxmLink = qs; } else { + // assume an executable name/binary m_executable = qs; } } @@ -182,6 +215,7 @@ void CommandLine::createOptions() ("command", po::value<std::string>(), "command") ("subargs", po::value<std::vector<std::string> >(), "args"); + // one command name, followed by any arguments for that command m_positional .add("command", 1) .add("subargs", -1); @@ -210,6 +244,7 @@ std::string CommandLine::usage(const Command* c) const << "\n" << "Commands:\n"; + // name and description for all commands std::vector<std::pair<std::string, std::string>> v; for (auto&& c : m_commands) { v.push_back({c->name(), c->description()}); @@ -224,6 +259,7 @@ std::string CommandLine::usage(const Command* c) const << "Global options:\n" << m_visibleOptions << "\n"; + // show the more text unless this is usage for a specific command if (!c) { oss << "\n" << more() << "\n"; } @@ -247,6 +283,8 @@ std::optional<QString> CommandLine::profile() const std::optional<QString> CommandLine::instance() const { + // note that moshortcut:// overrides -i + if (m_shortcut.isValid() && m_shortcut.hasInstance()) { return m_shortcut.instance(); } else if (m_vm.count("instance")) { @@ -369,21 +407,6 @@ po::positional_options_description Command::getPositional() const } -std::string Command::usage() const -{ - std::ostringstream oss; - - oss - << "\n" - << "Usage:\n" - << " ModOrganizer.exe [options] [[command] [command-options]]\n" - << "\n" - << "Options:\n" - << visibleOptions() << "\n"; - - return oss.str(); -} - std::optional<int> Command::run( const std::wstring& originalLine, po::variables_map vm, @@ -569,8 +592,6 @@ std::optional<int> ExeCommand::doRun() const auto args = vm()["arguments"].as<std::string>(); const auto cwd = vm()["cwd"].as<std::string>(); - - return 0; } diff --git a/src/commandline.h b/src/commandline.h index 72018ba3..478ee8ea 100644 --- a/src/commandline.h +++ b/src/commandline.h @@ -9,44 +9,96 @@ namespace cl namespace po = boost::program_options; - +// base class for all commands +// class Command { public: virtual ~Command() = default; + // command name, used on the command line to invoke it; this is meta().name + // std::string name() const; + + // command short description, shown in general help; this is + // meta().description + // std::string description() const; + + // usage line, puts together the name and whatever getUsageLine() returns + // std::string usageLine() const; + + // returns all options for this command, including hidden ones; used to parse + // the command line + // po::options_description allOptions() const; + + // returns visible options, used to display usage + // po::options_description visibleOptions() const; + + // returns positional arguments + // po::positional_options_description positional() const; - std::string usage() const; + // whether the command allows for unregistered options; this is only used for + // the old `launch` command and shouldn't be used anywhere else + // virtual bool allow_unregistered() const; + // runs this command, eventually calls doRun() + // std::optional<int> run( const std::wstring& originalLine, po::variables_map vm, std::vector<std::wstring> untouched); protected: + // meta information about this command, returned by derived classes + // struct Meta { std::string name, description; }; + // returns the usage line for this command, not including the name + // virtual std::string getUsageLine() const; + + // returns visible options specific to this command + // virtual po::options_description getVisibleOptions() const; + + // returns hidden options specific to this command + // virtual po::options_description getInternalOptions() const; + + // returns positional arguments specific to this command + // virtual po::positional_options_description getPositional() const; + + // meta + // virtual Meta meta() const = 0; + + // runs the command + // virtual std::optional<int> doRun() = 0; + + // returns the original command line + // const std::wstring& originalCmd() const; + + // variables + // const po::variables_map& vm() const; + + // returns unparsed options, only used by launch + // const std::vector<std::wstring>& untouched() const; private: @@ -56,6 +108,8 @@ private: }; +// generates a crash dump for another MO process +// class CrashDumpCommand : public Command { protected: @@ -93,6 +147,8 @@ protected: }; +// runs a configured executable +// class ExeCommand : public Command { protected: @@ -105,6 +161,8 @@ protected: }; +// runs an arbitrary executable +// class RunCommand : public Command { protected: @@ -114,24 +172,76 @@ protected: }; +// parses the command line and runs any given command +// +// the command line used to support a few commands but with no real conventions; +// those are mostly preserved for backwards compatibility, but deprecated: +// +// - moshortcut:// for desktop/taskbar shortcuts, may contain an instance name +// and a configured executable or arbitrary binary (still used in MO for +// shortcuts) +// +// - nxm:// links +// +// - the name of a configured executable or path to binary, followed by +// arbitrary parameters, forwarded to the program +// +// any command added CommandLine will unfortunately break any executable with +// the same name: `ModOrganizer.exe run` used to launch a program named "run" +// but will now execute the command "run" +// +// if moshortcut:// is detected and has an instance, it will override -i if both +// are given +// class CommandLine { public: CommandLine(); + // parses the given command line and executes the appropriate command, if + // any + // + // returns an empty optional if execution should continue, or a return code + // if MO must quit + // std::optional<int> run(const std::wstring& line); + + // clears parsed options, used when MO is "restarted" so the options aren't + // processed again + // void clear(); + // global usage string plus usage for the given command, if any + // std::string usage(const Command* c=nullptr) const; + + // whether --multiple was given + // bool multiple() const; + + // profile override (-p) + // std::optional<QString> profile() const; + + // instance override (-i) + // std::optional<QString> instance() const; + // returns the data parsed from an moshortcut:// option, if any + // const MOShortcut& shortcut() const; + + // returns the nxm:// link, if any + // std::optional<QString> nxmLink() const; + + // returns the executable/binary, if any std::optional<QString> executable() const; + // returns the list of arguments, excluding moshortcut or executable name; + // deprecated, only use with executable() + // const QStringList& untouched() const; private: |
