mirror of
https://github.com/qpdf/qpdf.git
synced 2025-01-02 22:50:20 +00:00
Support bash completion using complete -C
This commit is contained in:
parent
3c075fc017
commit
dd1aca552c
@ -1,3 +1,8 @@
|
||||
2018-12-21 Jay Berkenbilt <ejb@ql.org>
|
||||
|
||||
* You can now use eval $(qpdf --completion-bash) to enable bash
|
||||
completion for qpdf. It's not perfect, but it works pretty well.
|
||||
|
||||
2018-12-19 Jay Berkenbilt <ejb@ql.org>
|
||||
|
||||
* When splitting pages using --split-pages, the outlines
|
||||
|
311
qpdf/qpdf.cc
311
qpdf/qpdf.cc
@ -322,7 +322,7 @@ class ArgParser
|
||||
void argShowLinearization();
|
||||
void argShowXref();
|
||||
void argShowObject(char* parameter);
|
||||
void argShowObject();
|
||||
void argRawStreamData();
|
||||
void argFilteredStreamData();
|
||||
void argShowNpages();
|
||||
void argShowPages();
|
||||
@ -344,10 +344,16 @@ class ArgParser
|
||||
void argEndEncrypt();
|
||||
|
||||
void usage(std::string const& message);
|
||||
void checkCompletion();
|
||||
void initOptionTable();
|
||||
void handleHelpVersion();
|
||||
void handleHelpArgs();
|
||||
void handleArgFileArguments();
|
||||
void handleBashArguments();
|
||||
void readArgsFromFile(char const* filename);
|
||||
void doFinalChecks();
|
||||
void addOptionsToCompletions();
|
||||
void addChoicesToCompletions(std::string const&);
|
||||
void handleCompletion();
|
||||
std::vector<PageSpec> parsePagesOptions();
|
||||
void parseRotationParameter(std::string const&);
|
||||
std::vector<int> parseNumrange(char const* range, int max,
|
||||
@ -359,6 +365,12 @@ class ArgParser
|
||||
char** argv;
|
||||
Options& o;
|
||||
int cur_arg;
|
||||
bool bash_completion;
|
||||
std::string bash_prev;
|
||||
std::string bash_cur;
|
||||
std::string bash_line;
|
||||
size_t bash_point;
|
||||
std::set<std::string> completions;
|
||||
|
||||
std::map<std::string, OptionEntry>* option_table;
|
||||
std::map<std::string, OptionEntry> main_option_table;
|
||||
@ -366,14 +378,18 @@ class ArgParser
|
||||
std::map<std::string, OptionEntry> encrypt128_option_table;
|
||||
std::map<std::string, OptionEntry> encrypt256_option_table;
|
||||
std::vector<PointerHolder<char> > new_argv;
|
||||
std::vector<PointerHolder<char> > bash_argv;
|
||||
PointerHolder<char*> argv_ph;
|
||||
PointerHolder<char*> bash_argv_ph;
|
||||
};
|
||||
|
||||
ArgParser::ArgParser(int argc, char* argv[], Options& o) :
|
||||
argc(argc),
|
||||
argv(argv),
|
||||
o(o),
|
||||
cur_arg(0)
|
||||
cur_arg(0),
|
||||
bash_completion(false),
|
||||
bash_point(0)
|
||||
{
|
||||
option_table = &main_option_table;
|
||||
initOptionTable();
|
||||
@ -496,7 +512,7 @@ ArgParser::initOptionTable()
|
||||
(*t)["show-xref"] = oe_bare(&ArgParser::argShowXref);
|
||||
(*t)["show-object"] = oe_requiredParameter(
|
||||
&ArgParser::argShowObject, "obj[,gen]");
|
||||
(*t)["raw-stream-data"] = oe_bare(&ArgParser::argShowObject);
|
||||
(*t)["raw-stream-data"] = oe_bare(&ArgParser::argRawStreamData);
|
||||
(*t)["filtered-stream-data"] = oe_bare(&ArgParser::argFilteredStreamData);
|
||||
(*t)["show-npages"] = oe_bare(&ArgParser::argShowNpages);
|
||||
(*t)["show-pages"] = oe_bare(&ArgParser::argShowPages);
|
||||
@ -573,10 +589,31 @@ void
|
||||
ArgParser::argEncrypt()
|
||||
{
|
||||
++cur_arg;
|
||||
if (cur_arg + 3 >= argc)
|
||||
if (cur_arg + 3 > argc)
|
||||
{
|
||||
if (this->bash_completion)
|
||||
{
|
||||
if (cur_arg == argc)
|
||||
{
|
||||
this->completions.insert("user-password");
|
||||
}
|
||||
else if (cur_arg + 1 == argc)
|
||||
{
|
||||
this->completions.insert("owner-password");
|
||||
}
|
||||
else if (cur_arg + 2 == argc)
|
||||
{
|
||||
this->completions.insert("40");
|
||||
this->completions.insert("128");
|
||||
this->completions.insert("256");
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
usage("insufficient arguments to --encrypt");
|
||||
}
|
||||
}
|
||||
o.user_password = argv[cur_arg++];
|
||||
o.owner_password = argv[cur_arg++];
|
||||
std::string len_str = argv[cur_arg];
|
||||
@ -904,7 +941,7 @@ ArgParser::argShowObject(char* parameter)
|
||||
}
|
||||
|
||||
void
|
||||
ArgParser::argShowObject()
|
||||
ArgParser::argRawStreamData()
|
||||
{
|
||||
o.show_raw_stream_data = true;
|
||||
}
|
||||
@ -1098,14 +1135,55 @@ ArgParser::handleArgFileArguments()
|
||||
argv[argc] = 0;
|
||||
}
|
||||
|
||||
// Note: let's not be too noisy about documenting the fact that this
|
||||
// software purposely fails to enforce the distinction between user
|
||||
// and owner passwords. A user password is sufficient to gain full
|
||||
// access to the PDF file, so there is nothing this software can do
|
||||
// with an owner password that it couldn't do with a user password
|
||||
// other than changing the /P value in the encryption dictionary.
|
||||
// (Setting this value requires the owner password.) The
|
||||
// documentation discusses this as well.
|
||||
void
|
||||
ArgParser::handleBashArguments()
|
||||
{
|
||||
// Do a minimal job of parsing bash_line into arguments. This
|
||||
// doesn't do everything the shell does, but it should be good
|
||||
// enough for purposes of handling completion. We can't use
|
||||
// new_argv because this has to interoperate with @file arguments.
|
||||
|
||||
enum { st_top, st_quote } state = st_top;
|
||||
std::string arg;
|
||||
for (std::string::iterator iter = bash_line.begin();
|
||||
iter != bash_line.end(); ++iter)
|
||||
{
|
||||
char ch = (*iter);
|
||||
if ((state == st_top) && QUtil::is_space(ch) && (! arg.empty()))
|
||||
{
|
||||
bash_argv.push_back(
|
||||
PointerHolder<char>(
|
||||
true, QUtil::copy_string(arg.c_str())));
|
||||
arg.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ch == '"')
|
||||
{
|
||||
state = (state == st_top ? st_quote : st_top);
|
||||
}
|
||||
arg.append(1, ch);
|
||||
}
|
||||
}
|
||||
if (bash_argv.empty())
|
||||
{
|
||||
// This can't happen if properly invoked by bash, but ensure
|
||||
// we have a valid argv[0] regardless.
|
||||
bash_argv.push_back(
|
||||
PointerHolder<char>(
|
||||
true, QUtil::copy_string(argv[0])));
|
||||
}
|
||||
// Explicitly discard any non-space-terminated word. The "current
|
||||
// word" is handled specially.
|
||||
bash_argv_ph = PointerHolder<char*>(true, new char*[1+bash_argv.size()]);
|
||||
argv = bash_argv_ph.getPointer();
|
||||
for (size_t i = 0; i < bash_argv.size(); ++i)
|
||||
{
|
||||
argv[i] = bash_argv.at(i).getPointer();
|
||||
}
|
||||
argc = static_cast<int>(bash_argv.size());
|
||||
argv[argc] = 0;
|
||||
}
|
||||
|
||||
char const* ArgParser::help = "\
|
||||
\n\
|
||||
@ -1127,6 +1205,7 @@ Basic Options\n\
|
||||
--version show version of qpdf\n\
|
||||
--copyright show qpdf's copyright and license information\n\
|
||||
--help show command-line argument help\n\
|
||||
--completion-bash output a bash complete command you can eval\n\
|
||||
--password=password specify a password for accessing encrypted files\n\
|
||||
--verbose provide additional informational output\n\
|
||||
--progress give progress indicators while writing output\n\
|
||||
@ -1412,7 +1491,15 @@ void usageExit(std::string const& msg)
|
||||
void
|
||||
ArgParser::usage(std::string const& message)
|
||||
{
|
||||
if (this->bash_completion)
|
||||
{
|
||||
// This will cause bash to fall back to regular file completion.
|
||||
exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
usageExit(message);
|
||||
}
|
||||
}
|
||||
|
||||
static JSON json_schema()
|
||||
@ -1718,13 +1805,33 @@ ArgParser::readArgsFromFile(char const* filename)
|
||||
}
|
||||
|
||||
void
|
||||
ArgParser::handleHelpVersion()
|
||||
ArgParser::handleHelpArgs()
|
||||
{
|
||||
// Make sure the output looks right on an 80-column display.
|
||||
// Handle special-case informational options that are only
|
||||
// available as the sole option.
|
||||
|
||||
if ((argc == 2) &&
|
||||
((strcmp(argv[1], "--version") == 0) ||
|
||||
(strcmp(argv[1], "-version") == 0)))
|
||||
// The options processed here are also handled as a special case
|
||||
// in handleCompletion.
|
||||
|
||||
if (argc != 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
char* arg = argv[1];
|
||||
if (*arg != '-')
|
||||
{
|
||||
return;
|
||||
}
|
||||
++arg;
|
||||
if (*arg == '-')
|
||||
{
|
||||
++arg;
|
||||
}
|
||||
if (! *arg)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (strcmp(arg, "version") == 0)
|
||||
{
|
||||
std::cout
|
||||
<< whoami << " version " << QPDF::QPDFVersion() << std::endl
|
||||
@ -1733,10 +1840,9 @@ ArgParser::handleHelpVersion()
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if ((argc == 2) &&
|
||||
((strcmp(argv[1], "--copyright") == 0) ||
|
||||
(strcmp(argv[1], "-copyright") == 0)))
|
||||
if (strcmp(arg, "copyright") == 0)
|
||||
{
|
||||
// Make sure the output looks right on an 80-column display.
|
||||
// 1 2 3 4 5 6 7 8
|
||||
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
std::cout
|
||||
@ -1776,13 +1882,25 @@ ArgParser::handleHelpVersion()
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if ((argc == 2) &&
|
||||
((strcmp(argv[1], "--help") == 0) ||
|
||||
(strcmp(argv[1], "-help") == 0)))
|
||||
if (strcmp(arg, "help") == 0)
|
||||
{
|
||||
std::cout << help;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (strcmp(arg, "completion-bash") == 0)
|
||||
{
|
||||
std::string path = argv[0];
|
||||
size_t slash = path.find('/');
|
||||
if ((slash != 0) && (slash != std::string::npos))
|
||||
{
|
||||
std::cerr << "WARNING: qpdf completion enabled"
|
||||
<< " using relative path to qpdf" << std::endl;
|
||||
}
|
||||
std::cout << "complete -o bashdefault -o default -o nospace"
|
||||
<< " -C " << argv[0] << " " << whoami << std::endl;
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -1850,10 +1968,38 @@ ArgParser::parseRotationParameter(std::string const& parameter)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArgParser::checkCompletion()
|
||||
{
|
||||
// See if we're being invoked from bash completion.
|
||||
std::string bash_point_env;
|
||||
if (QUtil::get_env("COMP_LINE", &bash_line) &&
|
||||
QUtil::get_env("COMP_POINT", &bash_point_env))
|
||||
{
|
||||
int p = QUtil::string_to_int(bash_point_env.c_str());
|
||||
if ((p > 0) && (p <= static_cast<int>(bash_line.length())))
|
||||
{
|
||||
// Point to the last character
|
||||
bash_point = static_cast<size_t>(p) - 1;
|
||||
}
|
||||
if (argc >= 4)
|
||||
{
|
||||
bash_cur = argv[2];
|
||||
bash_prev = argv[3];
|
||||
handleBashArguments();
|
||||
bash_completion = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArgParser::parseOptions()
|
||||
{
|
||||
handleHelpVersion(); // QXXXQ calls std::cout
|
||||
checkCompletion();
|
||||
if (! this->bash_completion)
|
||||
{
|
||||
handleHelpArgs();
|
||||
}
|
||||
handleArgFileArguments();
|
||||
for (cur_arg = 1; cur_arg < argc; ++cur_arg)
|
||||
{
|
||||
@ -1957,7 +2103,19 @@ ArgParser::parseOptions()
|
||||
usage(std::string("unknown argument ") + arg);
|
||||
}
|
||||
}
|
||||
if (this->bash_completion)
|
||||
{
|
||||
handleCompletion();
|
||||
}
|
||||
else
|
||||
{
|
||||
doFinalChecks();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArgParser::doFinalChecks()
|
||||
{
|
||||
if (this->option_table != &(this->main_option_table))
|
||||
{
|
||||
usage("missing -- at end of options");
|
||||
@ -2002,6 +2160,107 @@ ArgParser::parseOptions()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArgParser::addChoicesToCompletions(std::string const& option)
|
||||
{
|
||||
if (this->option_table->count(option) != 0)
|
||||
{
|
||||
OptionEntry& oe = (*this->option_table)[option];
|
||||
for (std::set<std::string>::iterator iter = oe.choices.begin();
|
||||
iter != oe.choices.end(); ++iter)
|
||||
{
|
||||
completions.insert(*iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArgParser::addOptionsToCompletions()
|
||||
{
|
||||
for (std::map<std::string, OptionEntry>::iterator iter =
|
||||
this->option_table->begin();
|
||||
iter != this->option_table->end(); ++iter)
|
||||
{
|
||||
std::string const& arg = (*iter).first;
|
||||
OptionEntry& oe = (*iter).second;
|
||||
std::string base = "--" + arg;
|
||||
if (oe.param_arg_handler)
|
||||
{
|
||||
completions.insert(base + "=");
|
||||
}
|
||||
if (! oe.parameter_needed)
|
||||
{
|
||||
completions.insert(base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArgParser::handleCompletion()
|
||||
{
|
||||
if (this->completions.empty())
|
||||
{
|
||||
// Detect --option=... Bash treats the = as a word separator.
|
||||
std::string choice_option;
|
||||
if (bash_cur.empty() && (bash_prev.length() > 2) &&
|
||||
(bash_prev.at(0) == '-') &&
|
||||
(bash_prev.at(1) == '-') &&
|
||||
(bash_line.at(bash_point) == '='))
|
||||
{
|
||||
choice_option = bash_prev.substr(2, std::string::npos);
|
||||
}
|
||||
else if ((bash_prev == "=") &&
|
||||
(bash_line.length() > (bash_cur.length() + 1)))
|
||||
{
|
||||
// We're sitting at --option=x. Find previous option.
|
||||
size_t end_mark = bash_line.length() - bash_cur.length() - 1;
|
||||
char before_cur = bash_line.at(end_mark);
|
||||
if (before_cur == '=')
|
||||
{
|
||||
size_t space = bash_line.find_last_of(' ', end_mark);
|
||||
if (space != std::string::npos)
|
||||
{
|
||||
std::string candidate =
|
||||
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())
|
||||
{
|
||||
addChoicesToCompletions(choice_option);
|
||||
}
|
||||
else if ((! bash_cur.empty()) && (bash_cur.at(0) == '-'))
|
||||
{
|
||||
addOptionsToCompletions();
|
||||
if (this->argc == 1)
|
||||
{
|
||||
// Handle options usually handled by handleHelpArgs.
|
||||
this->completions.insert("--help");
|
||||
this->completions.insert("--version");
|
||||
this->completions.insert("--copyright");
|
||||
this->completions.insert("--completion-bash");
|
||||
}
|
||||
}
|
||||
}
|
||||
for (std::set<std::string>::iterator iter = completions.begin();
|
||||
iter != completions.end(); ++iter)
|
||||
{
|
||||
if (this->bash_cur.empty() ||
|
||||
((*iter).substr(0, bash_cur.length()) == bash_cur))
|
||||
{
|
||||
std::cout << *iter << std::endl;
|
||||
}
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static void set_qpdf_options(QPDF& pdf, Options& o)
|
||||
{
|
||||
if (o.ignore_xref_streams)
|
||||
|
@ -100,6 +100,36 @@ $td->runtest("UTF-16 encoding errors",
|
||||
{$td->FILE => "unicode-errors.out", $td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
|
||||
my @completion_tests = (
|
||||
['qpdf ', undef, 'top'],
|
||||
['qpdf -', undef, 'top-arg'],
|
||||
['qpdf --enc', undef, 'enc'],
|
||||
['qpdf --encrypt ', undef, 'encrypt'],
|
||||
['qpdf --encrypt u ', undef, 'encrypt-u'],
|
||||
['qpdf --encrypt u o ', undef, 'encrypt-u-o'],
|
||||
['qpdf @encrypt-u o ', undef, 'encrypt-u-o'],
|
||||
['qpdf --encrypt u o 40 --', undef, 'encrypt-40'],
|
||||
['qpdf --encrypt u o 128 --', undef, 'encrypt-128'],
|
||||
['qpdf --encrypt u o 256 --', undef, 'encrypt-256'],
|
||||
['qpdf --encrypt u o bad --', undef, 'encrypt-bad'],
|
||||
['qpdf --split-pag', undef, 'split'],
|
||||
['qpdf --decode-l', undef, 'decode-l'],
|
||||
['qpdf --decode-lzzz', 15, 'decode-l'],
|
||||
['qpdf --decode-level=', undef, 'decode-level'],
|
||||
['qpdf --check -', undef, 'later-arg'],
|
||||
);
|
||||
$n_tests += scalar(@completion_tests);
|
||||
foreach my $c (@completion_tests)
|
||||
{
|
||||
my ($cmd, $point, $description) = @$c;
|
||||
my $out = "completion-$description.out";
|
||||
$td->runtest("bash completion: $description",
|
||||
{$td->COMMAND => [@{bash_completion($cmd, $point)}],
|
||||
$td->FILTER => "perl filter-completion.pl $out"},
|
||||
{$td->FILE => "$out", $td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
}
|
||||
|
||||
show_ntests();
|
||||
# ----------
|
||||
$td->notify("--- Argument Parsing ---");
|
||||
@ -3144,6 +3174,24 @@ sub show_ntests
|
||||
}
|
||||
}
|
||||
|
||||
sub bash_completion
|
||||
{
|
||||
my ($line, $point) = @_;
|
||||
if (! defined $point)
|
||||
{
|
||||
$point = length($line);
|
||||
}
|
||||
my $before_point = substr($line, 0, $point);
|
||||
$before_point =~ m/^(.*)([ =])([^= ]*)$/ or die;
|
||||
my ($first, $sep, $cur) = ($1, $2, $3);
|
||||
my $prev = ($sep eq '=' ? $sep : $first);
|
||||
$prev =~ s/.* (\S+)$/$1/;
|
||||
my $this = $first;
|
||||
$this =~ s/(\S+)\s.*/$1/;
|
||||
['env', "COMP_LINE=$line", "COMP_POINT=$point",
|
||||
"qpdf", $this, $cur, $prev];
|
||||
}
|
||||
|
||||
sub check_pdf
|
||||
{
|
||||
my ($description, $command, $output, $status) = @_;
|
||||
|
2
qpdf/qtest/qpdf/completion-decode-l.out
Normal file
2
qpdf/qtest/qpdf/completion-decode-l.out
Normal file
@ -0,0 +1,2 @@
|
||||
--decode-level=
|
||||
!--help
|
4
qpdf/qtest/qpdf/completion-decode-level.out
Normal file
4
qpdf/qtest/qpdf/completion-decode-level.out
Normal file
@ -0,0 +1,4 @@
|
||||
all
|
||||
generalized
|
||||
none
|
||||
!--help
|
1
qpdf/qtest/qpdf/completion-enc.out
Normal file
1
qpdf/qtest/qpdf/completion-enc.out
Normal file
@ -0,0 +1 @@
|
||||
--encrypt
|
3
qpdf/qtest/qpdf/completion-encrypt-128.out
Normal file
3
qpdf/qtest/qpdf/completion-encrypt-128.out
Normal file
@ -0,0 +1,3 @@
|
||||
--force-V4
|
||||
!--annotate=
|
||||
!--force-R5
|
3
qpdf/qtest/qpdf/completion-encrypt-256.out
Normal file
3
qpdf/qtest/qpdf/completion-encrypt-256.out
Normal file
@ -0,0 +1,3 @@
|
||||
--force-R5
|
||||
!--annotate=
|
||||
!--force-V4
|
3
qpdf/qtest/qpdf/completion-encrypt-40.out
Normal file
3
qpdf/qtest/qpdf/completion-encrypt-40.out
Normal file
@ -0,0 +1,3 @@
|
||||
--annotate=
|
||||
!--force-R5
|
||||
!--force-V4
|
2
qpdf/qtest/qpdf/completion-encrypt-bad.out
Normal file
2
qpdf/qtest/qpdf/completion-encrypt-bad.out
Normal file
@ -0,0 +1,2 @@
|
||||
!--help
|
||||
!--print
|
3
qpdf/qtest/qpdf/completion-encrypt-u-o.out
Normal file
3
qpdf/qtest/qpdf/completion-encrypt-u-o.out
Normal file
@ -0,0 +1,3 @@
|
||||
128
|
||||
256
|
||||
40
|
1
qpdf/qtest/qpdf/completion-encrypt-u.out
Normal file
1
qpdf/qtest/qpdf/completion-encrypt-u.out
Normal file
@ -0,0 +1 @@
|
||||
owner-password
|
2
qpdf/qtest/qpdf/completion-encrypt.out
Normal file
2
qpdf/qtest/qpdf/completion-encrypt.out
Normal file
@ -0,0 +1,2 @@
|
||||
user-password
|
||||
!--print
|
7
qpdf/qtest/qpdf/completion-later-arg.out
Normal file
7
qpdf/qtest/qpdf/completion-later-arg.out
Normal file
@ -0,0 +1,7 @@
|
||||
--check
|
||||
--decode-level=
|
||||
--encrypt
|
||||
!--completion-bash
|
||||
!--copyright
|
||||
!--help
|
||||
!--version
|
2
qpdf/qtest/qpdf/completion-split.out
Normal file
2
qpdf/qtest/qpdf/completion-split.out
Normal file
@ -0,0 +1,2 @@
|
||||
--split-pages
|
||||
--split-pages=
|
7
qpdf/qtest/qpdf/completion-top-arg.out
Normal file
7
qpdf/qtest/qpdf/completion-top-arg.out
Normal file
@ -0,0 +1,7 @@
|
||||
--check
|
||||
--completion-bash
|
||||
--copyright
|
||||
--decode-level=
|
||||
--encrypt
|
||||
--help
|
||||
--version
|
3
qpdf/qtest/qpdf/completion-top.out
Normal file
3
qpdf/qtest/qpdf/completion-top.out
Normal file
@ -0,0 +1,3 @@
|
||||
!--copyright
|
||||
!--help
|
||||
!--version
|
2
qpdf/qtest/qpdf/encrypt-u
Normal file
2
qpdf/qtest/qpdf/encrypt-u
Normal file
@ -0,0 +1,2 @@
|
||||
--encrypt
|
||||
u
|
39
qpdf/qtest/qpdf/filter-completion.pl
Normal file
39
qpdf/qtest/qpdf/filter-completion.pl
Normal file
@ -0,0 +1,39 @@
|
||||
use warnings;
|
||||
use strict;
|
||||
|
||||
# Output every line from STDIN that appears in the file.
|
||||
my %wanted = ();
|
||||
my %notwanted = ();
|
||||
my $f = $ARGV[0];
|
||||
if (open(F, "<$f"))
|
||||
{
|
||||
while (<F>)
|
||||
{
|
||||
chomp;
|
||||
if (s/^!//)
|
||||
{
|
||||
$notwanted{$_} = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
$wanted{$_} = 1;
|
||||
}
|
||||
}
|
||||
close(F);
|
||||
}
|
||||
while (<STDIN>)
|
||||
{
|
||||
chomp;
|
||||
if (exists $wanted{$_})
|
||||
{
|
||||
print $_, "\n";
|
||||
}
|
||||
elsif (exists $notwanted{$_})
|
||||
{
|
||||
delete $notwanted{$_};
|
||||
}
|
||||
}
|
||||
foreach my $k (sort keys %notwanted)
|
||||
{
|
||||
print "!$k\n";
|
||||
}
|
Loading…
Reference in New Issue
Block a user