qpdf/include/qpdf/QPDFArgParser.hh

323 lines
12 KiB
C++

// 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 <qpdf/DLL.h>
#include <memory>
#include <string>
#include <set>
#include <map>
#include <vector>
#include <functional>
#include <sstream>
// 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<void()> bare_arg_handler_t;
typedef std::function<void(char*)> 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 <class T>
static bare_arg_handler_t bindBare(void (T::*f)(), T* o)
{
return std::bind(std::mem_fn(f), o);
}
template <class T>
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<std::string> 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<std::string, OptionEntry> 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<std::string> 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<std::string> completions;
std::map<std::string, option_table_t> 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<std::shared_ptr<char>> new_argv;
std::vector<std::shared_ptr<char>> bash_argv;
std::shared_ptr<char*> argv_ph;
std::shared_ptr<char*> bash_argv_ph;
std::map<std::string, HelpTopic> help_topics;
std::map<std::string, HelpTopic> option_help;
std::string help_footer;
};
std::shared_ptr<Members> m;
};
#endif // QPDFARGPARSER_HH