// Copyright (c) 2005-2021 Jay Berkenbilt // // This file is part of qpdf. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Versions of qpdf prior to version 7 were released under the terms // of version 2.0 of the Artistic License. At your option, you may // continue to consider qpdf to be licensed under those terms. Please // see the manual for additional information. #ifndef QPDFARGPARSER_HH #define QPDFARGPARSER_HH #include #include #include #include #include #include #include #include // This is not a general-purpose argument parser. It is tightly // crafted to work with qpdf. qpdf's command-line syntax is very // complex because of its long history, and it doesn't really follow // any kind of normal standard for arguments, but it's important for // backward compatibility to ensure we don't break what constitutes a // valid command. This class handles the quirks of qpdf's argument // parsing, bash/zsh completion, and support for @argfile to read // arguments from a file. For the qpdf CLI, setup of QPDFArgParser is // done mostly by automatically-generated code (one-off code for // qpdf), though the handlers themselves are hand-coded. See // generate_auto_job at the top of the source tree for details. // Note about memory: there is code that expects argv to be a char*[], // meaning that arguments are writable. Several operations, including // reading arguments from a file or parsing a line for bash // completion, involve fabricating an argv array. To ensure that the // memory is valid and is cleaned up properly, we keep various vectors // of smart character pointers that argv points into. In order for // those pointers to remain valid, the QPDFArgParser instance must // remain in scope for the life of any code that may reference // anything from argv. class QPDFArgParser { public: // progname_env is used to override argv[0] when figuring out the // name of the executable for setting up completion. This may be // needed if the program is invoked by a wrapper. QPDF_DLL QPDFArgParser(int argc, char* argv[], char const* progname_env); // Calls exit(0) if a help option is given or if in completion // mode. If there are argument parsing errors, QPDFUsage is // thrown. QPDF_DLL void parseArgs(); // Return the program name as the last path element of the program // executable. QPDF_DLL std::string getProgname(); // Methods for registering arguments. QPDFArgParser starts off // with the main option table selected. You can add handlers for // arguments in the current option table, and you can select which // option table is current. The help option table is special and // contains arguments that are only valid as the first and only // option. Named option tables are for subparsers and always start // a series of options that end with `--`. typedef std::function bare_arg_handler_t; typedef std::function param_arg_handler_t; QPDF_DLL void selectMainOptionTable(); QPDF_DLL void selectHelpOptionTable(); QPDF_DLL void selectOptionTable(std::string const& name); // Register a new options table. This also selects the option table. QPDF_DLL void registerOptionTable( std::string const& name, bare_arg_handler_t end_handler); // Add handlers for options in the current table QPDF_DLL void addPositional(param_arg_handler_t); QPDF_DLL void addBare(std::string const& arg, bare_arg_handler_t); QPDF_DLL void addRequiredParameter( std::string const& arg, param_arg_handler_t, char const* parameter_name); QPDF_DLL void addOptionalParameter(std::string const& arg, param_arg_handler_t); QPDF_DLL void addChoices( std::string const& arg, param_arg_handler_t, bool required, char const** choices); // The default behavior when an invalid choice is specified with // an option that takes choices is to list all the choices. This // may not be good if there are too many choices, so you can // provide your own handler in this case. QPDF_DLL void addInvalidChoiceHandler(std::string const& arg, param_arg_handler_t); // The final check handler is called at the very end of argument // parsing. QPDF_DLL void addFinalCheck(bare_arg_handler_t); // Help generation methods // Help is available on topics and options. Options may be // associated with topics. Users can run --help, --help=topic, or // --help=--arg to get help. The top-level help tells the user how // to run help and lists available topics. Help for a topic prints // a short synopsis about the topic and lists any options that may // be associated with the topic. Help for an option provides a // short synopsis for that option. All help output is appended // with a blurb (if supplied) directing the user to the full // documentation. Help is not shown for options for which help has // not been added. This makes it possible to have undocumented // options for testing, backward-compatibility, etc. Also, it // could be quite confusing to handle appropriate help for some // inner options that may be repeated with different semantics // inside different option tables. There is also no checking for // whether an option that has help actually exists. In other // words, it's up to the caller to ensure that help actually // corresponds to the program's actual options. Rather than this // being an intentional design decision, it is because this class // is specifically for qpdf, qpdf generates its help and has other // means to ensure consistency. // Note about newlines: // // short_text should fit easily after the topic/option on the same // line and should not end with a newline. Keep it to around 40 to // 60 characters. // // long_text and footer should end with a single newline. They can // have embedded newlines. Keep lines to under 80 columns. // // QPDFArgParser does reformat the text, but it may add blank // lines in some situations. Following the above conventions will // keep the help looking uniform. // If provided, this footer is appended to all help, separated by // a blank line. QPDF_DLL void addHelpFooter(std::string const&); // Add a help topic along with the text for that topic QPDF_DLL void addHelpTopic(std::string const& topic, std::string const& short_text, std::string const& long_text); // Add help for an option, and associate it with a topic. QPDF_DLL void addOptionHelp(std::string const& option_name, std::string const& topic, std::string const& short_text, std::string const& long_text); // Return the help text for a topic or option. Passing a null // pointer returns the top-level help information. Passing an // unknown value returns a string directing the user to run the // top-level --help option. QPDF_DLL std::string getHelp(char const* topic_or_option); // Convenience methods for adding member functions of a class as // handlers. template static bare_arg_handler_t bindBare(void (T::*f)(), T* o) { return std::bind(std::mem_fn(f), o); } template static param_arg_handler_t bindParam(void (T::*f)(char *), T* o) { return std::bind(std::mem_fn(f), o, std::placeholders::_1); } // When processing arguments, indicate how many arguments remain // after the one whose handler is being called. QPDF_DLL int argsLeft() const; // Indicate whether we are in completion mode. QPDF_DLL bool isCompleting() const; // Insert a completion during argument parsing; useful for // customizing completion in the position argument handler. Should // only be used in completion mode. QPDF_DLL void insertCompletion(std::string const&); // Throw a Usage exception with the given message. In completion // mode, this just exits to prevent errors from partial commands // or other error messages from messing up completion. QPDF_DLL void usage(std::string const& message); private: struct OptionEntry { OptionEntry() : parameter_needed(false), bare_arg_handler(0), param_arg_handler(0), invalid_choice_handler(0) { } bool parameter_needed; std::string parameter_name; std::set choices; bare_arg_handler_t bare_arg_handler; param_arg_handler_t param_arg_handler; param_arg_handler_t invalid_choice_handler; }; typedef std::map option_table_t; struct HelpTopic { HelpTopic() = default; HelpTopic(std::string const& short_text, std::string const& long_text) : short_text(short_text), long_text(long_text) { } std::string short_text; std::string long_text; std::set options; }; OptionEntry& registerArg(std::string const& arg); void completionCommon(bool zsh); void argCompletionBash(); void argCompletionZsh(); void argHelp(char*); void invalidHelpArg(char*); void checkCompletion(); void handleArgFileArguments(); void handleBashArguments(); void readArgsFromFile(char const* filename); void doFinalChecks(); void addOptionsToCompletions(option_table_t&); void addChoicesToCompletions( option_table_t&, std::string const&, std::string const&); void insertCompletions( option_table_t&, std::string const&, std::string const&); void handleCompletion(); void getTopHelp(std::ostringstream&); void getAllHelp(std::ostringstream&); void getTopicHelp( std::string const& name, HelpTopic const&, std::ostringstream&); class Members { friend class QPDFArgParser; public: QPDF_DLL ~Members() = default; private: Members(int argc, char* argv[], char const* progname_env); Members(Members const&) = delete; int argc; char** argv; char const* whoami; std::string progname_env; int cur_arg; bool bash_completion; bool zsh_completion; std::string bash_prev; std::string bash_cur; std::string bash_line; std::set completions; std::map option_tables; option_table_t main_option_table; option_table_t help_option_table; option_table_t* option_table; std::string option_table_name; bare_arg_handler_t final_check_handler; std::vector> new_argv; std::vector> bash_argv; std::shared_ptr argv_ph; std::shared_ptr bash_argv_ph; std::map help_topics; std::map option_help; std::string help_footer; }; std::shared_ptr m; }; #endif // QPDFARGPARSER_HH