mirror of
https://github.com/qpdf/qpdf.git
synced 2025-01-03 15:17:29 +00:00
Implement QPDFArgParser based on ArgParser from qpdf.cc
This commit is contained in:
parent
8a5ba5686c
commit
52817f0a45
@ -32,6 +32,7 @@
|
|||||||
"autolabel",
|
"autolabel",
|
||||||
"automake",
|
"automake",
|
||||||
"autotools",
|
"autotools",
|
||||||
|
"baaa",
|
||||||
"backports",
|
"backports",
|
||||||
"bashcompinit",
|
"bashcompinit",
|
||||||
"berkenbilt",
|
"berkenbilt",
|
||||||
@ -319,6 +320,7 @@
|
|||||||
"qpdf",
|
"qpdf",
|
||||||
"qpdfacroformdocumenthelper",
|
"qpdfacroformdocumenthelper",
|
||||||
"qpdfannotationobjecthelper",
|
"qpdfannotationobjecthelper",
|
||||||
|
"qpdfargparser",
|
||||||
"qpdfconstants",
|
"qpdfconstants",
|
||||||
"qpdfcrypto",
|
"qpdfcrypto",
|
||||||
"qpdfcryptoimpl",
|
"qpdfcryptoimpl",
|
||||||
|
221
include/qpdf/QPDFArgParser.hh
Normal file
221
include/qpdf/QPDFArgParser.hh
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
// 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 <qpdf/PointerHolder.hh>
|
||||||
|
#include <string>
|
||||||
|
#include <set>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
// 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 not 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.
|
||||||
|
|
||||||
|
// 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:
|
||||||
|
// Usage exception is thrown if there are any errors parsing
|
||||||
|
// arguments
|
||||||
|
class QPDF_DLL_CLASS Usage: public std::runtime_error
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QPDF_DLL
|
||||||
|
Usage(std::string const&);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
// QPDFArgParser::Usage is thrown.
|
||||||
|
QPDF_DLL
|
||||||
|
void parseArgs();
|
||||||
|
|
||||||
|
// 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 addRequiredChoices(
|
||||||
|
std::string const& arg, param_arg_handler_t, char const** choices);
|
||||||
|
// The final check handler is called at the very end of argument
|
||||||
|
// parsing.
|
||||||
|
QPDF_DLL
|
||||||
|
void addFinalCheck(bare_arg_handler_t);
|
||||||
|
|
||||||
|
// 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&);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct OptionEntry
|
||||||
|
{
|
||||||
|
OptionEntry() :
|
||||||
|
parameter_needed(false),
|
||||||
|
bare_arg_handler(0),
|
||||||
|
param_arg_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;
|
||||||
|
};
|
||||||
|
friend struct OptionEntry;
|
||||||
|
|
||||||
|
OptionEntry& registerArg(std::string const& arg);
|
||||||
|
|
||||||
|
void completionCommon(bool zsh);
|
||||||
|
|
||||||
|
void argCompletionBash();
|
||||||
|
void argCompletionZsh();
|
||||||
|
|
||||||
|
void usage(std::string const& message);
|
||||||
|
void checkCompletion();
|
||||||
|
void handleArgFileArguments();
|
||||||
|
void handleBashArguments();
|
||||||
|
void readArgsFromFile(char const* filename);
|
||||||
|
void doFinalChecks();
|
||||||
|
void addOptionsToCompletions();
|
||||||
|
void addChoicesToCompletions(std::string const&, std::string const&);
|
||||||
|
void handleCompletion();
|
||||||
|
|
||||||
|
typedef std::map<std::string, OptionEntry> option_table_t;
|
||||||
|
|
||||||
|
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<PointerHolder<char>> new_argv;
|
||||||
|
std::vector<PointerHolder<char>> bash_argv;
|
||||||
|
PointerHolder<char*> argv_ph;
|
||||||
|
PointerHolder<char*> bash_argv_ph;
|
||||||
|
};
|
||||||
|
PointerHolder<Members> m;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QPDFARGPARSER_HH
|
799
libqpdf/QPDFArgParser.cc
Normal file
799
libqpdf/QPDFArgParser.cc
Normal file
@ -0,0 +1,799 @@
|
|||||||
|
#include <qpdf/QPDFArgParser.hh>
|
||||||
|
#include <qpdf/QUtil.hh>
|
||||||
|
#include <qpdf/QIntC.hh>
|
||||||
|
#include <qpdf/QTC.hh>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
QPDFArgParser::Usage::Usage(std::string const& msg) :
|
||||||
|
std::runtime_error(msg)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QPDFArgParser::Members::Members(
|
||||||
|
int argc, char* argv[], char const* progname_env) :
|
||||||
|
|
||||||
|
argc(argc),
|
||||||
|
argv(argv),
|
||||||
|
whoami(QUtil::getWhoami(argv[0])),
|
||||||
|
progname_env(progname_env),
|
||||||
|
cur_arg(0),
|
||||||
|
bash_completion(false),
|
||||||
|
zsh_completion(false),
|
||||||
|
option_table(nullptr),
|
||||||
|
final_check_handler(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QPDFArgParser::QPDFArgParser(int argc, char* argv[], char const* progname_env) :
|
||||||
|
m(new Members(argc, argv, progname_env))
|
||||||
|
{
|
||||||
|
selectHelpOptionTable();
|
||||||
|
addBare("completion-bash",
|
||||||
|
std::bind(std::mem_fn(&QPDFArgParser::argCompletionBash), this));
|
||||||
|
addBare("completion-zsh",
|
||||||
|
std::bind(std::mem_fn(&QPDFArgParser::argCompletionZsh), this));
|
||||||
|
selectMainOptionTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::selectMainOptionTable()
|
||||||
|
{
|
||||||
|
this->m->option_table = &this->m->main_option_table;
|
||||||
|
this->m->option_table_name = "main";
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::selectHelpOptionTable()
|
||||||
|
{
|
||||||
|
this->m->option_table = &this->m->help_option_table;
|
||||||
|
this->m->option_table_name = "help";
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::selectOptionTable(std::string const& name)
|
||||||
|
{
|
||||||
|
auto t = this->m->option_tables.find(name);
|
||||||
|
if (t == this->m->option_tables.end())
|
||||||
|
{
|
||||||
|
QTC::TC("libtests", "QPDFArgParser select unregistered table");
|
||||||
|
throw std::logic_error(
|
||||||
|
"QPDFArgParser: selecting unregistered option table " + name);
|
||||||
|
}
|
||||||
|
this->m->option_table = &(t->second);
|
||||||
|
this->m->option_table_name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::registerOptionTable(
|
||||||
|
std::string const& name,
|
||||||
|
bare_arg_handler_t end_handler)
|
||||||
|
{
|
||||||
|
if (0 != this->m->option_tables.count(name))
|
||||||
|
{
|
||||||
|
QTC::TC("libtests", "QPDFArgParser register registered table");
|
||||||
|
throw std::logic_error(
|
||||||
|
"QPDFArgParser: registering already registered option table "
|
||||||
|
+ name);
|
||||||
|
}
|
||||||
|
this->m->option_tables[name];
|
||||||
|
selectOptionTable(name);
|
||||||
|
addBare("--", end_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPDFArgParser::OptionEntry&
|
||||||
|
QPDFArgParser::registerArg(std::string const& arg)
|
||||||
|
{
|
||||||
|
if (0 != this->m->option_table->count(arg))
|
||||||
|
{
|
||||||
|
QTC::TC("libtests", "QPDFArgParser duplicate handler");
|
||||||
|
throw std::logic_error(
|
||||||
|
"QPDFArgParser: adding a duplicate handler for option " +
|
||||||
|
arg + " in " + this->m->option_table_name +
|
||||||
|
" option table");
|
||||||
|
}
|
||||||
|
return ((*this->m->option_table)[arg]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::addPositional(param_arg_handler_t handler)
|
||||||
|
{
|
||||||
|
OptionEntry& oe = registerArg("");
|
||||||
|
oe.param_arg_handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::addBare(
|
||||||
|
std::string const& arg, bare_arg_handler_t handler)
|
||||||
|
{
|
||||||
|
OptionEntry& oe = registerArg(arg);
|
||||||
|
oe.parameter_needed = false;
|
||||||
|
oe.bare_arg_handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::addRequiredParameter(
|
||||||
|
std::string const& arg,
|
||||||
|
param_arg_handler_t handler,
|
||||||
|
char const* parameter_name)
|
||||||
|
{
|
||||||
|
OptionEntry& oe = registerArg(arg);
|
||||||
|
oe.parameter_needed = true;
|
||||||
|
oe.parameter_name = parameter_name;
|
||||||
|
oe.param_arg_handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::addOptionalParameter(
|
||||||
|
std::string const& arg, param_arg_handler_t handler)
|
||||||
|
{
|
||||||
|
OptionEntry& oe = registerArg(arg);
|
||||||
|
oe.parameter_needed = false;
|
||||||
|
oe.param_arg_handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::addRequiredChoices(
|
||||||
|
std::string const& arg,
|
||||||
|
param_arg_handler_t handler,
|
||||||
|
char const** choices)
|
||||||
|
{
|
||||||
|
OptionEntry& oe = registerArg(arg);
|
||||||
|
oe.parameter_needed = true;
|
||||||
|
oe.param_arg_handler = handler;
|
||||||
|
for (char const** i = choices; *i; ++i)
|
||||||
|
{
|
||||||
|
oe.choices.insert(*i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::addFinalCheck(bare_arg_handler_t handler)
|
||||||
|
{
|
||||||
|
this->m->final_check_handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
QPDFArgParser::isCompleting() const
|
||||||
|
{
|
||||||
|
return this->m->bash_completion;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
QPDFArgParser::argsLeft() const
|
||||||
|
{
|
||||||
|
return this->m->argc - this->m->cur_arg - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::insertCompletion(std::string const& arg)
|
||||||
|
{
|
||||||
|
this->m->completions.insert(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::completionCommon(bool zsh)
|
||||||
|
{
|
||||||
|
std::string progname = this->m->argv[0];
|
||||||
|
std::string executable;
|
||||||
|
std::string appdir;
|
||||||
|
std::string appimage;
|
||||||
|
if (QUtil::get_env(this->m->progname_env.c_str(), &executable))
|
||||||
|
{
|
||||||
|
progname = executable;
|
||||||
|
}
|
||||||
|
else if (QUtil::get_env("APPDIR", &appdir) &&
|
||||||
|
QUtil::get_env("APPIMAGE", &appimage))
|
||||||
|
{
|
||||||
|
// Detect if we're in an AppImage and adjust
|
||||||
|
if ((appdir.length() < strlen(this->m->argv[0])) &&
|
||||||
|
(strncmp(appdir.c_str(), this->m->argv[0], appdir.length()) == 0))
|
||||||
|
{
|
||||||
|
progname = appimage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (zsh)
|
||||||
|
{
|
||||||
|
std::cout << "autoload -U +X bashcompinit && bashcompinit && ";
|
||||||
|
}
|
||||||
|
std::cout << "complete -o bashdefault -o default";
|
||||||
|
if (! zsh)
|
||||||
|
{
|
||||||
|
std::cout << " -o nospace";
|
||||||
|
}
|
||||||
|
std::cout << " -C " << progname << " " << this->m->whoami << std::endl;
|
||||||
|
// Put output before error so calling from zsh works properly
|
||||||
|
std::string path = progname;
|
||||||
|
size_t slash = path.find('/');
|
||||||
|
if ((slash != 0) && (slash != std::string::npos))
|
||||||
|
{
|
||||||
|
std::cerr << "WARNING: " << this->m->whoami << " completion enabled"
|
||||||
|
<< " using relative path to executable" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::argCompletionBash()
|
||||||
|
{
|
||||||
|
completionCommon(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::argCompletionZsh()
|
||||||
|
{
|
||||||
|
completionCommon(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::handleArgFileArguments()
|
||||||
|
{
|
||||||
|
// Support reading arguments from files. Create a new argv. Ensure
|
||||||
|
// that argv itself as well as all its contents are automatically
|
||||||
|
// deleted by using PointerHolder objects to back the pointers in
|
||||||
|
// argv.
|
||||||
|
this->m->new_argv.push_back(
|
||||||
|
PointerHolder<char>(true, QUtil::copy_string(this->m->argv[0])));
|
||||||
|
for (int i = 1; i < this->m->argc; ++i)
|
||||||
|
{
|
||||||
|
char* argfile = 0;
|
||||||
|
if ((strlen(this->m->argv[i]) > 1) && (this->m->argv[i][0] == '@'))
|
||||||
|
{
|
||||||
|
argfile = 1 + this->m->argv[i];
|
||||||
|
if (strcmp(argfile, "-") != 0)
|
||||||
|
{
|
||||||
|
if (! QUtil::file_can_be_opened(argfile))
|
||||||
|
{
|
||||||
|
// The file's not there; treating as regular option
|
||||||
|
argfile = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (argfile)
|
||||||
|
{
|
||||||
|
readArgsFromFile(1 + this->m->argv[i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->m->new_argv.push_back(
|
||||||
|
PointerHolder<char>(
|
||||||
|
true, QUtil::copy_string(this->m->argv[i])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->m->argv_ph =
|
||||||
|
PointerHolder<char*>(true, new char*[1 + this->m->new_argv.size()]);
|
||||||
|
this->m->argv = this->m->argv_ph.getPointer();
|
||||||
|
for (size_t i = 0; i < this->m->new_argv.size(); ++i)
|
||||||
|
{
|
||||||
|
this->m->argv[i] = this->m->new_argv.at(i).getPointer();
|
||||||
|
}
|
||||||
|
this->m->argc = QIntC::to_int(this->m->new_argv.size());
|
||||||
|
this->m->argv[this->m->argc] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::handleBashArguments()
|
||||||
|
{
|
||||||
|
// Do a minimal job of parsing bash_line into arguments. This
|
||||||
|
// doesn't do everything the shell does (e.g. $(...), variable
|
||||||
|
// expansion, arithmetic, globs, etc.), but it should be good
|
||||||
|
// enough for purposes of handling completion. As we build up the
|
||||||
|
// new argv, we can't use this->m->new_argv because this code has to
|
||||||
|
// interoperate with @file arguments, so memory for both ways of
|
||||||
|
// fabricating argv has to be protected.
|
||||||
|
|
||||||
|
bool last_was_backslash = false;
|
||||||
|
enum { st_top, st_squote, st_dquote } state = st_top;
|
||||||
|
std::string arg;
|
||||||
|
for (std::string::iterator iter = this->m->bash_line.begin();
|
||||||
|
iter != this->m->bash_line.end(); ++iter)
|
||||||
|
{
|
||||||
|
char ch = (*iter);
|
||||||
|
if (last_was_backslash)
|
||||||
|
{
|
||||||
|
arg.append(1, ch);
|
||||||
|
last_was_backslash = false;
|
||||||
|
}
|
||||||
|
else if (ch == '\\')
|
||||||
|
{
|
||||||
|
last_was_backslash = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool append = false;
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case st_top:
|
||||||
|
if (QUtil::is_space(ch))
|
||||||
|
{
|
||||||
|
if (! arg.empty())
|
||||||
|
{
|
||||||
|
this->m->bash_argv.push_back(
|
||||||
|
PointerHolder<char>(
|
||||||
|
true, QUtil::copy_string(arg.c_str())));
|
||||||
|
arg.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ch == '"')
|
||||||
|
{
|
||||||
|
state = st_dquote;
|
||||||
|
}
|
||||||
|
else if (ch == '\'')
|
||||||
|
{
|
||||||
|
state = st_squote;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
append = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case st_squote:
|
||||||
|
if (ch == '\'')
|
||||||
|
{
|
||||||
|
state = st_top;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
append = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case st_dquote:
|
||||||
|
if (ch == '"')
|
||||||
|
{
|
||||||
|
state = st_top;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
append = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (append)
|
||||||
|
{
|
||||||
|
arg.append(1, ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this->m->bash_argv.empty())
|
||||||
|
{
|
||||||
|
// This can't happen if properly invoked by bash, but ensure
|
||||||
|
// we have a valid argv[0] regardless.
|
||||||
|
this->m->bash_argv.push_back(
|
||||||
|
PointerHolder<char>(
|
||||||
|
true, QUtil::copy_string(this->m->argv[0])));
|
||||||
|
}
|
||||||
|
// Explicitly discard any non-space-terminated word. The "current
|
||||||
|
// word" is handled specially.
|
||||||
|
this->m->bash_argv_ph =
|
||||||
|
PointerHolder<char*>(true, new char*[1 + this->m->bash_argv.size()]);
|
||||||
|
this->m->argv = this->m->bash_argv_ph.getPointer();
|
||||||
|
for (size_t i = 0; i < this->m->bash_argv.size(); ++i)
|
||||||
|
{
|
||||||
|
this->m->argv[i] = this->m->bash_argv.at(i).getPointer();
|
||||||
|
}
|
||||||
|
this->m->argc = QIntC::to_int(this->m->bash_argv.size());
|
||||||
|
this->m->argv[this->m->argc] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::usage(std::string const& message)
|
||||||
|
{
|
||||||
|
if (this->m->bash_completion)
|
||||||
|
{
|
||||||
|
// This will cause bash to fall back to regular file completion.
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
throw Usage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::readArgsFromFile(char const* filename)
|
||||||
|
{
|
||||||
|
std::list<std::string> lines;
|
||||||
|
if (strcmp(filename, "-") == 0)
|
||||||
|
{
|
||||||
|
QTC::TC("libtests", "QPDFArgParser read args from stdin");
|
||||||
|
lines = QUtil::read_lines_from_file(std::cin);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QTC::TC("libtests", "QPDFArgParser read args from file");
|
||||||
|
lines = QUtil::read_lines_from_file(filename);
|
||||||
|
}
|
||||||
|
for (std::list<std::string>::iterator iter = lines.begin();
|
||||||
|
iter != lines.end(); ++iter)
|
||||||
|
{
|
||||||
|
this->m->new_argv.push_back(
|
||||||
|
PointerHolder<char>(true, QUtil::copy_string((*iter).c_str())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::checkCompletion()
|
||||||
|
{
|
||||||
|
// See if we're being invoked from bash completion.
|
||||||
|
std::string bash_point_env;
|
||||||
|
// On Windows with mingw, there have been times when there appears
|
||||||
|
// to be no way to distinguish between an empty environment
|
||||||
|
// variable and an unset variable. There are also conditions under
|
||||||
|
// which bash doesn't set COMP_LINE. Therefore, enter this logic
|
||||||
|
// if either COMP_LINE or COMP_POINT are set. They will both be
|
||||||
|
// set together under ordinary circumstances.
|
||||||
|
bool got_line = QUtil::get_env("COMP_LINE", &this->m->bash_line);
|
||||||
|
bool got_point = QUtil::get_env("COMP_POINT", &bash_point_env);
|
||||||
|
if (got_line || got_point)
|
||||||
|
{
|
||||||
|
size_t p = QUtil::string_to_uint(bash_point_env.c_str());
|
||||||
|
if (p < this->m->bash_line.length())
|
||||||
|
{
|
||||||
|
// Truncate the line. We ignore everything at or after the
|
||||||
|
// cursor for completion purposes.
|
||||||
|
this->m->bash_line = this->m->bash_line.substr(0, p);
|
||||||
|
}
|
||||||
|
if (p > this->m->bash_line.length())
|
||||||
|
{
|
||||||
|
p = this->m->bash_line.length();
|
||||||
|
}
|
||||||
|
// Set bash_cur and bash_prev based on bash_line rather than
|
||||||
|
// relying on argv. This enables us to use bashcompinit to get
|
||||||
|
// completion in zsh too since bashcompinit sets COMP_LINE and
|
||||||
|
// COMP_POINT but doesn't invoke the command with options like
|
||||||
|
// bash does.
|
||||||
|
|
||||||
|
// p is equal to length of the string. Walk backwards looking
|
||||||
|
// for the first separator. bash_cur is everything after the
|
||||||
|
// last separator, possibly empty.
|
||||||
|
char sep(0);
|
||||||
|
while (p > 0)
|
||||||
|
{
|
||||||
|
--p;
|
||||||
|
char ch = this->m->bash_line.at(p);
|
||||||
|
if ((ch == ' ') || (ch == '=') || (ch == ':'))
|
||||||
|
{
|
||||||
|
sep = ch;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (1+p <= this->m->bash_line.length())
|
||||||
|
{
|
||||||
|
this->m->bash_cur = this->m->bash_line.substr(
|
||||||
|
1+p, std::string::npos);
|
||||||
|
}
|
||||||
|
if ((sep == ':') || (sep == '='))
|
||||||
|
{
|
||||||
|
// Bash sets prev to the non-space separator if any.
|
||||||
|
// Actually, if there are multiple separators in a row,
|
||||||
|
// they are all included in prev, but that detail is not
|
||||||
|
// important to us and not worth coding.
|
||||||
|
this->m->bash_prev = this->m->bash_line.substr(p, 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Go back to the last separator and set prev based on
|
||||||
|
// that.
|
||||||
|
size_t p1 = p;
|
||||||
|
while (p1 > 0)
|
||||||
|
{
|
||||||
|
--p1;
|
||||||
|
char ch = this->m->bash_line.at(p1);
|
||||||
|
if ((ch == ' ') || (ch == ':') || (ch == '='))
|
||||||
|
{
|
||||||
|
this->m->bash_prev =
|
||||||
|
this->m->bash_line.substr(p1 + 1, p - p1 - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this->m->bash_prev.empty())
|
||||||
|
{
|
||||||
|
this->m->bash_prev = this->m->bash_line.substr(0, p);
|
||||||
|
}
|
||||||
|
if (this->m->argc == 1)
|
||||||
|
{
|
||||||
|
// This is probably zsh using bashcompinit. There are a
|
||||||
|
// few differences in the expected output.
|
||||||
|
this->m->zsh_completion = true;
|
||||||
|
}
|
||||||
|
handleBashArguments();
|
||||||
|
this->m->bash_completion = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::parseArgs()
|
||||||
|
{
|
||||||
|
selectMainOptionTable();
|
||||||
|
checkCompletion();
|
||||||
|
handleArgFileArguments();
|
||||||
|
for (this->m->cur_arg = 1;
|
||||||
|
this->m->cur_arg < this->m->argc;
|
||||||
|
++this->m->cur_arg)
|
||||||
|
{
|
||||||
|
bool help_option = false;
|
||||||
|
bool end_option = false;
|
||||||
|
auto oep = this->m->option_table->end();
|
||||||
|
char* arg = this->m->argv[this->m->cur_arg];
|
||||||
|
char* parameter = nullptr;
|
||||||
|
std::string o_arg(arg);
|
||||||
|
std::string arg_s(arg);
|
||||||
|
if ((strcmp(arg, "--") == 0) &&
|
||||||
|
(this->m->option_table != &this->m->main_option_table))
|
||||||
|
{
|
||||||
|
// Special case for -- option, which is used to break out
|
||||||
|
// of subparsers.
|
||||||
|
oep = this->m->option_table->find("--");
|
||||||
|
end_option = true;
|
||||||
|
if (oep == this->m->option_table->end())
|
||||||
|
{
|
||||||
|
// This is registered automatically, so this can't happen.
|
||||||
|
throw std::logic_error("ArgParser: -- handler not registered");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((arg[0] == '-') && (strcmp(arg, "-") != 0))
|
||||||
|
{
|
||||||
|
++arg;
|
||||||
|
if (arg[0] == '-')
|
||||||
|
{
|
||||||
|
// Be lax about -arg vs --arg
|
||||||
|
++arg;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QTC::TC("libtests", "QPDFArgParser single dash");
|
||||||
|
}
|
||||||
|
if (strlen(arg) > 0)
|
||||||
|
{
|
||||||
|
// Prevent --=something from being treated as an empty
|
||||||
|
// arg since the empty string in the option table is
|
||||||
|
// for positional arguments.
|
||||||
|
parameter = const_cast<char*>(strchr(1 + arg, '='));
|
||||||
|
}
|
||||||
|
if (parameter)
|
||||||
|
{
|
||||||
|
*parameter++ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
arg_s = arg;
|
||||||
|
|
||||||
|
if ((! this->m->bash_completion) &&
|
||||||
|
(this->m->argc == 2) && (this->m->cur_arg == 1) &&
|
||||||
|
this->m->help_option_table.count(arg_s))
|
||||||
|
{
|
||||||
|
// Handle help option, which is only valid as the sole
|
||||||
|
// option.
|
||||||
|
QTC::TC("libtests", "QPDFArgParser help option");
|
||||||
|
oep = this->m->help_option_table.find(arg_s);
|
||||||
|
help_option = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! (help_option || arg_s.empty() || (arg_s.at(0) == '-')))
|
||||||
|
{
|
||||||
|
oep = this->m->option_table->find(arg_s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The empty string maps to the positional argument
|
||||||
|
// handler.
|
||||||
|
QTC::TC("libtests", "QPDFArgParser positional");
|
||||||
|
oep = this->m->option_table->find("");
|
||||||
|
parameter = arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oep == this->m->option_table->end())
|
||||||
|
{
|
||||||
|
QTC::TC("libtests", "QPDFArgParser unrecognized");
|
||||||
|
std::string message = "unrecognized argument " + o_arg;
|
||||||
|
if (this->m->option_table != &this->m->main_option_table)
|
||||||
|
{
|
||||||
|
message += " (" + this->m->option_table_name +
|
||||||
|
" options must be terminated with --)";
|
||||||
|
}
|
||||||
|
usage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionEntry& oe = oep->second;
|
||||||
|
if ((oe.parameter_needed && (0 == parameter)) ||
|
||||||
|
((! oe.choices.empty() &&
|
||||||
|
((0 == parameter) ||
|
||||||
|
(0 == oe.choices.count(parameter))))))
|
||||||
|
{
|
||||||
|
std::string message =
|
||||||
|
"--" + arg_s + " must be given as --" + arg_s + "=";
|
||||||
|
if (! oe.choices.empty())
|
||||||
|
{
|
||||||
|
QTC::TC("libtests", "QPDFArgParser required choices");
|
||||||
|
message += "{";
|
||||||
|
for (std::set<std::string>::iterator iter =
|
||||||
|
oe.choices.begin();
|
||||||
|
iter != oe.choices.end(); ++iter)
|
||||||
|
{
|
||||||
|
if (iter != oe.choices.begin())
|
||||||
|
{
|
||||||
|
message += ",";
|
||||||
|
}
|
||||||
|
message += *iter;
|
||||||
|
}
|
||||||
|
message += "}";
|
||||||
|
}
|
||||||
|
else if (! oe.parameter_name.empty())
|
||||||
|
{
|
||||||
|
QTC::TC("libtests", "QPDFArgParser required parameter");
|
||||||
|
message += oe.parameter_name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// should not be possible
|
||||||
|
message += "option";
|
||||||
|
}
|
||||||
|
usage(message);
|
||||||
|
}
|
||||||
|
if (oe.bare_arg_handler)
|
||||||
|
{
|
||||||
|
oe.bare_arg_handler();
|
||||||
|
}
|
||||||
|
else if (oe.param_arg_handler)
|
||||||
|
{
|
||||||
|
oe.param_arg_handler(parameter);
|
||||||
|
}
|
||||||
|
if (help_option)
|
||||||
|
{
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
if (end_option)
|
||||||
|
{
|
||||||
|
selectMainOptionTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this->m->bash_completion)
|
||||||
|
{
|
||||||
|
handleCompletion();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
doFinalChecks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::doFinalChecks()
|
||||||
|
{
|
||||||
|
if (this->m->option_table != &(this->m->main_option_table))
|
||||||
|
{
|
||||||
|
QTC::TC("libtests", "QPDFArgParser missing --");
|
||||||
|
usage("missing -- at end of " + this->m->option_table_name +
|
||||||
|
" options");
|
||||||
|
}
|
||||||
|
if (this->m->final_check_handler != nullptr)
|
||||||
|
{
|
||||||
|
this->m->final_check_handler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::addChoicesToCompletions(std::string const& option,
|
||||||
|
std::string const& extra_prefix)
|
||||||
|
{
|
||||||
|
if (this->m->option_table->count(option) != 0)
|
||||||
|
{
|
||||||
|
OptionEntry& oe = (*this->m->option_table)[option];
|
||||||
|
for (std::set<std::string>::iterator iter = oe.choices.begin();
|
||||||
|
iter != oe.choices.end(); ++iter)
|
||||||
|
{
|
||||||
|
QTC::TC("libtests", "QPDFArgParser complete choices");
|
||||||
|
this->m->completions.insert(extra_prefix + *iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::addOptionsToCompletions()
|
||||||
|
{
|
||||||
|
for (std::map<std::string, OptionEntry>::iterator iter =
|
||||||
|
this->m->option_table->begin();
|
||||||
|
iter != this->m->option_table->end(); ++iter)
|
||||||
|
{
|
||||||
|
std::string const& arg = (*iter).first;
|
||||||
|
if (arg == "--")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
OptionEntry& oe = (*iter).second;
|
||||||
|
std::string base = "--" + arg;
|
||||||
|
if (oe.param_arg_handler)
|
||||||
|
{
|
||||||
|
if (this->m->zsh_completion)
|
||||||
|
{
|
||||||
|
// zsh doesn't treat = as a word separator, so add all
|
||||||
|
// the options so we don't get a space after the =.
|
||||||
|
addChoicesToCompletions(arg, base + "=");
|
||||||
|
}
|
||||||
|
this->m->completions.insert(base + "=");
|
||||||
|
}
|
||||||
|
if (! oe.parameter_needed)
|
||||||
|
{
|
||||||
|
this->m->completions.insert(base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFArgParser::handleCompletion()
|
||||||
|
{
|
||||||
|
std::string extra_prefix;
|
||||||
|
if (this->m->completions.empty())
|
||||||
|
{
|
||||||
|
// Detect --option=... Bash treats the = as a word separator.
|
||||||
|
std::string choice_option;
|
||||||
|
if (this->m->bash_cur.empty() && (this->m->bash_prev.length() > 2) &&
|
||||||
|
(this->m->bash_prev.at(0) == '-') &&
|
||||||
|
(this->m->bash_prev.at(1) == '-') &&
|
||||||
|
(this->m->bash_line.at(this->m->bash_line.length() - 1) == '='))
|
||||||
|
{
|
||||||
|
choice_option = this->m->bash_prev.substr(2, std::string::npos);
|
||||||
|
}
|
||||||
|
else if ((this->m->bash_prev == "=") &&
|
||||||
|
(this->m->bash_line.length() >
|
||||||
|
(this->m->bash_cur.length() + 1)))
|
||||||
|
{
|
||||||
|
// We're sitting at --option=x. Find previous option.
|
||||||
|
size_t end_mark = this->m->bash_line.length() -
|
||||||
|
this->m->bash_cur.length() - 1;
|
||||||
|
char before_cur = this->m->bash_line.at(end_mark);
|
||||||
|
if (before_cur == '=')
|
||||||
|
{
|
||||||
|
size_t space = this->m->bash_line.find_last_of(' ', end_mark);
|
||||||
|
if (space != std::string::npos)
|
||||||
|
{
|
||||||
|
std::string candidate =
|
||||||
|
this->m->bash_line.substr(
|
||||||
|
space + 1, end_mark - space - 1);
|
||||||
|
if ((candidate.length() > 2) &&
|
||||||
|
(candidate.at(0) == '-') &&
|
||||||
|
(candidate.at(1) == '-'))
|
||||||
|
{
|
||||||
|
choice_option =
|
||||||
|
candidate.substr(2, std::string::npos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! choice_option.empty())
|
||||||
|
{
|
||||||
|
if (this->m->zsh_completion)
|
||||||
|
{
|
||||||
|
// zsh wants --option=choice rather than just choice
|
||||||
|
extra_prefix = "--" + choice_option + "=";
|
||||||
|
}
|
||||||
|
addChoicesToCompletions(choice_option, extra_prefix);
|
||||||
|
}
|
||||||
|
else if ((! this->m->bash_cur.empty()) &&
|
||||||
|
(this->m->bash_cur.at(0) == '-'))
|
||||||
|
{
|
||||||
|
addOptionsToCompletions();
|
||||||
|
if (this->m->argc == 1)
|
||||||
|
{
|
||||||
|
// Help options are valid only by themselves.
|
||||||
|
for (std::map<std::string, OptionEntry>::iterator iter =
|
||||||
|
this->m->help_option_table.begin();
|
||||||
|
iter != this->m->help_option_table.end(); ++iter)
|
||||||
|
{
|
||||||
|
this->m->completions.insert("--" + (*iter).first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::string prefix = extra_prefix + this->m->bash_cur;
|
||||||
|
for (std::set<std::string>::iterator iter = this->m->completions.begin();
|
||||||
|
iter != this->m->completions.end(); ++iter)
|
||||||
|
{
|
||||||
|
if (prefix.empty() ||
|
||||||
|
((*iter).substr(0, prefix.length()) == prefix))
|
||||||
|
{
|
||||||
|
std::cout << *iter << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exit(0);
|
||||||
|
}
|
@ -57,6 +57,7 @@ SRCS_libqpdf = \
|
|||||||
libqpdf/QPDF.cc \
|
libqpdf/QPDF.cc \
|
||||||
libqpdf/QPDFAcroFormDocumentHelper.cc \
|
libqpdf/QPDFAcroFormDocumentHelper.cc \
|
||||||
libqpdf/QPDFAnnotationObjectHelper.cc \
|
libqpdf/QPDFAnnotationObjectHelper.cc \
|
||||||
|
libqpdf/QPDFArgParser.cc \
|
||||||
libqpdf/QPDFCryptoProvider.cc \
|
libqpdf/QPDFCryptoProvider.cc \
|
||||||
libqpdf/QPDFEFStreamObjectHelper.cc \
|
libqpdf/QPDFEFStreamObjectHelper.cc \
|
||||||
libqpdf/QPDFEmbeddedFileDocumentHelper.cc \
|
libqpdf/QPDFEmbeddedFileDocumentHelper.cc \
|
||||||
|
215
libtests/arg_parser.cc
Normal file
215
libtests/arg_parser.cc
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
#include <qpdf/QPDFArgParser.hh>
|
||||||
|
#include <qpdf/QUtil.hh>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
class ArgParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ArgParser(int argc, char* argv[]);
|
||||||
|
void parseArgs();
|
||||||
|
|
||||||
|
void test_exceptions();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void handlePotato();
|
||||||
|
void handleSalad(char* p);
|
||||||
|
void handleMoo(char* p);
|
||||||
|
void handleOink(char* p);
|
||||||
|
void handleQuack(char* p);
|
||||||
|
void startQuack();
|
||||||
|
void getQuack(char* p);
|
||||||
|
void endQuack();
|
||||||
|
void finalChecks();
|
||||||
|
|
||||||
|
void initOptions();
|
||||||
|
void output(std::string const&);
|
||||||
|
|
||||||
|
QPDFArgParser ap;
|
||||||
|
int quacks;
|
||||||
|
};
|
||||||
|
|
||||||
|
ArgParser::ArgParser(int argc, char* argv[]) :
|
||||||
|
ap(QPDFArgParser(argc, argv, "TEST_ARG_PARSER")),
|
||||||
|
quacks(0)
|
||||||
|
{
|
||||||
|
initOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ArgParser::initOptions()
|
||||||
|
{
|
||||||
|
auto b = [this](void (ArgParser::*f)()) {
|
||||||
|
return QPDFArgParser::bindBare(f, this);
|
||||||
|
};
|
||||||
|
auto p = [this](void (ArgParser::*f)(char *)) {
|
||||||
|
return QPDFArgParser::bindParam(f, this);
|
||||||
|
};
|
||||||
|
|
||||||
|
ap.addBare("potato", b(&ArgParser::handlePotato));
|
||||||
|
ap.addRequiredParameter("salad", p(&ArgParser::handleSalad), "tossed");
|
||||||
|
ap.addOptionalParameter("moo", p(&ArgParser::handleMoo));
|
||||||
|
char const* choices[] = {"pig", "boar", "sow", 0};
|
||||||
|
ap.addRequiredChoices("oink", p(&ArgParser::handleOink), choices);
|
||||||
|
ap.selectHelpOptionTable();
|
||||||
|
ap.addBare("version", [this](){ output("3.14159"); });
|
||||||
|
ap.selectMainOptionTable();
|
||||||
|
ap.addBare("quack", b(&ArgParser::startQuack));
|
||||||
|
ap.registerOptionTable("quack", b(&ArgParser::endQuack));
|
||||||
|
ap.addPositional(p(&ArgParser::getQuack));
|
||||||
|
ap.addFinalCheck(b(&ArgParser::finalChecks));
|
||||||
|
ap.selectMainOptionTable();
|
||||||
|
ap.addBare("baaa", [this](){ this->ap.selectOptionTable("baaa"); });
|
||||||
|
ap.registerOptionTable("baaa", nullptr);
|
||||||
|
ap.addBare("ewe", [this](){ output("you"); });
|
||||||
|
ap.addBare("ram", [this](){ output("ram"); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ArgParser::output(std::string const& msg)
|
||||||
|
{
|
||||||
|
if (! this->ap.isCompleting())
|
||||||
|
{
|
||||||
|
std::cout << msg << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ArgParser::handlePotato()
|
||||||
|
{
|
||||||
|
output("got potato");
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ArgParser::handleSalad(char* p)
|
||||||
|
{
|
||||||
|
output(std::string("got salad=") + p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ArgParser::handleMoo(char* p)
|
||||||
|
{
|
||||||
|
output(std::string("got moo=") + (p ? p : "(none)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ArgParser::handleOink(char* p)
|
||||||
|
{
|
||||||
|
output(std::string("got oink=") + p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ArgParser::parseArgs()
|
||||||
|
{
|
||||||
|
this->ap.parseArgs();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ArgParser::startQuack()
|
||||||
|
{
|
||||||
|
this->ap.selectOptionTable("quack");
|
||||||
|
if (this->ap.isCompleting())
|
||||||
|
{
|
||||||
|
if (this->ap.isCompleting() && (this->ap.argsLeft() == 0))
|
||||||
|
{
|
||||||
|
this->ap.insertCompletion("something");
|
||||||
|
this->ap.insertCompletion("anything");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ArgParser::getQuack(char* p)
|
||||||
|
{
|
||||||
|
++this->quacks;
|
||||||
|
if (this->ap.isCompleting() && (this->ap.argsLeft() == 0))
|
||||||
|
{
|
||||||
|
this->ap.insertCompletion(
|
||||||
|
std::string("thing-") + QUtil::int_to_string(this->quacks));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
output(std::string("got quack: ") + p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ArgParser::endQuack()
|
||||||
|
{
|
||||||
|
output("total quacks so far: " + QUtil::int_to_string(this->quacks));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ArgParser::finalChecks()
|
||||||
|
{
|
||||||
|
output("total quacks: " + QUtil::int_to_string(this->quacks));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ArgParser::test_exceptions()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ap.selectMainOptionTable();
|
||||||
|
ap.addBare("potato", [](){});
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
|
std::cout << "duplicate handler: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ap.selectOptionTable("baaa");
|
||||||
|
ap.addBare("ram", [](){});
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
|
std::cout << "duplicate handler: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ap.registerOptionTable("baaa", nullptr);
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
|
std::cout << "duplicate table: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ap.selectOptionTable("aardvark");
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
|
std::cout << "unknown table: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
|
||||||
|
ArgParser ap(argc, argv);
|
||||||
|
if ((argc == 2) && (strcmp(argv[1], "exceptions") == 0))
|
||||||
|
{
|
||||||
|
ap.test_exceptions();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ap.parseArgs();
|
||||||
|
}
|
||||||
|
catch (QPDFArgParser::Usage& e)
|
||||||
|
{
|
||||||
|
std::cerr << "usage: " << e.what() << std::endl;
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
|
std::cerr << "exception: " << e.what() << std::endl;
|
||||||
|
exit(3);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
BINS_libtests = \
|
BINS_libtests = \
|
||||||
cxx11 \
|
cxx11 \
|
||||||
aes \
|
aes \
|
||||||
|
arg_parser \
|
||||||
ascii85 \
|
ascii85 \
|
||||||
bits \
|
bits \
|
||||||
buffer \
|
buffer \
|
||||||
|
@ -39,3 +39,16 @@ JSON key missing in object 0
|
|||||||
JSON wanted array 0
|
JSON wanted array 0
|
||||||
JSON schema array error 0
|
JSON schema array error 0
|
||||||
JSON key extra in object 0
|
JSON key extra in object 0
|
||||||
|
QPDFArgParser read args from stdin 0
|
||||||
|
QPDFArgParser read args from file 0
|
||||||
|
QPDFArgParser required choices 0
|
||||||
|
QPDFArgParser required parameter 0
|
||||||
|
QPDFArgParser select unregistered table 0
|
||||||
|
QPDFArgParser register registered table 0
|
||||||
|
QPDFArgParser duplicate handler 0
|
||||||
|
QPDFArgParser missing -- 0
|
||||||
|
QPDFArgParser single dash 0
|
||||||
|
QPDFArgParser help option 0
|
||||||
|
QPDFArgParser positional 0
|
||||||
|
QPDFArgParser unrecognized 0
|
||||||
|
QPDFArgParser complete choices 0
|
||||||
|
102
libtests/qtest/arg_parser.test
Normal file
102
libtests/qtest/arg_parser.test
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
#!/usr/bin/env perl
|
||||||
|
require 5.008;
|
||||||
|
use warnings;
|
||||||
|
use strict;
|
||||||
|
|
||||||
|
chdir("arg_parser") or die "chdir testdir failed: $!\n";
|
||||||
|
unshift(@INC, '.');
|
||||||
|
require completion_helpers;
|
||||||
|
|
||||||
|
require TestDriver;
|
||||||
|
|
||||||
|
my $td = new TestDriver('arg_parser');
|
||||||
|
|
||||||
|
my @completion_tests = (
|
||||||
|
['', 0, 'bad-input-1'],
|
||||||
|
['', 1, 'bad-input-2'],
|
||||||
|
['', 2, 'bad-input-3'],
|
||||||
|
['arg_parser', 2, 'bad-input-4'],
|
||||||
|
['arg_parser ', undef, 'top'],
|
||||||
|
['arg_parser -', undef, 'top-arg'],
|
||||||
|
['arg_parser --po', undef, 'po'],
|
||||||
|
['arg_parser --potato ', undef, 'potato'],
|
||||||
|
['arg_parser --quack ', undef, 'quack'],
|
||||||
|
['arg_parser --quack -', undef, 'quack-'],
|
||||||
|
['arg_parser --quack x ', undef, 'quack-x'],
|
||||||
|
['arg_parser --quack x x ', undef, 'quack-x-x'],
|
||||||
|
['arg_parser --baaa -', undef, 'baaa'],
|
||||||
|
['arg_parser --baaa -- --', undef, 'second'],
|
||||||
|
['arg_parser @quack-xyz ', undef, 'quack-x-y-z'],
|
||||||
|
['arg_parser --quack \'user " password\' ', undef, 'quack-x'],
|
||||||
|
['arg_parser --quack \'user password\' ', undef, 'quack-x'],
|
||||||
|
['arg_parser --quack "user password" ', undef, 'quack-x'],
|
||||||
|
['arg_parser --quack "user pass\'word" ', undef, 'quack-x'],
|
||||||
|
['arg_parser --quack user\ password ', undef, 'quack-x'],
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach my $c (@completion_tests)
|
||||||
|
{
|
||||||
|
my ($cmd, $point, $description) = @$c;
|
||||||
|
my $out = "completion-$description.out";
|
||||||
|
my $zout = "completion-$description-zsh.out";
|
||||||
|
if (! -f $zout)
|
||||||
|
{
|
||||||
|
$zout = $out;
|
||||||
|
}
|
||||||
|
$td->runtest("bash completion: $description",
|
||||||
|
{$td->COMMAND =>
|
||||||
|
[@{bash_completion("arg_parser", $cmd, $point)}],
|
||||||
|
$td->FILTER => "perl filter-completion.pl $out"},
|
||||||
|
{$td->FILE => "$out", $td->EXIT_STATUS => 0},
|
||||||
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
$td->runtest("zsh completion: $description",
|
||||||
|
{$td->COMMAND =>
|
||||||
|
[@{zsh_completion("arg_parser", $cmd, $point)}],
|
||||||
|
$td->FILTER => "perl filter-completion.pl $zout"},
|
||||||
|
{$td->FILE => "$zout", $td->EXIT_STATUS => 0},
|
||||||
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
}
|
||||||
|
|
||||||
|
my @arg_tests = (
|
||||||
|
['--potato', 0], # 0
|
||||||
|
['--oops', 2], # 1
|
||||||
|
['--version', 0], # 2
|
||||||
|
['--version --potato', 2], # 3
|
||||||
|
['--potato --version', 2], # 4
|
||||||
|
['--quack', 2], # 5
|
||||||
|
['--quack --', 0], # 6
|
||||||
|
['--quack 1 2 3 --', 0], # 7
|
||||||
|
['--potato --quack 1 2 3 --' . # 8
|
||||||
|
' --potato --quack a b c --' .
|
||||||
|
' --baaa --ram --', 0],
|
||||||
|
['--baaa --potato --', 2], # 9
|
||||||
|
['--baaa --ewe', 2], # 10
|
||||||
|
['--oink=baaa', 2], # 11
|
||||||
|
['--oink=sow', 0], # 12
|
||||||
|
['-oink=sow', 0], # 13
|
||||||
|
['@quack-xyz', 2], # 14
|
||||||
|
['@quack-xyz --', 0], # 15
|
||||||
|
['--salad', 2], # 16
|
||||||
|
['--salad=spinach', 0], # 17
|
||||||
|
);
|
||||||
|
|
||||||
|
for (my $i = 0; $i < scalar(@arg_tests); ++$i)
|
||||||
|
{
|
||||||
|
my ($args, $status) = @{$arg_tests[$i]};
|
||||||
|
$td->runtest("arg_tests $i",
|
||||||
|
{$td->COMMAND => "arg_parser $args"},
|
||||||
|
{$td->FILE => "args-$i.out", $td->EXIT_STATUS => $status},
|
||||||
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
}
|
||||||
|
|
||||||
|
$td->runtest("exceptions",
|
||||||
|
{$td->COMMAND => "arg_parser exceptions"},
|
||||||
|
{$td->FILE => "exceptions.out", $td->EXIT_STATUS => 0},
|
||||||
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
|
||||||
|
$td->runtest("args from stdin",
|
||||||
|
{$td->COMMAND => 'echo --potato | arg_parser @-'},
|
||||||
|
{$td->FILE => "stdin.out", $td->EXIT_STATUS => 0},
|
||||||
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
|
||||||
|
$td->report(2 + (2 * scalar(@completion_tests)) + scalar(@arg_tests));
|
2
libtests/qtest/arg_parser/args-0.out
Normal file
2
libtests/qtest/arg_parser/args-0.out
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
got potato
|
||||||
|
total quacks: 0
|
1
libtests/qtest/arg_parser/args-1.out
Normal file
1
libtests/qtest/arg_parser/args-1.out
Normal file
@ -0,0 +1 @@
|
|||||||
|
usage: unrecognized argument --oops
|
2
libtests/qtest/arg_parser/args-10.out
Normal file
2
libtests/qtest/arg_parser/args-10.out
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
you
|
||||||
|
usage: missing -- at end of baaa options
|
1
libtests/qtest/arg_parser/args-11.out
Normal file
1
libtests/qtest/arg_parser/args-11.out
Normal file
@ -0,0 +1 @@
|
|||||||
|
usage: --oink must be given as --oink={boar,pig,sow}
|
2
libtests/qtest/arg_parser/args-12.out
Normal file
2
libtests/qtest/arg_parser/args-12.out
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
got oink=sow
|
||||||
|
total quacks: 0
|
2
libtests/qtest/arg_parser/args-13.out
Normal file
2
libtests/qtest/arg_parser/args-13.out
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
got oink=sow
|
||||||
|
total quacks: 0
|
7
libtests/qtest/arg_parser/args-14.out
Normal file
7
libtests/qtest/arg_parser/args-14.out
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
got potato
|
||||||
|
got potato
|
||||||
|
got quack: x
|
||||||
|
total quacks so far: 1
|
||||||
|
got quack: y
|
||||||
|
got quack: z
|
||||||
|
usage: missing -- at end of quack options
|
8
libtests/qtest/arg_parser/args-15.out
Normal file
8
libtests/qtest/arg_parser/args-15.out
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
got potato
|
||||||
|
got potato
|
||||||
|
got quack: x
|
||||||
|
total quacks so far: 1
|
||||||
|
got quack: y
|
||||||
|
got quack: z
|
||||||
|
total quacks so far: 3
|
||||||
|
total quacks: 3
|
1
libtests/qtest/arg_parser/args-16.out
Normal file
1
libtests/qtest/arg_parser/args-16.out
Normal file
@ -0,0 +1 @@
|
|||||||
|
usage: --salad must be given as --salad=tossed
|
2
libtests/qtest/arg_parser/args-17.out
Normal file
2
libtests/qtest/arg_parser/args-17.out
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
got salad=spinach
|
||||||
|
total quacks: 0
|
1
libtests/qtest/arg_parser/args-2.out
Normal file
1
libtests/qtest/arg_parser/args-2.out
Normal file
@ -0,0 +1 @@
|
|||||||
|
3.14159
|
1
libtests/qtest/arg_parser/args-3.out
Normal file
1
libtests/qtest/arg_parser/args-3.out
Normal file
@ -0,0 +1 @@
|
|||||||
|
usage: unrecognized argument --version
|
2
libtests/qtest/arg_parser/args-4.out
Normal file
2
libtests/qtest/arg_parser/args-4.out
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
got potato
|
||||||
|
usage: unrecognized argument --version
|
1
libtests/qtest/arg_parser/args-5.out
Normal file
1
libtests/qtest/arg_parser/args-5.out
Normal file
@ -0,0 +1 @@
|
|||||||
|
usage: missing -- at end of quack options
|
2
libtests/qtest/arg_parser/args-6.out
Normal file
2
libtests/qtest/arg_parser/args-6.out
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
total quacks so far: 0
|
||||||
|
total quacks: 0
|
5
libtests/qtest/arg_parser/args-7.out
Normal file
5
libtests/qtest/arg_parser/args-7.out
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
got quack: 1
|
||||||
|
got quack: 2
|
||||||
|
got quack: 3
|
||||||
|
total quacks so far: 3
|
||||||
|
total quacks: 3
|
12
libtests/qtest/arg_parser/args-8.out
Normal file
12
libtests/qtest/arg_parser/args-8.out
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
got potato
|
||||||
|
got quack: 1
|
||||||
|
got quack: 2
|
||||||
|
got quack: 3
|
||||||
|
total quacks so far: 3
|
||||||
|
got potato
|
||||||
|
got quack: a
|
||||||
|
got quack: b
|
||||||
|
got quack: c
|
||||||
|
total quacks so far: 6
|
||||||
|
ram
|
||||||
|
total quacks: 6
|
1
libtests/qtest/arg_parser/args-9.out
Normal file
1
libtests/qtest/arg_parser/args-9.out
Normal file
@ -0,0 +1 @@
|
|||||||
|
usage: unrecognized argument --potato (baaa options must be terminated with --)
|
3
libtests/qtest/arg_parser/completion-baaa.out
Normal file
3
libtests/qtest/arg_parser/completion-baaa.out
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
--ewe
|
||||||
|
--ram
|
||||||
|
!--potato
|
1
libtests/qtest/arg_parser/completion-bad-input-1.out
Normal file
1
libtests/qtest/arg_parser/completion-bad-input-1.out
Normal file
@ -0,0 +1 @@
|
|||||||
|
!
|
1
libtests/qtest/arg_parser/completion-bad-input-2.out
Normal file
1
libtests/qtest/arg_parser/completion-bad-input-2.out
Normal file
@ -0,0 +1 @@
|
|||||||
|
!
|
1
libtests/qtest/arg_parser/completion-bad-input-3.out
Normal file
1
libtests/qtest/arg_parser/completion-bad-input-3.out
Normal file
@ -0,0 +1 @@
|
|||||||
|
!
|
1
libtests/qtest/arg_parser/completion-bad-input-4.out
Normal file
1
libtests/qtest/arg_parser/completion-bad-input-4.out
Normal file
@ -0,0 +1 @@
|
|||||||
|
!
|
1
libtests/qtest/arg_parser/completion-po.out
Normal file
1
libtests/qtest/arg_parser/completion-po.out
Normal file
@ -0,0 +1 @@
|
|||||||
|
--potato
|
2
libtests/qtest/arg_parser/completion-potato.out
Normal file
2
libtests/qtest/arg_parser/completion-potato.out
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
!got
|
||||||
|
!potato
|
1
libtests/qtest/arg_parser/completion-quack-.out
Normal file
1
libtests/qtest/arg_parser/completion-quack-.out
Normal file
@ -0,0 +1 @@
|
|||||||
|
!--
|
4
libtests/qtest/arg_parser/completion-quack-x-x.out
Normal file
4
libtests/qtest/arg_parser/completion-quack-x-x.out
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
thing-2
|
||||||
|
!anything
|
||||||
|
!something
|
||||||
|
!thing-1
|
2
libtests/qtest/arg_parser/completion-quack-x-y-z.out
Normal file
2
libtests/qtest/arg_parser/completion-quack-x-y-z.out
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
thing-3
|
||||||
|
!thing-2
|
4
libtests/qtest/arg_parser/completion-quack-x.out
Normal file
4
libtests/qtest/arg_parser/completion-quack-x.out
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
thing-1
|
||||||
|
!anything
|
||||||
|
!something
|
||||||
|
!thing-2
|
4
libtests/qtest/arg_parser/completion-quack.out
Normal file
4
libtests/qtest/arg_parser/completion-quack.out
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
anything
|
||||||
|
something
|
||||||
|
!thing-0
|
||||||
|
!thing-1
|
11
libtests/qtest/arg_parser/completion-second-zsh.out
Normal file
11
libtests/qtest/arg_parser/completion-second-zsh.out
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
--baaa
|
||||||
|
--moo
|
||||||
|
--moo=
|
||||||
|
--oink=
|
||||||
|
--oink=pig
|
||||||
|
--potato
|
||||||
|
--salad=
|
||||||
|
!--completion-zsh
|
||||||
|
!--ewe
|
||||||
|
!--ram
|
||||||
|
!--version
|
11
libtests/qtest/arg_parser/completion-second.out
Normal file
11
libtests/qtest/arg_parser/completion-second.out
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
--baaa
|
||||||
|
--moo
|
||||||
|
--moo=
|
||||||
|
--oink=
|
||||||
|
--potato
|
||||||
|
--salad=
|
||||||
|
!--completion-zsh
|
||||||
|
!--ewe
|
||||||
|
!--oink=pig
|
||||||
|
!--ram
|
||||||
|
!--version
|
11
libtests/qtest/arg_parser/completion-top-arg-zsh.out
Normal file
11
libtests/qtest/arg_parser/completion-top-arg-zsh.out
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
--baaa
|
||||||
|
--completion-zsh
|
||||||
|
--moo
|
||||||
|
--moo=
|
||||||
|
--oink=
|
||||||
|
--oink=pig
|
||||||
|
--potato
|
||||||
|
--salad=
|
||||||
|
--version
|
||||||
|
!--ewe
|
||||||
|
!--ram
|
11
libtests/qtest/arg_parser/completion-top-arg.out
Normal file
11
libtests/qtest/arg_parser/completion-top-arg.out
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
--baaa
|
||||||
|
--completion-zsh
|
||||||
|
--moo
|
||||||
|
--moo=
|
||||||
|
--oink=
|
||||||
|
--potato
|
||||||
|
--salad=
|
||||||
|
--version
|
||||||
|
!--ewe
|
||||||
|
!--oink=pig
|
||||||
|
!--ram
|
4
libtests/qtest/arg_parser/completion-top.out
Normal file
4
libtests/qtest/arg_parser/completion-top.out
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
!--completion-zsh
|
||||||
|
!--potato
|
||||||
|
!--salad=tossed
|
||||||
|
!--version
|
4
libtests/qtest/arg_parser/exceptions.out
Normal file
4
libtests/qtest/arg_parser/exceptions.out
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
duplicate handler: QPDFArgParser: adding a duplicate handler for option potato in main option table
|
||||||
|
duplicate handler: QPDFArgParser: adding a duplicate handler for option ram in baaa option table
|
||||||
|
duplicate table: QPDFArgParser: registering already registered option table baaa
|
||||||
|
unknown table: QPDFArgParser: selecting unregistered option table aardvark
|
8
libtests/qtest/arg_parser/quack-xyz
Normal file
8
libtests/qtest/arg_parser/quack-xyz
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
--potato
|
||||||
|
--potato
|
||||||
|
--quack
|
||||||
|
x
|
||||||
|
--
|
||||||
|
--quack
|
||||||
|
y
|
||||||
|
z
|
2
libtests/qtest/arg_parser/stdin.out
Normal file
2
libtests/qtest/arg_parser/stdin.out
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
got potato
|
||||||
|
total quacks: 0
|
@ -88,6 +88,11 @@ $td->runtest("UTF-16 encoding errors",
|
|||||||
{$td->FILE => "unicode-errors.out", $td->EXIT_STATUS => 0},
|
{$td->FILE => "unicode-errors.out", $td->EXIT_STATUS => 0},
|
||||||
$td->NORMALIZE_NEWLINES);
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
|
||||||
|
# Tests to exercise QPDFArgParser belong in arg_parser.test in
|
||||||
|
# libtests. These tests are supposed to be specific to the qpdf cli.
|
||||||
|
# Since they were written prior to moving QPDFArgParser into the
|
||||||
|
# library, there are several tests here that also exercise
|
||||||
|
# QPDFArgParser logic.
|
||||||
my @completion_tests = (
|
my @completion_tests = (
|
||||||
['', 0, 'bad-input-1'],
|
['', 0, 'bad-input-1'],
|
||||||
['', 1, 'bad-input-2'],
|
['', 1, 'bad-input-2'],
|
||||||
|
Loading…
Reference in New Issue
Block a user