Allow reading command-line args from files (fixes #16)

This commit is contained in:
Jay Berkenbilt 2017-07-29 22:23:21 -04:00
parent 5993c3e83c
commit 2d5b854468
10 changed files with 152 additions and 3 deletions

View File

@ -1,5 +1,9 @@
2017-07-29 Jay Berkenbilt <ejb@ql.org>
* Support @filename and @- in the qpdf command-line tool to read
command-line arguments, one per line, from the named file. @-
reads from standard input. Fixes #16.
* Detect when input file and output file are the same and exit to
avoid overwriting and losing input file. Fixes #29.

View File

@ -161,6 +161,11 @@ namespace QUtil
QPDF_DLL
RandomDataProvider* getRandomDataProvider();
QPDF_DLL
std::list<std::string> read_lines_from_file(char const* filename);
QPDF_DLL
std::list<std::string> read_lines_from_file(std::istream&);
// These routines help the tokenizer recognize certain character
// classes without using ctype, which we avoid because of locale
// considerations.

View File

@ -11,6 +11,7 @@
#include <cmath>
#include <iomanip>
#include <sstream>
#include <fstream>
#include <stdexcept>
#include <stdio.h>
#include <errno.h>
@ -566,3 +567,55 @@ QUtil::is_number(char const* p)
}
return found_digit;
}
std::list<std::string>
QUtil::read_lines_from_file(char const* filename)
{
std::ifstream in(filename, std::ios_base::binary);
if (! in.is_open())
{
throw_system_error(std::string("open ") + filename);
}
std::list<std::string> lines = read_lines_from_file(in);
in.close();
return lines;
}
std::list<std::string>
QUtil::read_lines_from_file(std::istream& in)
{
std::list<std::string> result;
std::string* buf = 0;
char c;
while (in.get(c))
{
if (buf == 0)
{
result.push_back("");
buf = &(result.back());
buf->reserve(80);
}
if (buf->capacity() == buf->size())
{
buf->reserve(buf->capacity() * 2);
}
if (c == '\n')
{
// Remove any carriage return that preceded the
// newline and discard the newline
if ((! buf->empty()) && ((*(buf->rbegin())) == '\r'))
{
buf->erase(buf->length() - 1);
}
buf = 0;
}
else
{
buf->append(1, c);
}
}
return result;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -170,6 +170,16 @@ void same_file_test()
assert_same_file("", "qutil.out", false);
}
void read_lines_from_file_test()
{
std::list<std::string> lines = QUtil::read_lines_from_file("other-file");
for (std::list<std::string>::iterator iter = lines.begin();
iter != lines.end(); ++iter)
{
std::cout << *iter << std::endl;
}
}
int main(int argc, char* argv[])
{
try
@ -187,6 +197,8 @@ int main(int argc, char* argv[])
get_whoami_test();
std::cout << "----" << std::endl;
same_file_test();
std::cout << "----" << std::endl;
read_lines_from_file_test();
}
catch (std::exception& e)
{

View File

@ -254,6 +254,14 @@ make
were going to add pages from another source, as discussed in <xref
linkend="ref.page-selection"/>.
</para>
<para>
If <option>@filename</option> appears anywhere in the
command-line, it will be read line by line, and each line will be
treated as a command-line argument. The <option>@-</option> option
allows arguments to be read from standard input. This allows qpdf
to be invoked with an arbitrary number of arbitrarily long
arguments.
</para>
<para>
<option>outfilename</option> does not have to be seekable, even
when generating linearized files. Specifying

View File

@ -69,6 +69,10 @@ Usage: qpdf [ options ] { infilename | --empty } [ outfilename ]\n\
\n\
An option summary appears below. Please see the documentation for details.\n\
\n\
If @filename appears anywhere in the command-line, each line of filename\n\
will be interpreted as an argument. No interpolation is done. Line\n\
terminators are stripped. @- can be specified to read from standard input.\n\
\n\
Note that when contradictory options are provided, whichever options are\n\
provided last take precedence.\n\
\n\
@ -953,6 +957,28 @@ static void parse_version(std::string const& full_version_string,
version = v;
}
static void read_args_from_file(char const* filename,
std::vector<PointerHolder<char> >& new_argv)
{
std::list<std::string> lines;
if (strcmp(filename, "-") == 0)
{
QTC::TC("qpdf", "qpdf read args from stdin");
lines = QUtil::read_lines_from_file(std::cin);
}
else
{
QTC::TC("qpdf", "qpdf read args from file");
lines = QUtil::read_lines_from_file(filename);
}
for (std::list<std::string>::iterator iter = lines.begin();
iter != lines.end(); ++iter)
{
new_argv.push_back(
PointerHolder<char>(QUtil::copy_string((*iter).c_str()), true));
}
}
int main(int argc, char* argv[])
{
whoami = QUtil::getWhoami(argv[0]);
@ -1060,6 +1086,33 @@ int main(int argc, char* argv[])
char const* infilename = 0;
char const* outfilename = 0;
// 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.
std::vector<PointerHolder<char> > new_argv;
new_argv.push_back(PointerHolder<char>(QUtil::copy_string(argv[0]), true));
for (int i = 1; i < argc; ++i)
{
if ((strlen(argv[i]) > 1) && (argv[i][0] == '@'))
{
read_args_from_file(1+argv[i], new_argv);
}
else
{
new_argv.push_back(
PointerHolder<char>(QUtil::copy_string(argv[i]), true));
}
}
PointerHolder<char*> argv_ph(new char*[1+new_argv.size()], true);
argv = argv_ph.getPointer();
for (size_t i = 0; i < new_argv.size(); ++i)
{
argv[i] = new_argv.at(i).getPointer();
}
argc = static_cast<int>(new_argv.size());
argv[argc] = 0;
for (int i = 1; i < argc; ++i)
{
char const* arg = argv[i];

View File

@ -285,3 +285,5 @@ QPDFObjectHandle non-stream in parsecontent 0
QPDFObjectHandle errors in parsecontent 0
QPDF stream with non-space 0
qpdf same file error 0
qpdf read args from stdin 0
qpdf read args from file 0

View File

@ -635,14 +635,20 @@ $td->runtest("dump bad xref",
{$td->FILE => "bad-xref-entry.out",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
# Test @file here too.
open(F, ">args") or die;
print F "--check\n";
print F "--show-xref\n";
close(F);
$td->runtest("dump corrected bad xref",
{$td->COMMAND => "qpdf --check --show-xref bad-xref-entry.pdf"},
{$td->COMMAND => "qpdf \@args bad-xref-entry.pdf"},
{$td->FILE => "bad-xref-entry-corrected.out",
$td->EXIT_STATUS => 3},
$td->NORMALIZE_NEWLINES);
unlink "args";
$td->runtest("don't overwrite self",
{$td->COMMAND => "qpdf a.pdf a.pdf"},
{$td->COMMAND => "(echo a.pdf; echo a.pdf) | qpdf \@-"},
{$td->REGEXP => "input file and output file are the same.*",
$td->EXIT_STATUS => 2});