Commit c60b4ea55a7d0844a81417d004f32c1b5f9f9df0

Authored by Jay Berkenbilt
1 parent 52817f0a

Refactor arg parsing in qpdf.cc to use QPDFArgParser

qpdf/qpdf.cc
@@ -27,6 +27,7 @@ @@ -27,6 +27,7 @@
27 #include <qpdf/QPDFSystemError.hh> 27 #include <qpdf/QPDFSystemError.hh>
28 #include <qpdf/QPDFCryptoProvider.hh> 28 #include <qpdf/QPDFCryptoProvider.hh>
29 #include <qpdf/QPDFEmbeddedFileDocumentHelper.hh> 29 #include <qpdf/QPDFEmbeddedFileDocumentHelper.hh>
  30 +#include <qpdf/QPDFArgParser.hh>
30 31
31 #include <qpdf/QPDFWriter.hh> 32 #include <qpdf/QPDFWriter.hh>
32 #include <qpdf/QIntC.hh> 33 #include <qpdf/QIntC.hh>
@@ -740,14 +741,6 @@ static void parse_object_id(std::string const&amp; objspec, @@ -740,14 +741,6 @@ static void parse_object_id(std::string const&amp; objspec,
740 } 741 }
741 } 742 }
742 743
743 -// This is not a general-purpose argument parser. It is tightly  
744 -// crafted to work with qpdf. qpdf's command-line syntax is very  
745 -// complex because of its long history, and it doesn't really follow  
746 -// any kind of normal standard for arguments, but I don't want to  
747 -// break compatibility by changing what constitutes a valid command.  
748 -// This class is intended to simplify the argument parsing code and  
749 -// also to make it possible to add bash completion support while  
750 -// guaranteeing consistency with the actual argument syntax.  
751 class ArgParser 744 class ArgParser
752 { 745 {
753 public: 746 public:
@@ -755,38 +748,18 @@ class ArgParser @@ -755,38 +748,18 @@ class ArgParser
755 void parseOptions(); 748 void parseOptions();
756 749
757 private: 750 private:
758 - typedef void (ArgParser::*bare_arg_handler_t)();  
759 - typedef void (ArgParser::*param_arg_handler_t)(char* parameter);  
760 -  
761 - struct OptionEntry  
762 - {  
763 - OptionEntry() :  
764 - parameter_needed(false),  
765 - bare_arg_handler(0),  
766 - param_arg_handler(0)  
767 - {  
768 - }  
769 - bool parameter_needed;  
770 - std::string parameter_name;  
771 - std::set<std::string> choices;  
772 - bare_arg_handler_t bare_arg_handler;  
773 - param_arg_handler_t param_arg_handler;  
774 - };  
775 - friend struct OptionEntry;  
776 -  
777 - OptionEntry oe_positional(param_arg_handler_t);  
778 - OptionEntry oe_bare(bare_arg_handler_t);  
779 - OptionEntry oe_requiredParameter(param_arg_handler_t, char const* name);  
780 - OptionEntry oe_optionalParameter(param_arg_handler_t);  
781 - OptionEntry oe_requiredChoices(param_arg_handler_t, char const** choices);  
782 -  
783 - void completionCommon(bool zsh); 751 + static constexpr char const* O_PAGES = "pages";
  752 + static constexpr char const* O_ENCRYPT = "encryption";
  753 + static constexpr char const* O_ENCRYPT_40 = "40-bit encryption";
  754 + static constexpr char const* O_ENCRYPT_128 = "128-bit encryption";
  755 + static constexpr char const* O_ENCRYPT_256 = "256-bit encryption";
  756 + static constexpr char const* O_UNDER_OVERLAY = "underlay/overlay";
  757 + static constexpr char const* O_ATTACHMENT = "attachment";
  758 + static constexpr char const* O_COPY_ATTACHMENT = "copy attachment";
784 759
785 void argHelp(); 760 void argHelp();
786 void argVersion(); 761 void argVersion();
787 void argCopyright(); 762 void argCopyright();
788 - void argCompletionBash();  
789 - void argCompletionZsh();  
790 void argJsonHelp(); 763 void argJsonHelp();
791 void argShowCrypto(); 764 void argShowCrypto();
792 void argPositional(char* arg); 765 void argPositional(char* arg);
@@ -804,6 +777,9 @@ class ArgParser @@ -804,6 +777,9 @@ class ArgParser
804 void argCopyEncryption(char* parameter); 777 void argCopyEncryption(char* parameter);
805 void argEncryptionFilePassword(char* parameter); 778 void argEncryptionFilePassword(char* parameter);
806 void argPages(); 779 void argPages();
  780 + void argPagesPassword(char* parameter);
  781 + void argPagesPositional(char* parameter);
  782 + void argEndPages();
807 void argUnderlay(); 783 void argUnderlay();
808 void argOverlay(); 784 void argOverlay();
809 void argRotate(char* parameter); 785 void argRotate(char* parameter);
@@ -884,6 +860,7 @@ class ArgParser @@ -884,6 +860,7 @@ class ArgParser
884 void arg128UseAes(char* parameter); 860 void arg128UseAes(char* parameter);
885 void arg128ForceV4(); 861 void arg128ForceV4();
886 void arg256ForceR5(); 862 void arg256ForceR5();
  863 + void argEncryptPositional(char* arg);
887 void argEndEncrypt(); 864 void argEndEncrypt();
888 void argUOpositional(char* arg); 865 void argUOpositional(char* arg);
889 void argUOto(char* parameter); 866 void argUOto(char* parameter);
@@ -909,329 +886,257 @@ class ArgParser @@ -909,329 +886,257 @@ class ArgParser
909 void argEndCopyAttachments(); 886 void argEndCopyAttachments();
910 887
911 void usage(std::string const& message); 888 void usage(std::string const& message);
912 - void checkCompletion();  
913 void initOptionTable(); 889 void initOptionTable();
914 - void handleArgFileArguments();  
915 - void handleBashArguments();  
916 - void readArgsFromFile(char const* filename);  
917 void doFinalChecks(); 890 void doFinalChecks();
918 - void addOptionsToCompletions();  
919 - void addChoicesToCompletions(std::string const&, std::string const&);  
920 - void handleCompletion();  
921 - std::vector<PageSpec> parsePagesOptions();  
922 void parseUnderOverlayOptions(UnderOverlay*); 891 void parseUnderOverlayOptions(UnderOverlay*);
923 void parseRotationParameter(std::string const&); 892 void parseRotationParameter(std::string const&);
924 std::vector<int> parseNumrange(char const* range, int max, 893 std::vector<int> parseNumrange(char const* range, int max,
925 bool throw_error = false); 894 bool throw_error = false);
926 895
927 - int argc;  
928 - char** argv; 896 + QPDFArgParser ap;
929 Options& o; 897 Options& o;
930 - int cur_arg;  
931 - bool bash_completion;  
932 - bool zsh_completion;  
933 - std::string bash_prev;  
934 - std::string bash_cur;  
935 - std::string bash_line;  
936 - std::set<std::string> completions;  
937 -  
938 - std::map<std::string, OptionEntry>* option_table;  
939 - std::map<std::string, OptionEntry> help_option_table;  
940 - std::map<std::string, OptionEntry> main_option_table;  
941 - std::map<std::string, OptionEntry> encrypt40_option_table;  
942 - std::map<std::string, OptionEntry> encrypt128_option_table;  
943 - std::map<std::string, OptionEntry> encrypt256_option_table;  
944 - std::map<std::string, OptionEntry> under_overlay_option_table;  
945 - std::map<std::string, OptionEntry> add_attachment_option_table;  
946 - std::map<std::string, OptionEntry> copy_attachments_option_table;  
947 - std::vector<PointerHolder<char> > new_argv;  
948 - std::vector<PointerHolder<char> > bash_argv;  
949 - PointerHolder<char*> argv_ph;  
950 - PointerHolder<char*> bash_argv_ph; 898 + std::vector<char*> accumulated_args;
  899 + char* pages_password;
951 }; 900 };
952 901
953 ArgParser::ArgParser(int argc, char* argv[], Options& o) : 902 ArgParser::ArgParser(int argc, char* argv[], Options& o) :
954 - argc(argc),  
955 - argv(argv), 903 + ap(argc, argv, "QPDF_EXECUTABLE"),
956 o(o), 904 o(o),
957 - cur_arg(0),  
958 - bash_completion(false),  
959 - zsh_completion(false) 905 + pages_password(nullptr)
960 { 906 {
961 - option_table = &main_option_table;  
962 initOptionTable(); 907 initOptionTable();
963 } 908 }
964 909
965 -ArgParser::OptionEntry  
966 -ArgParser::oe_positional(param_arg_handler_t h)  
967 -{  
968 - OptionEntry oe;  
969 - oe.param_arg_handler = h;  
970 - return oe;  
971 -}  
972 -  
973 -ArgParser::OptionEntry  
974 -ArgParser::oe_bare(bare_arg_handler_t h)  
975 -{  
976 - OptionEntry oe;  
977 - oe.parameter_needed = false;  
978 - oe.bare_arg_handler = h;  
979 - return oe;  
980 -}  
981 -  
982 -ArgParser::OptionEntry  
983 -ArgParser::oe_requiredParameter(param_arg_handler_t h, char const* name)  
984 -{  
985 - OptionEntry oe;  
986 - oe.parameter_needed = true;  
987 - oe.parameter_name = name;  
988 - oe.param_arg_handler = h;  
989 - return oe;  
990 -}  
991 -  
992 -ArgParser::OptionEntry  
993 -ArgParser::oe_optionalParameter(param_arg_handler_t h)  
994 -{  
995 - OptionEntry oe;  
996 - oe.parameter_needed = false;  
997 - oe.param_arg_handler = h;  
998 - return oe;  
999 -}  
1000 -  
1001 -ArgParser::OptionEntry  
1002 -ArgParser::oe_requiredChoices(param_arg_handler_t h, char const** choices)  
1003 -{  
1004 - OptionEntry oe;  
1005 - oe.parameter_needed = true;  
1006 - oe.param_arg_handler = h;  
1007 - for (char const** i = choices; *i; ++i)  
1008 - {  
1009 - oe.choices.insert(*i);  
1010 - }  
1011 - return oe;  
1012 -}  
1013 -  
1014 void 910 void
1015 ArgParser::initOptionTable() 911 ArgParser::initOptionTable()
1016 { 912 {
1017 - std::map<std::string, OptionEntry>* t = &this->help_option_table;  
1018 - (*t)["help"] = oe_bare(&ArgParser::argHelp);  
1019 - (*t)["version"] = oe_bare(&ArgParser::argVersion);  
1020 - (*t)["copyright"] = oe_bare(&ArgParser::argCopyright);  
1021 - (*t)["completion-bash"] = oe_bare(&ArgParser::argCompletionBash);  
1022 - (*t)["completion-zsh"] = oe_bare(&ArgParser::argCompletionZsh);  
1023 - (*t)["json-help"] = oe_bare(&ArgParser::argJsonHelp);  
1024 - (*t)["show-crypto"] = oe_bare(&ArgParser::argShowCrypto); 913 + auto b = [this](void (ArgParser::*f)()) {
  914 + return QPDFArgParser::bindBare(f, this);
  915 + };
  916 + auto p = [this](void (ArgParser::*f)(char *)) {
  917 + return QPDFArgParser::bindParam(f, this);
  918 + };
  919 +
  920 + this->ap.addFinalCheck(b(&ArgParser::doFinalChecks));
1025 921
1026 - t = &this->main_option_table; 922 + this->ap.selectHelpOptionTable();
  923 + this->ap.addBare("help", b(&ArgParser::argHelp));
  924 + this->ap.addBare("version", b(&ArgParser::argVersion));
  925 + this->ap.addBare("copyright", b(&ArgParser::argCopyright));
  926 + this->ap.addBare("json-help", b(&ArgParser::argJsonHelp));
  927 + this->ap.addBare("show-crypto", b(&ArgParser::argShowCrypto));
  928 +
  929 + this->ap.selectMainOptionTable();
1027 char const* yn[] = {"y", "n", 0}; 930 char const* yn[] = {"y", "n", 0};
1028 - (*t)[""] = oe_positional(&ArgParser::argPositional);  
1029 - (*t)["password"] = oe_requiredParameter(  
1030 - &ArgParser::argPassword, "password");  
1031 - (*t)["password-file"] = oe_requiredParameter(  
1032 - &ArgParser::argPasswordFile, "password-file");  
1033 - (*t)["empty"] = oe_bare(&ArgParser::argEmpty);  
1034 - (*t)["linearize"] = oe_bare(&ArgParser::argLinearize);  
1035 - (*t)["encrypt"] = oe_bare(&ArgParser::argEncrypt);  
1036 - (*t)["decrypt"] = oe_bare(&ArgParser::argDecrypt);  
1037 - (*t)["password-is-hex-key"] = oe_bare(&ArgParser::argPasswordIsHexKey);  
1038 - (*t)["suppress-password-recovery"] =  
1039 - oe_bare(&ArgParser::argSuppressPasswordRecovery); 931 + this->ap.addPositional(p(&ArgParser::argPositional));
  932 + this->ap.addRequiredParameter("password",
  933 + p(&ArgParser::argPassword), "password");
  934 + this->ap.addRequiredParameter("password-file",
  935 + p(&ArgParser::argPasswordFile), "password-file");
  936 + this->ap.addBare("empty", b(&ArgParser::argEmpty));
  937 + this->ap.addBare("linearize", b(&ArgParser::argLinearize));
  938 + this->ap.addBare("decrypt", b(&ArgParser::argDecrypt));
  939 + this->ap.addBare("password-is-hex-key", b(&ArgParser::argPasswordIsHexKey));
  940 + this->ap.addBare("suppress-password-recovery",
  941 + b(&ArgParser::argSuppressPasswordRecovery));
1040 char const* password_mode_choices[] = 942 char const* password_mode_choices[] =
1041 {"bytes", "hex-bytes", "unicode", "auto", 0}; 943 {"bytes", "hex-bytes", "unicode", "auto", 0};
1042 - (*t)["password-mode"] = oe_requiredChoices(  
1043 - &ArgParser::argPasswordMode, password_mode_choices);  
1044 - (*t)["copy-encryption"] = oe_requiredParameter(  
1045 - &ArgParser::argCopyEncryption, "file");  
1046 - (*t)["encryption-file-password"] = oe_requiredParameter(  
1047 - &ArgParser::argEncryptionFilePassword, "password");  
1048 - (*t)["pages"] = oe_bare(&ArgParser::argPages);  
1049 - (*t)["rotate"] = oe_requiredParameter(  
1050 - &ArgParser::argRotate, "[+|-]angle:page-range"); 944 + this->ap.addRequiredChoices("password-mode",
  945 + p(&ArgParser::argPasswordMode), password_mode_choices);
  946 + this->ap.addRequiredParameter("copy-encryption",
  947 + p(&ArgParser::argCopyEncryption), "file");
  948 + this->ap.addRequiredParameter("encryption-file-password",
  949 + p(&ArgParser::argEncryptionFilePassword), "password");
  950 + this->ap.addRequiredParameter("rotate",
  951 + p(&ArgParser::argRotate), "[+|-]angle:page-range");
1051 char const* stream_data_choices[] = 952 char const* stream_data_choices[] =
1052 {"compress", "preserve", "uncompress", 0}; 953 {"compress", "preserve", "uncompress", 0};
1053 - (*t)["collate"] = oe_optionalParameter(&ArgParser::argCollate);  
1054 - (*t)["flatten-rotation"] = oe_bare(&ArgParser::argFlattenRotation);  
1055 - (*t)["list-attachments"] = oe_bare(&ArgParser::argListAttachments);  
1056 - (*t)["show-attachment"] = oe_requiredParameter(  
1057 - &ArgParser::argShowAttachment, "attachment-key");  
1058 - (*t)["remove-attachment"] = oe_requiredParameter(  
1059 - &ArgParser::argRemoveAttachment, "attachment-key");  
1060 - (*t)["add-attachment"] = oe_bare(&ArgParser::argAddAttachment);  
1061 - (*t)["copy-attachments-from"] = oe_bare(&ArgParser::argCopyAttachments);  
1062 - (*t)["stream-data"] = oe_requiredChoices(  
1063 - &ArgParser::argStreamData, stream_data_choices);  
1064 - (*t)["compress-streams"] = oe_requiredChoices(  
1065 - &ArgParser::argCompressStreams, yn);  
1066 - (*t)["recompress-flate"] = oe_bare(&ArgParser::argRecompressFlate);  
1067 - (*t)["compression-level"] = oe_requiredParameter(  
1068 - &ArgParser::argCompressionLevel, "level"); 954 + this->ap.addOptionalParameter("collate",p(&ArgParser::argCollate));
  955 + this->ap.addBare("flatten-rotation", b(&ArgParser::argFlattenRotation));
  956 + this->ap.addBare("list-attachments", b(&ArgParser::argListAttachments));
  957 + this->ap.addRequiredParameter("show-attachment",
  958 + p(&ArgParser::argShowAttachment), "attachment-key");
  959 + this->ap.addRequiredParameter("remove-attachment",
  960 + p(&ArgParser::argRemoveAttachment), "attachment-key");
  961 + this->ap.addBare("add-attachment", b(&ArgParser::argAddAttachment));
  962 + this->ap.addBare(
  963 + "copy-attachments-from", b(&ArgParser::argCopyAttachments));
  964 + this->ap.addRequiredChoices("stream-data",
  965 + p(&ArgParser::argStreamData), stream_data_choices);
  966 + this->ap.addRequiredChoices("compress-streams",
  967 + p(&ArgParser::argCompressStreams), yn);
  968 + this->ap.addBare("recompress-flate", b(&ArgParser::argRecompressFlate));
  969 + this->ap.addRequiredParameter("compression-level",
  970 + p(&ArgParser::argCompressionLevel), "level");
1069 char const* decode_level_choices[] = 971 char const* decode_level_choices[] =
1070 {"none", "generalized", "specialized", "all", 0}; 972 {"none", "generalized", "specialized", "all", 0};
1071 - (*t)["decode-level"] = oe_requiredChoices(  
1072 - &ArgParser::argDecodeLevel, decode_level_choices);  
1073 - (*t)["normalize-content"] = oe_requiredChoices(  
1074 - &ArgParser::argNormalizeContent, yn);  
1075 - (*t)["suppress-recovery"] = oe_bare(&ArgParser::argSuppressRecovery); 973 + this->ap.addRequiredChoices("decode-level",
  974 + p(&ArgParser::argDecodeLevel), decode_level_choices);
  975 + this->ap.addRequiredChoices("normalize-content",
  976 + p(&ArgParser::argNormalizeContent), yn);
  977 + this->ap.addBare("suppress-recovery", b(&ArgParser::argSuppressRecovery));
1076 char const* object_streams_choices[] = { 978 char const* object_streams_choices[] = {
1077 "disable", "preserve", "generate", 0}; 979 "disable", "preserve", "generate", 0};
1078 - (*t)["object-streams"] = oe_requiredChoices(  
1079 - &ArgParser::argObjectStreams, object_streams_choices);  
1080 - (*t)["ignore-xref-streams"] = oe_bare(&ArgParser::argIgnoreXrefStreams);  
1081 - (*t)["qdf"] = oe_bare(&ArgParser::argQdf);  
1082 - (*t)["preserve-unreferenced"] = oe_bare(  
1083 - &ArgParser::argPreserveUnreferenced);  
1084 - (*t)["preserve-unreferenced-resources"] = oe_bare(  
1085 - &ArgParser::argPreserveUnreferencedResources); 980 + this->ap.addRequiredChoices("object-streams",
  981 + p(&ArgParser::argObjectStreams), object_streams_choices);
  982 + this->ap.addBare(
  983 + "ignore-xref-streams", b(&ArgParser::argIgnoreXrefStreams));
  984 + this->ap.addBare("qdf", b(&ArgParser::argQdf));
  985 + this->ap.addBare(
  986 + "preserve-unreferenced", b(&ArgParser::argPreserveUnreferenced));
  987 + this->ap.addBare(
  988 + "preserve-unreferenced-resources",
  989 + b(&ArgParser::argPreserveUnreferencedResources));
1086 char const* remove_unref_choices[] = { 990 char const* remove_unref_choices[] = {
1087 "auto", "yes", "no", 0}; 991 "auto", "yes", "no", 0};
1088 - (*t)["remove-unreferenced-resources"] = oe_requiredChoices(  
1089 - &ArgParser::argRemoveUnreferencedResources, remove_unref_choices);  
1090 - (*t)["keep-files-open"] = oe_requiredChoices(  
1091 - &ArgParser::argKeepFilesOpen, yn);  
1092 - (*t)["keep-files-open-threshold"] = oe_requiredParameter(  
1093 - &ArgParser::argKeepFilesOpenThreshold, "count");  
1094 - (*t)["newline-before-endstream"] = oe_bare(  
1095 - &ArgParser::argNewlineBeforeEndstream);  
1096 - (*t)["linearize-pass1"] = oe_requiredParameter(  
1097 - &ArgParser::argLinearizePass1, "filename");  
1098 - (*t)["coalesce-contents"] = oe_bare(&ArgParser::argCoalesceContents); 992 + this->ap.addRequiredChoices("remove-unreferenced-resources",
  993 + p(&ArgParser::argRemoveUnreferencedResources), remove_unref_choices);
  994 + this->ap.addRequiredChoices("keep-files-open",
  995 + p(&ArgParser::argKeepFilesOpen), yn);
  996 + this->ap.addRequiredParameter("keep-files-open-threshold",
  997 + p(&ArgParser::argKeepFilesOpenThreshold), "count");
  998 + this->ap.addBare("newline-before-endstream", b(&ArgParser::argNewlineBeforeEndstream));
  999 + this->ap.addRequiredParameter("linearize-pass1",
  1000 + p(&ArgParser::argLinearizePass1), "filename");
  1001 + this->ap.addBare("coalesce-contents", b(&ArgParser::argCoalesceContents));
1099 char const* flatten_choices[] = {"all", "print", "screen", 0}; 1002 char const* flatten_choices[] = {"all", "print", "screen", 0};
1100 - (*t)["flatten-annotations"] = oe_requiredChoices(  
1101 - &ArgParser::argFlattenAnnotations, flatten_choices);  
1102 - (*t)["generate-appearances"] =  
1103 - oe_bare(&ArgParser::argGenerateAppearances);  
1104 - (*t)["min-version"] = oe_requiredParameter(  
1105 - &ArgParser::argMinVersion, "version");  
1106 - (*t)["force-version"] = oe_requiredParameter(  
1107 - &ArgParser::argForceVersion, "version");  
1108 - (*t)["split-pages"] = oe_optionalParameter(&ArgParser::argSplitPages);  
1109 - (*t)["verbose"] = oe_bare(&ArgParser::argVerbose);  
1110 - (*t)["progress"] = oe_bare(&ArgParser::argProgress);  
1111 - (*t)["no-warn"] = oe_bare(&ArgParser::argNoWarn);  
1112 - (*t)["warning-exit-0"] = oe_bare(&ArgParser::argWarningExitZero);  
1113 - (*t)["deterministic-id"] = oe_bare(&ArgParser::argDeterministicId);  
1114 - (*t)["static-id"] = oe_bare(&ArgParser::argStaticId);  
1115 - (*t)["static-aes-iv"] = oe_bare(&ArgParser::argStaticAesIv);  
1116 - (*t)["no-original-object-ids"] = oe_bare(  
1117 - &ArgParser::argNoOriginalObjectIds);  
1118 - (*t)["show-encryption"] = oe_bare(&ArgParser::argShowEncryption);  
1119 - (*t)["show-encryption-key"] = oe_bare(&ArgParser::argShowEncryptionKey);  
1120 - (*t)["check-linearization"] = oe_bare(&ArgParser::argCheckLinearization);  
1121 - (*t)["show-linearization"] = oe_bare(&ArgParser::argShowLinearization);  
1122 - (*t)["show-xref"] = oe_bare(&ArgParser::argShowXref);  
1123 - (*t)["show-object"] = oe_requiredParameter(  
1124 - &ArgParser::argShowObject, "trailer|obj[,gen]");  
1125 - (*t)["raw-stream-data"] = oe_bare(&ArgParser::argRawStreamData);  
1126 - (*t)["filtered-stream-data"] = oe_bare(&ArgParser::argFilteredStreamData);  
1127 - (*t)["show-npages"] = oe_bare(&ArgParser::argShowNpages);  
1128 - (*t)["show-pages"] = oe_bare(&ArgParser::argShowPages);  
1129 - (*t)["with-images"] = oe_bare(&ArgParser::argWithImages);  
1130 - (*t)["json"] = oe_bare(&ArgParser::argJson); 1003 + this->ap.addRequiredChoices("flatten-annotations",
  1004 + p(&ArgParser::argFlattenAnnotations), flatten_choices);
  1005 + this->ap.addBare("generate-appearances", b(&ArgParser::argGenerateAppearances));
  1006 + this->ap.addRequiredParameter("min-version",
  1007 + p(&ArgParser::argMinVersion), "version");
  1008 + this->ap.addRequiredParameter("force-version",
  1009 + p(&ArgParser::argForceVersion), "version");
  1010 + this->ap.addOptionalParameter("split-pages",p(&ArgParser::argSplitPages));
  1011 + this->ap.addBare("verbose", b(&ArgParser::argVerbose));
  1012 + this->ap.addBare("progress", b(&ArgParser::argProgress));
  1013 + this->ap.addBare("no-warn", b(&ArgParser::argNoWarn));
  1014 + this->ap.addBare("warning-exit-0", b(&ArgParser::argWarningExitZero));
  1015 + this->ap.addBare("deterministic-id", b(&ArgParser::argDeterministicId));
  1016 + this->ap.addBare("static-id", b(&ArgParser::argStaticId));
  1017 + this->ap.addBare("static-aes-iv", b(&ArgParser::argStaticAesIv));
  1018 + this->ap.addBare("no-original-object-ids", b(&ArgParser::argNoOriginalObjectIds));
  1019 + this->ap.addBare("show-encryption", b(&ArgParser::argShowEncryption));
  1020 + this->ap.addBare("show-encryption-key", b(&ArgParser::argShowEncryptionKey));
  1021 + this->ap.addBare("check-linearization", b(&ArgParser::argCheckLinearization));
  1022 + this->ap.addBare("show-linearization", b(&ArgParser::argShowLinearization));
  1023 + this->ap.addBare("show-xref", b(&ArgParser::argShowXref));
  1024 + this->ap.addRequiredParameter("show-object",
  1025 + p(&ArgParser::argShowObject), "trailer|obj[,gen]");
  1026 + this->ap.addBare("raw-stream-data", b(&ArgParser::argRawStreamData));
  1027 + this->ap.addBare("filtered-stream-data", b(&ArgParser::argFilteredStreamData));
  1028 + this->ap.addBare("show-npages", b(&ArgParser::argShowNpages));
  1029 + this->ap.addBare("show-pages", b(&ArgParser::argShowPages));
  1030 + this->ap.addBare("with-images", b(&ArgParser::argWithImages));
  1031 + this->ap.addBare("json", b(&ArgParser::argJson));
1131 // The list of selectable top-level keys id duplicated in three 1032 // The list of selectable top-level keys id duplicated in three
1132 // places: json_schema, do_json, and initOptionTable. 1033 // places: json_schema, do_json, and initOptionTable.
1133 char const* json_key_choices[] = { 1034 char const* json_key_choices[] = {
1134 "objects", "objectinfo", "pages", "pagelabels", "outlines", 1035 "objects", "objectinfo", "pages", "pagelabels", "outlines",
1135 "acroform", "encrypt", "attachments", 0}; 1036 "acroform", "encrypt", "attachments", 0};
1136 - (*t)["json-key"] = oe_requiredChoices(  
1137 - &ArgParser::argJsonKey, json_key_choices);  
1138 - (*t)["json-object"] = oe_requiredParameter(  
1139 - &ArgParser::argJsonObject, "trailer|obj[,gen]");  
1140 - (*t)["check"] = oe_bare(&ArgParser::argCheck);  
1141 - (*t)["optimize-images"] = oe_bare(&ArgParser::argOptimizeImages);  
1142 - (*t)["externalize-inline-images"] =  
1143 - oe_bare(&ArgParser::argExternalizeInlineImages);  
1144 - (*t)["keep-inline-images"] = oe_bare(&ArgParser::argKeepInlineImages);  
1145 - (*t)["remove-page-labels"] = oe_bare(&ArgParser::argRemovePageLabels);  
1146 - (*t)["oi-min-width"] = oe_requiredParameter(  
1147 - &ArgParser::argOiMinWidth, "minimum-width");  
1148 - (*t)["oi-min-height"] = oe_requiredParameter(  
1149 - &ArgParser::argOiMinHeight, "minimum-height");  
1150 - (*t)["oi-min-area"] = oe_requiredParameter(  
1151 - &ArgParser::argOiMinArea, "minimum-area");  
1152 - (*t)["ii-min-bytes"] = oe_requiredParameter(  
1153 - &ArgParser::argIiMinBytes, "minimum-bytes");  
1154 - (*t)["overlay"] = oe_bare(&ArgParser::argOverlay);  
1155 - (*t)["underlay"] = oe_bare(&ArgParser::argUnderlay);  
1156 - (*t)["replace-input"] = oe_bare(&ArgParser::argReplaceInput);  
1157 - (*t)["is-encrypted"] = oe_bare(&ArgParser::argIsEncrypted);  
1158 - (*t)["requires-password"] = oe_bare(&ArgParser::argRequiresPassword);  
1159 - (*t)["allow-weak-crypto"] = oe_bare(&ArgParser::argAllowWeakCrypto);  
1160 -  
1161 - t = &this->encrypt40_option_table;  
1162 - (*t)["--"] = oe_bare(&ArgParser::argEndEncrypt);  
1163 - // The above 40-bit options are also 128-bit and 256-bit options,  
1164 - // so copy what we have so far to 128. Then continue separately  
1165 - // with 128. We later copy 128 to 256.  
1166 - this->encrypt128_option_table = this->encrypt40_option_table;  
1167 - (*t)["print"] = oe_requiredChoices(&ArgParser::arg40Print, yn);  
1168 - (*t)["modify"] = oe_requiredChoices(&ArgParser::arg40Modify, yn);  
1169 - (*t)["extract"] = oe_requiredChoices(&ArgParser::arg40Extract, yn);  
1170 - (*t)["annotate"] = oe_requiredChoices(&ArgParser::arg40Annotate, yn);  
1171 -  
1172 - t = &this->encrypt128_option_table;  
1173 - (*t)["accessibility"] = oe_requiredChoices(  
1174 - &ArgParser::arg128Accessibility, yn);  
1175 - (*t)["extract"] = oe_requiredChoices(&ArgParser::arg128Extract, yn);  
1176 - char const* print128_choices[] = {"full", "low", "none", 0};  
1177 - (*t)["print"] = oe_requiredChoices(  
1178 - &ArgParser::arg128Print, print128_choices);  
1179 - (*t)["assemble"] = oe_requiredChoices(&ArgParser::arg128Assemble, yn);  
1180 - (*t)["annotate"] = oe_requiredChoices(&ArgParser::arg128Annotate, yn);  
1181 - (*t)["form"] = oe_requiredChoices(&ArgParser::arg128Form, yn);  
1182 - (*t)["modify-other"] = oe_requiredChoices(&ArgParser::arg128ModOther, yn);  
1183 - char const* modify128_choices[] =  
1184 - {"all", "annotate", "form", "assembly", "none", 0};  
1185 - (*t)["modify"] = oe_requiredChoices(  
1186 - &ArgParser::arg128Modify, modify128_choices);  
1187 - (*t)["cleartext-metadata"] = oe_bare(&ArgParser::arg128ClearTextMetadata);  
1188 -  
1189 - // The above 128-bit options are also 256-bit options, so copy  
1190 - // what we have so far. Then continue separately with 128 and 256.  
1191 - this->encrypt256_option_table = this->encrypt128_option_table;  
1192 - (*t)["use-aes"] = oe_requiredChoices(&ArgParser::arg128UseAes, yn);  
1193 - (*t)["force-V4"] = oe_bare(&ArgParser::arg128ForceV4);  
1194 -  
1195 - t = &this->encrypt256_option_table;  
1196 - (*t)["force-R5"] = oe_bare(&ArgParser::arg256ForceR5);  
1197 - (*t)["allow-insecure"] = oe_bare(&ArgParser::argAllowInsecure);  
1198 -  
1199 - t = &this->under_overlay_option_table;  
1200 - (*t)[""] = oe_positional(&ArgParser::argUOpositional);  
1201 - (*t)["to"] = oe_requiredParameter(  
1202 - &ArgParser::argUOto, "page-range");  
1203 - (*t)["from"] = oe_requiredParameter(  
1204 - &ArgParser::argUOfrom, "page-range");  
1205 - (*t)["repeat"] = oe_requiredParameter(  
1206 - &ArgParser::argUOrepeat, "page-range");  
1207 - (*t)["password"] = oe_requiredParameter(  
1208 - &ArgParser::argUOpassword, "password");  
1209 - (*t)["--"] = oe_bare(&ArgParser::argEndUnderOverlay);  
1210 -  
1211 - t = &this->add_attachment_option_table;  
1212 - (*t)[""] = oe_positional(&ArgParser::argAApositional);  
1213 - (*t)["key"] = oe_requiredParameter(  
1214 - &ArgParser::argAAKey, "attachment-key");  
1215 - (*t)["filename"] = oe_requiredParameter(  
1216 - &ArgParser::argAAFilename, "filename");  
1217 - (*t)["creationdate"] = oe_requiredParameter(  
1218 - &ArgParser::argAACreationDate, "creation-date");  
1219 - (*t)["moddate"] = oe_requiredParameter(  
1220 - &ArgParser::argAAModDate, "modification-date");  
1221 - (*t)["mimetype"] = oe_requiredParameter(  
1222 - &ArgParser::argAAMimeType, "mime/type");  
1223 - (*t)["description"] = oe_requiredParameter(  
1224 - &ArgParser::argAADescription, "description");  
1225 - (*t)["replace"] = oe_bare(&ArgParser::argAAReplace);  
1226 - (*t)["--"] = oe_bare(&ArgParser::argEndAddAttachment);  
1227 -  
1228 - t = &this->copy_attachments_option_table;  
1229 - (*t)[""] = oe_positional(&ArgParser::argCApositional);  
1230 - (*t)["prefix"] = oe_requiredParameter(  
1231 - &ArgParser::argCAprefix, "prefix");  
1232 - (*t)["password"] = oe_requiredParameter(  
1233 - &ArgParser::argCApassword, "password");  
1234 - (*t)["--"] = oe_bare(&ArgParser::argEndCopyAttachments); 1037 + this->ap.addRequiredChoices("json-key",
  1038 + p(&ArgParser::argJsonKey), json_key_choices);
  1039 + this->ap.addRequiredParameter("json-object",
  1040 + p(&ArgParser::argJsonObject), "trailer|obj[,gen]");
  1041 + this->ap.addBare("check", b(&ArgParser::argCheck));
  1042 + this->ap.addBare("optimize-images", b(&ArgParser::argOptimizeImages));
  1043 + this->ap.addBare("externalize-inline-images", b(&ArgParser::argExternalizeInlineImages));
  1044 + this->ap.addBare("keep-inline-images", b(&ArgParser::argKeepInlineImages));
  1045 + this->ap.addBare("remove-page-labels", b(&ArgParser::argRemovePageLabels));
  1046 + this->ap.addRequiredParameter("oi-min-width",
  1047 + p(&ArgParser::argOiMinWidth), "minimum-width");
  1048 + this->ap.addRequiredParameter("oi-min-height",
  1049 + p(&ArgParser::argOiMinHeight), "minimum-height");
  1050 + this->ap.addRequiredParameter("oi-min-area",
  1051 + p(&ArgParser::argOiMinArea), "minimum-area");
  1052 + this->ap.addRequiredParameter("ii-min-bytes",
  1053 + p(&ArgParser::argIiMinBytes), "minimum-bytes");
  1054 + this->ap.addBare("overlay", b(&ArgParser::argOverlay));
  1055 + this->ap.addBare("underlay", b(&ArgParser::argUnderlay));
  1056 + this->ap.addBare("replace-input", b(&ArgParser::argReplaceInput));
  1057 + this->ap.addBare("is-encrypted", b(&ArgParser::argIsEncrypted));
  1058 + this->ap.addBare("requires-password", b(&ArgParser::argRequiresPassword));
  1059 + this->ap.addBare("allow-weak-crypto", b(&ArgParser::argAllowWeakCrypto));
  1060 +
  1061 + this->ap.selectMainOptionTable();
  1062 + this->ap.addBare("pages", b(&ArgParser::argPages));
  1063 + this->ap.registerOptionTable(O_PAGES, b(&ArgParser::argEndPages));
  1064 + this->ap.addRequiredParameter(
  1065 + "password", p(&ArgParser::argPagesPassword), "password");
  1066 + this->ap.addPositional(p(&ArgParser::argPagesPositional));
  1067 +
  1068 + this->ap.selectMainOptionTable();
  1069 + this->ap.addBare("encrypt", b(&ArgParser::argEncrypt));
  1070 + this->ap.registerOptionTable(O_ENCRYPT, b(&ArgParser::argEndEncrypt));
  1071 + this->ap.addPositional(p(&ArgParser::argEncryptPositional));
  1072 + this->ap.registerOptionTable(O_ENCRYPT_40, b(&ArgParser::argEndEncrypt));
  1073 + this->ap.addRequiredChoices("extract",p(&ArgParser::arg40Extract), yn);
  1074 + this->ap.addRequiredChoices("annotate",p(&ArgParser::arg40Annotate), yn);
  1075 + this->ap.addRequiredChoices("print",p(&ArgParser::arg40Print), yn);
  1076 + this->ap.addRequiredChoices("modify",p(&ArgParser::arg40Modify), yn);
  1077 + this->ap.registerOptionTable(O_ENCRYPT_128, b(&ArgParser::argEndEncrypt));
  1078 + this->ap.registerOptionTable(O_ENCRYPT_256, b(&ArgParser::argEndEncrypt));
  1079 + for (char const* k: {O_ENCRYPT_128, O_ENCRYPT_256})
  1080 + {
  1081 + this->ap.selectOptionTable(k);
  1082 + this->ap.addRequiredChoices("accessibility",
  1083 + p(&ArgParser::arg128Accessibility), yn);
  1084 + this->ap.addRequiredChoices("extract", p(&ArgParser::arg128Extract), yn);
  1085 + char const* print128_choices[] = {"full", "low", "none", 0};
  1086 + this->ap.addRequiredChoices("print",
  1087 + p(&ArgParser::arg128Print), print128_choices);
  1088 + this->ap.addRequiredChoices("assemble",p(&ArgParser::arg128Assemble), yn);
  1089 + this->ap.addRequiredChoices("annotate",p(&ArgParser::arg128Annotate), yn);
  1090 + this->ap.addRequiredChoices("form",p(&ArgParser::arg128Form), yn);
  1091 + this->ap.addRequiredChoices("modify-other",p(&ArgParser::arg128ModOther), yn);
  1092 + char const* modify128_choices[] =
  1093 + {"all", "annotate", "form", "assembly", "none", 0};
  1094 + this->ap.addRequiredChoices("modify",
  1095 + p(&ArgParser::arg128Modify), modify128_choices);
  1096 + this->ap.addBare("cleartext-metadata", b(&ArgParser::arg128ClearTextMetadata));
  1097 + }
  1098 +
  1099 + this->ap.selectOptionTable(O_ENCRYPT_128);
  1100 + this->ap.addRequiredChoices("use-aes",p(&ArgParser::arg128UseAes), yn);
  1101 + this->ap.addBare("force-V4", b(&ArgParser::arg128ForceV4));
  1102 +
  1103 + this->ap.selectOptionTable(O_ENCRYPT_256);
  1104 + this->ap.addBare("force-R5", b(&ArgParser::arg256ForceR5));
  1105 + this->ap.addBare("allow-insecure", b(&ArgParser::argAllowInsecure));
  1106 +
  1107 + this->ap.registerOptionTable(O_UNDER_OVERLAY, b(&ArgParser::argEndUnderOverlay));
  1108 + this->ap.addPositional(p(&ArgParser::argUOpositional));
  1109 + this->ap.addRequiredParameter("to",
  1110 + p(&ArgParser::argUOto), "page-range");
  1111 + this->ap.addRequiredParameter("from",
  1112 + p(&ArgParser::argUOfrom), "page-range");
  1113 + this->ap.addRequiredParameter("repeat",
  1114 + p(&ArgParser::argUOrepeat), "page-range");
  1115 + this->ap.addRequiredParameter("password",
  1116 + p(&ArgParser::argUOpassword), "password");
  1117 +
  1118 + this->ap.registerOptionTable(O_ATTACHMENT, b(&ArgParser::argEndAddAttachment));
  1119 + this->ap.addPositional(p(&ArgParser::argAApositional));
  1120 + this->ap.addRequiredParameter("key",
  1121 + p(&ArgParser::argAAKey), "attachment-key");
  1122 + this->ap.addRequiredParameter("filename",
  1123 + p(&ArgParser::argAAFilename), "filename");
  1124 + this->ap.addRequiredParameter("creationdate",
  1125 + p(&ArgParser::argAACreationDate), "creation-date");
  1126 + this->ap.addRequiredParameter("moddate",
  1127 + p(&ArgParser::argAAModDate), "modification-date");
  1128 + this->ap.addRequiredParameter("mimetype",
  1129 + p(&ArgParser::argAAMimeType), "mime/type");
  1130 + this->ap.addRequiredParameter("description",
  1131 + p(&ArgParser::argAADescription), "description");
  1132 + this->ap.addBare("replace", b(&ArgParser::argAAReplace));
  1133 +
  1134 + this->ap.registerOptionTable(O_COPY_ATTACHMENT, b(&ArgParser::argEndCopyAttachments));
  1135 + this->ap.addPositional(p(&ArgParser::argCApositional));
  1136 + this->ap.addRequiredParameter("prefix",
  1137 + p(&ArgParser::argCAprefix), "prefix");
  1138 + this->ap.addRequiredParameter("password",
  1139 + p(&ArgParser::argCApassword), "password");
1235 } 1140 }
1236 1141
1237 void 1142 void
@@ -1818,58 +1723,6 @@ ArgParser::argHelp() @@ -1818,58 +1723,6 @@ ArgParser::argHelp()
1818 } 1723 }
1819 1724
1820 void 1725 void
1821 -ArgParser::completionCommon(bool zsh)  
1822 -{  
1823 - std::string progname = argv[0];  
1824 - std::string executable;  
1825 - std::string appdir;  
1826 - std::string appimage;  
1827 - if (QUtil::get_env("QPDF_EXECUTABLE", &executable))  
1828 - {  
1829 - progname = executable;  
1830 - }  
1831 - else if (QUtil::get_env("APPDIR", &appdir) &&  
1832 - QUtil::get_env("APPIMAGE", &appimage))  
1833 - {  
1834 - // Detect if we're in an AppImage and adjust  
1835 - if ((appdir.length() < strlen(argv[0])) &&  
1836 - (strncmp(appdir.c_str(), argv[0], appdir.length()) == 0))  
1837 - {  
1838 - progname = appimage;  
1839 - }  
1840 - }  
1841 - if (zsh)  
1842 - {  
1843 - std::cout << "autoload -U +X bashcompinit && bashcompinit && ";  
1844 - }  
1845 - std::cout << "complete -o bashdefault -o default";  
1846 - if (! zsh)  
1847 - {  
1848 - std::cout << " -o nospace";  
1849 - }  
1850 - std::cout << " -C " << progname << " " << whoami << std::endl;  
1851 - // Put output before error so calling from zsh works properly  
1852 - std::string path = progname;  
1853 - size_t slash = path.find('/');  
1854 - if ((slash != 0) && (slash != std::string::npos))  
1855 - {  
1856 - std::cerr << "WARNING: qpdf completion enabled"  
1857 - << " using relative path to qpdf" << std::endl;  
1858 - }  
1859 -}  
1860 -  
1861 -void  
1862 -ArgParser::argCompletionBash()  
1863 -{  
1864 - completionCommon(false);  
1865 -}  
1866 -  
1867 -void  
1868 -ArgParser::argCompletionZsh()  
1869 -{  
1870 - completionCommon(true);  
1871 -}  
1872 -void  
1873 ArgParser::argJsonHelp() 1726 ArgParser::argJsonHelp()
1874 { 1727 {
1875 // Make sure the output looks right on an 80-column display. 1728 // Make sure the output looks right on an 80-column display.
@@ -1962,50 +1815,54 @@ ArgParser::argLinearize() @@ -1962,50 +1815,54 @@ ArgParser::argLinearize()
1962 void 1815 void
1963 ArgParser::argEncrypt() 1816 ArgParser::argEncrypt()
1964 { 1817 {
1965 - ++cur_arg;  
1966 - if (cur_arg + 3 > argc) 1818 + this->accumulated_args.clear();
  1819 + if (this->ap.isCompleting() && this->ap.argsLeft() == 0)
  1820 + {
  1821 + this->ap.insertCompletion("user-password");
  1822 + }
  1823 + this->ap.selectOptionTable(O_ENCRYPT);
  1824 +}
  1825 +
  1826 +void
  1827 +ArgParser::argEncryptPositional(char* arg)
  1828 +{
  1829 + this->accumulated_args.push_back(arg);
  1830 + size_t n_args = this->accumulated_args.size();
  1831 + if (n_args < 3)
1967 { 1832 {
1968 - if (this->bash_completion) 1833 + if (this->ap.isCompleting() && (this->ap.argsLeft() == 0))
1969 { 1834 {
1970 - if (cur_arg == argc)  
1971 - {  
1972 - this->completions.insert("user-password");  
1973 - }  
1974 - else if (cur_arg + 1 == argc) 1835 + if (n_args == 1)
1975 { 1836 {
1976 - this->completions.insert("owner-password"); 1837 + this->ap.insertCompletion("owner-password");
1977 } 1838 }
1978 - else if (cur_arg + 2 == argc) 1839 + else if (n_args == 2)
1979 { 1840 {
1980 - this->completions.insert("40");  
1981 - this->completions.insert("128");  
1982 - this->completions.insert("256"); 1841 + this->ap.insertCompletion("40");
  1842 + this->ap.insertCompletion("128");
  1843 + this->ap.insertCompletion("256");
1983 } 1844 }
1984 - return;  
1985 - }  
1986 - else  
1987 - {  
1988 - usage("insufficient arguments to --encrypt");  
1989 } 1845 }
  1846 + return;
1990 } 1847 }
1991 - o.user_password = argv[cur_arg++];  
1992 - o.owner_password = argv[cur_arg++];  
1993 - std::string len_str = argv[cur_arg]; 1848 + o.user_password = this->accumulated_args.at(0);
  1849 + o.owner_password = this->accumulated_args.at(1);
  1850 + std::string len_str = this->accumulated_args.at(2);
1994 if (len_str == "40") 1851 if (len_str == "40")
1995 { 1852 {
1996 o.keylen = 40; 1853 o.keylen = 40;
1997 - this->option_table = &(this->encrypt40_option_table); 1854 + this->ap.selectOptionTable(O_ENCRYPT_40);
1998 } 1855 }
1999 else if (len_str == "128") 1856 else if (len_str == "128")
2000 { 1857 {
2001 o.keylen = 128; 1858 o.keylen = 128;
2002 - this->option_table = &(this->encrypt128_option_table); 1859 + this->ap.selectOptionTable(O_ENCRYPT_128);
2003 } 1860 }
2004 else if (len_str == "256") 1861 else if (len_str == "256")
2005 { 1862 {
2006 o.keylen = 256; 1863 o.keylen = 256;
2007 o.use_aes = true; 1864 o.use_aes = true;
2008 - this->option_table = &(this->encrypt256_option_table); 1865 + this->ap.selectOptionTable(O_ENCRYPT_256);
2009 } 1866 }
2010 else 1867 else
2011 { 1868 {
@@ -2096,12 +1953,116 @@ ArgParser::argCollate(char* parameter) @@ -2096,12 +1953,116 @@ ArgParser::argCollate(char* parameter)
2096 void 1953 void
2097 ArgParser::argPages() 1954 ArgParser::argPages()
2098 { 1955 {
2099 - ++cur_arg;  
2100 if (! o.page_specs.empty()) 1956 if (! o.page_specs.empty())
2101 { 1957 {
2102 usage("the --pages may only be specified one time"); 1958 usage("the --pages may only be specified one time");
2103 } 1959 }
2104 - o.page_specs = parsePagesOptions(); 1960 + this->accumulated_args.clear();
  1961 + this->ap.selectOptionTable(O_PAGES);
  1962 +}
  1963 +
  1964 +void
  1965 +ArgParser::argPagesPassword(char* parameter)
  1966 +{
  1967 + if (this->pages_password != nullptr)
  1968 + {
  1969 + QTC::TC("qpdf", "qpdf duplicated pages password");
  1970 + usage("--password already specified for this file");
  1971 + }
  1972 + if (this->accumulated_args.size() != 1)
  1973 + {
  1974 + QTC::TC("qpdf", "qpdf misplaced pages password");
  1975 + usage("in --pages, --password must immediately follow a file name");
  1976 + }
  1977 + this->pages_password = parameter;
  1978 +}
  1979 +
  1980 +void
  1981 +ArgParser::argPagesPositional(char* arg)
  1982 +{
  1983 + if (arg == nullptr)
  1984 + {
  1985 + if (this->accumulated_args.empty())
  1986 + {
  1987 + return;
  1988 + }
  1989 + }
  1990 + else
  1991 + {
  1992 + this->accumulated_args.push_back(arg);
  1993 + }
  1994 +
  1995 + char const* file = this->accumulated_args.at(0);
  1996 + char const* range = nullptr;
  1997 +
  1998 + size_t n_args = this->accumulated_args.size();
  1999 + if (n_args >= 2)
  2000 + {
  2001 + range = this->accumulated_args.at(1);
  2002 + }
  2003 +
  2004 + // See if the user omitted the range entirely, in which case we
  2005 + // assume "1-z".
  2006 + char* next_file = nullptr;
  2007 + if (range == nullptr)
  2008 + {
  2009 + if (arg == nullptr)
  2010 + {
  2011 + // The filename or password was the last argument
  2012 + QTC::TC("qpdf", "qpdf pages range omitted at end",
  2013 + this->pages_password == nullptr ? 0 : 1);
  2014 + }
  2015 + else
  2016 + {
  2017 + // We need to accumulate some more arguments
  2018 + return;
  2019 + }
  2020 + }
  2021 + else
  2022 + {
  2023 + try
  2024 + {
  2025 + parseNumrange(range, 0, true);
  2026 + }
  2027 + catch (std::runtime_error& e1)
  2028 + {
  2029 + // The range is invalid. Let's see if it's a file.
  2030 + if (strcmp(range, ".") == 0)
  2031 + {
  2032 + // "." means the input file.
  2033 + QTC::TC("qpdf", "qpdf pages range omitted with .");
  2034 + }
  2035 + else if (QUtil::file_can_be_opened(range))
  2036 + {
  2037 + QTC::TC("qpdf", "qpdf pages range omitted in middle");
  2038 + // Yup, it's a file.
  2039 + }
  2040 + else
  2041 + {
  2042 + // Give the range error
  2043 + usage(e1.what());
  2044 + }
  2045 + next_file = const_cast<char*>(range);
  2046 + range = nullptr;
  2047 + }
  2048 + }
  2049 + if (range == nullptr)
  2050 + {
  2051 + range = "1-z";
  2052 + }
  2053 + o.page_specs.push_back(PageSpec(file, this->pages_password, range));
  2054 + this->accumulated_args.clear();
  2055 + this->pages_password = nullptr;
  2056 + if (next_file != nullptr)
  2057 + {
  2058 + this->accumulated_args.push_back(next_file);
  2059 + }
  2060 +}
  2061 +
  2062 +void
  2063 +ArgParser::argEndPages()
  2064 +{
  2065 + argPagesPositional(nullptr);
2105 if (o.page_specs.empty()) 2066 if (o.page_specs.empty())
2106 { 2067 {
2107 usage("--pages: no page specifications given"); 2068 usage("--pages: no page specifications given");
@@ -2155,15 +2116,15 @@ ArgParser::argRemoveAttachment(char* parameter) @@ -2155,15 +2116,15 @@ ArgParser::argRemoveAttachment(char* parameter)
2155 void 2116 void
2156 ArgParser::argAddAttachment() 2117 ArgParser::argAddAttachment()
2157 { 2118 {
2158 - this->option_table = &(this->add_attachment_option_table);  
2159 o.attachments_to_add.push_back(AddAttachment()); 2119 o.attachments_to_add.push_back(AddAttachment());
  2120 + this->ap.selectOptionTable(O_ATTACHMENT);
2160 } 2121 }
2161 2122
2162 void 2123 void
2163 ArgParser::argCopyAttachments() 2124 ArgParser::argCopyAttachments()
2164 { 2125 {
2165 - this->option_table = &(this->copy_attachments_option_table);  
2166 o.attachments_to_copy.push_back(CopyAttachmentFrom()); 2126 o.attachments_to_copy.push_back(CopyAttachmentFrom());
  2127 + this->ap.selectOptionTable(O_COPY_ATTACHMENT);
2167 } 2128 }
2168 2129
2169 void 2130 void
@@ -2743,7 +2704,6 @@ ArgParser::argEndEncrypt() @@ -2743,7 +2704,6 @@ ArgParser::argEndEncrypt()
2743 o.encrypt = true; 2704 o.encrypt = true;
2744 o.decrypt = false; 2705 o.decrypt = false;
2745 o.copy_encryption = false; 2706 o.copy_encryption = false;
2746 - this->option_table = &(this->main_option_table);  
2747 } 2707 }
2748 2708
2749 void 2709 void
@@ -2795,7 +2755,6 @@ ArgParser::argUOpassword(char* parameter) @@ -2795,7 +2755,6 @@ ArgParser::argUOpassword(char* parameter)
2795 void 2755 void
2796 ArgParser::argEndUnderOverlay() 2756 ArgParser::argEndUnderOverlay()
2797 { 2757 {
2798 - this->option_table = &(this->main_option_table);  
2799 if (0 == o.under_overlay->filename) 2758 if (0 == o.under_overlay->filename)
2800 { 2759 {
2801 usage(o.under_overlay->which + " file not specified"); 2760 usage(o.under_overlay->which + " file not specified");
@@ -2888,7 +2847,6 @@ ArgParser::argEndAddAttachment() @@ -2888,7 +2847,6 @@ ArgParser::argEndAddAttachment()
2888 { 2847 {
2889 static std::string now = QUtil::qpdf_time_to_pdf_time( 2848 static std::string now = QUtil::qpdf_time_to_pdf_time(
2890 QUtil::get_current_qpdf_time()); 2849 QUtil::get_current_qpdf_time());
2891 - this->option_table = &(this->main_option_table);  
2892 auto& cur = o.attachments_to_add.back(); 2850 auto& cur = o.attachments_to_add.back();
2893 if (cur.path.empty()) 2851 if (cur.path.empty())
2894 { 2852 {
@@ -2938,161 +2896,12 @@ ArgParser::argCApassword(char* parameter) @@ -2938,161 +2896,12 @@ ArgParser::argCApassword(char* parameter)
2938 void 2896 void
2939 ArgParser::argEndCopyAttachments() 2897 ArgParser::argEndCopyAttachments()
2940 { 2898 {
2941 - this->option_table = &(this->main_option_table);  
2942 if (o.attachments_to_copy.back().path.empty()) 2899 if (o.attachments_to_copy.back().path.empty())
2943 { 2900 {
2944 usage("copy attachments: no path specified"); 2901 usage("copy attachments: no path specified");
2945 } 2902 }
2946 } 2903 }
2947 2904
2948 -void  
2949 -ArgParser::handleArgFileArguments()  
2950 -{  
2951 - // Support reading arguments from files. Create a new argv. Ensure  
2952 - // that argv itself as well as all its contents are automatically  
2953 - // deleted by using PointerHolder objects to back the pointers in  
2954 - // argv.  
2955 - new_argv.push_back(PointerHolder<char>(true, QUtil::copy_string(argv[0])));  
2956 - for (int i = 1; i < argc; ++i)  
2957 - {  
2958 - char* argfile = 0;  
2959 - if ((strlen(argv[i]) > 1) && (argv[i][0] == '@'))  
2960 - {  
2961 - argfile = 1 + argv[i];  
2962 - if (strcmp(argfile, "-") != 0)  
2963 - {  
2964 - if (! QUtil::file_can_be_opened(argfile))  
2965 - {  
2966 - // The file's not there; treating as regular option  
2967 - argfile = nullptr;  
2968 - }  
2969 - }  
2970 - }  
2971 - if (argfile)  
2972 - {  
2973 - readArgsFromFile(1+argv[i]);  
2974 - }  
2975 - else  
2976 - {  
2977 - new_argv.push_back(  
2978 - PointerHolder<char>(true, QUtil::copy_string(argv[i])));  
2979 - }  
2980 - }  
2981 - argv_ph = PointerHolder<char*>(true, new char*[1+new_argv.size()]);  
2982 - argv = argv_ph.getPointer();  
2983 - for (size_t i = 0; i < new_argv.size(); ++i)  
2984 - {  
2985 - argv[i] = new_argv.at(i).getPointer();  
2986 - }  
2987 - argc = QIntC::to_int(new_argv.size());  
2988 - argv[argc] = 0;  
2989 -}  
2990 -  
2991 -void  
2992 -ArgParser::handleBashArguments()  
2993 -{  
2994 - // Do a minimal job of parsing bash_line into arguments. This  
2995 - // doesn't do everything the shell does (e.g. $(...), variable  
2996 - // expansion, arithmetic, globs, etc.), but it should be good  
2997 - // enough for purposes of handling completion. As we build up the  
2998 - // new argv, we can't use this->new_argv because this code has to  
2999 - // interoperate with @file arguments, so memory for both ways of  
3000 - // fabricating argv has to be protected.  
3001 -  
3002 - bool last_was_backslash = false;  
3003 - enum { st_top, st_squote, st_dquote } state = st_top;  
3004 - std::string arg;  
3005 - for (std::string::iterator iter = bash_line.begin();  
3006 - iter != bash_line.end(); ++iter)  
3007 - {  
3008 - char ch = (*iter);  
3009 - if (last_was_backslash)  
3010 - {  
3011 - arg.append(1, ch);  
3012 - last_was_backslash = false;  
3013 - }  
3014 - else if (ch == '\\')  
3015 - {  
3016 - last_was_backslash = true;  
3017 - }  
3018 - else  
3019 - {  
3020 - bool append = false;  
3021 - switch (state)  
3022 - {  
3023 - case st_top:  
3024 - if (QUtil::is_space(ch))  
3025 - {  
3026 - if (! arg.empty())  
3027 - {  
3028 - bash_argv.push_back(  
3029 - PointerHolder<char>(  
3030 - true, QUtil::copy_string(arg.c_str())));  
3031 - arg.clear();  
3032 - }  
3033 - }  
3034 - else if (ch == '"')  
3035 - {  
3036 - state = st_dquote;  
3037 - }  
3038 - else if (ch == '\'')  
3039 - {  
3040 - state = st_squote;  
3041 - }  
3042 - else  
3043 - {  
3044 - append = true;  
3045 - }  
3046 - break;  
3047 -  
3048 - case st_squote:  
3049 - if (ch == '\'')  
3050 - {  
3051 - state = st_top;  
3052 - }  
3053 - else  
3054 - {  
3055 - append = true;  
3056 - }  
3057 - break;  
3058 -  
3059 - case st_dquote:  
3060 - if (ch == '"')  
3061 - {  
3062 - state = st_top;  
3063 - }  
3064 - else  
3065 - {  
3066 - append = true;  
3067 - }  
3068 - break;  
3069 - }  
3070 - if (append)  
3071 - {  
3072 - arg.append(1, ch);  
3073 - }  
3074 - }  
3075 - }  
3076 - if (bash_argv.empty())  
3077 - {  
3078 - // This can't happen if properly invoked by bash, but ensure  
3079 - // we have a valid argv[0] regardless.  
3080 - bash_argv.push_back(  
3081 - PointerHolder<char>(  
3082 - true, QUtil::copy_string(argv[0])));  
3083 - }  
3084 - // Explicitly discard any non-space-terminated word. The "current  
3085 - // word" is handled specially.  
3086 - bash_argv_ph = PointerHolder<char*>(true, new char*[1+bash_argv.size()]);  
3087 - argv = bash_argv_ph.getPointer();  
3088 - for (size_t i = 0; i < bash_argv.size(); ++i)  
3089 - {  
3090 - argv[i] = bash_argv.at(i).getPointer();  
3091 - }  
3092 - argc = QIntC::to_int(bash_argv.size());  
3093 - argv[argc] = 0;  
3094 -}  
3095 -  
3096 void usageExit(std::string const& msg) 2905 void usageExit(std::string const& msg)
3097 { 2906 {
3098 std::cerr 2907 std::cerr
@@ -3108,7 +2917,7 @@ void usageExit(std::string const&amp; msg) @@ -3108,7 +2917,7 @@ void usageExit(std::string const&amp; msg)
3108 void 2917 void
3109 ArgParser::usage(std::string const& message) 2918 ArgParser::usage(std::string const& message)
3110 { 2919 {
3111 - if (this->bash_completion) 2920 + if (this->ap.isCompleting())
3112 { 2921 {
3113 // This will cause bash to fall back to regular file completion. 2922 // This will cause bash to fall back to regular file completion.
3114 exit(0); 2923 exit(0);
@@ -3234,101 +3043,11 @@ ArgParser::parseNumrange(char const* range, int max, bool throw_error) @@ -3234,101 +3043,11 @@ ArgParser::parseNumrange(char const* range, int max, bool throw_error)
3234 return std::vector<int>(); 3043 return std::vector<int>();
3235 } 3044 }
3236 3045
3237 -std::vector<PageSpec>  
3238 -ArgParser::parsePagesOptions()  
3239 -{  
3240 - auto check_unclosed = [this](char const* arg, int n) {  
3241 - if ((strlen(arg) > 0) && (arg[0] == '-'))  
3242 - {  
3243 - // A common error is to forget to close --pages with --,  
3244 - // so catch this as special case  
3245 - QTC::TC("qpdf", "check unclosed --pages", n);  
3246 - usage("the --pages option must be terminated with -- by itself");  
3247 - }  
3248 - };  
3249 -  
3250 - std::vector<PageSpec> result;  
3251 - while (1)  
3252 - {  
3253 - if ((cur_arg < argc) && (strcmp(argv[cur_arg], "--") == 0))  
3254 - {  
3255 - break;  
3256 - }  
3257 - if (cur_arg + 1 >= argc)  
3258 - {  
3259 - usage("insufficient arguments to --pages");  
3260 - }  
3261 - char const* file = argv[cur_arg++];  
3262 - char const* password = 0;  
3263 - char const* range = argv[cur_arg++];  
3264 - if (! QUtil::file_can_be_opened(file))  
3265 - {  
3266 - check_unclosed(file, 0);  
3267 - }  
3268 - if (strncmp(range, "--password=", 11) == 0)  
3269 - {  
3270 - // Oh, that's the password, not the range  
3271 - if (cur_arg + 1 >= argc)  
3272 - {  
3273 - usage("insufficient arguments to --pages");  
3274 - }  
3275 - password = range + 11;  
3276 - range = argv[cur_arg++];  
3277 - }  
3278 -  
3279 - // See if the user omitted the range entirely, in which case  
3280 - // we assume "1-z".  
3281 - bool range_omitted = false;  
3282 - if (strcmp(range, "--") == 0)  
3283 - {  
3284 - // The filename or password was the last argument  
3285 - QTC::TC("qpdf", "qpdf pages range omitted at end");  
3286 - range_omitted = true;  
3287 - }  
3288 - else  
3289 - {  
3290 - try  
3291 - {  
3292 - parseNumrange(range, 0, true);  
3293 - }  
3294 - catch (std::runtime_error& e1)  
3295 - {  
3296 - // The range is invalid. Let's see if it's a file.  
3297 - range_omitted = true;  
3298 - if (strcmp(range, ".") == 0)  
3299 - {  
3300 - // "." means the input file.  
3301 - QTC::TC("qpdf", "qpdf pages range omitted with .");  
3302 - }  
3303 - else if (QUtil::file_can_be_opened(range))  
3304 - {  
3305 - QTC::TC("qpdf", "qpdf pages range omitted in middle");  
3306 - // Yup, it's a file.  
3307 - }  
3308 - else  
3309 - {  
3310 - check_unclosed(range, 1);  
3311 - // Give the range error  
3312 - usage(e1.what());  
3313 - }  
3314 - }  
3315 - }  
3316 - if (range_omitted)  
3317 - {  
3318 - --cur_arg;  
3319 - range = "1-z";  
3320 - }  
3321 -  
3322 - result.push_back(PageSpec(file, password, range));  
3323 - }  
3324 - return result;  
3325 -}  
3326 -  
3327 void 3046 void
3328 ArgParser::parseUnderOverlayOptions(UnderOverlay* uo) 3047 ArgParser::parseUnderOverlayOptions(UnderOverlay* uo)
3329 { 3048 {
3330 o.under_overlay = uo; 3049 o.under_overlay = uo;
3331 - this->option_table = &(this->under_overlay_option_table); 3050 + this->ap.selectOptionTable(O_UNDER_OVERLAY);
3332 } 3051 }
3333 3052
3334 QPDFPageData::QPDFPageData(std::string const& filename, 3053 QPDFPageData::QPDFPageData(std::string const& filename,
@@ -3374,28 +3093,6 @@ static void parse_version(std::string const&amp; full_version_string, @@ -3374,28 +3093,6 @@ static void parse_version(std::string const&amp; full_version_string,
3374 } 3093 }
3375 3094
3376 void 3095 void
3377 -ArgParser::readArgsFromFile(char const* filename)  
3378 -{  
3379 - std::list<std::string> lines;  
3380 - if (strcmp(filename, "-") == 0)  
3381 - {  
3382 - QTC::TC("qpdf", "qpdf read args from stdin");  
3383 - lines = QUtil::read_lines_from_file(std::cin);  
3384 - }  
3385 - else  
3386 - {  
3387 - QTC::TC("qpdf", "qpdf read args from file");  
3388 - lines = QUtil::read_lines_from_file(filename);  
3389 - }  
3390 - for (std::list<std::string>::iterator iter = lines.begin();  
3391 - iter != lines.end(); ++iter)  
3392 - {  
3393 - new_argv.push_back(  
3394 - PointerHolder<char>(true, QUtil::copy_string((*iter).c_str())));  
3395 - }  
3396 -}  
3397 -  
3398 -void  
3399 ArgParser::parseRotationParameter(std::string const& parameter) 3096 ArgParser::parseRotationParameter(std::string const& parameter)
3400 { 3097 {
3401 std::string angle_str; 3098 std::string angle_str;
@@ -3462,232 +3159,21 @@ ArgParser::parseRotationParameter(std::string const&amp; parameter) @@ -3462,232 +3159,21 @@ ArgParser::parseRotationParameter(std::string const&amp; parameter)
3462 } 3159 }
3463 3160
3464 void 3161 void
3465 -ArgParser::checkCompletion()  
3466 -{  
3467 - // See if we're being invoked from bash completion.  
3468 - std::string bash_point_env;  
3469 - // On Windows with mingw, there have been times when there appears  
3470 - // to be no way to distinguish between an empty environment  
3471 - // variable and an unset variable. There are also conditions under  
3472 - // which bash doesn't set COMP_LINE. Therefore, enter this logic  
3473 - // if either COMP_LINE or COMP_POINT are set. They will both be  
3474 - // set together under ordinary circumstances.  
3475 - bool got_line = QUtil::get_env("COMP_LINE", &bash_line);  
3476 - bool got_point = QUtil::get_env("COMP_POINT", &bash_point_env);  
3477 - if (got_line || got_point)  
3478 - {  
3479 - size_t p = QUtil::string_to_uint(bash_point_env.c_str());  
3480 - if (p < bash_line.length())  
3481 - {  
3482 - // Truncate the line. We ignore everything at or after the  
3483 - // cursor for completion purposes.  
3484 - bash_line = bash_line.substr(0, p);  
3485 - }  
3486 - if (p > bash_line.length())  
3487 - {  
3488 - p = bash_line.length();  
3489 - }  
3490 - // Set bash_cur and bash_prev based on bash_line rather than  
3491 - // relying on argv. This enables us to use bashcompinit to get  
3492 - // completion in zsh too since bashcompinit sets COMP_LINE and  
3493 - // COMP_POINT but doesn't invoke the command with options like  
3494 - // bash does.  
3495 -  
3496 - // p is equal to length of the string. Walk backwards looking  
3497 - // for the first separator. bash_cur is everything after the  
3498 - // last separator, possibly empty.  
3499 - char sep(0);  
3500 - while (p > 0)  
3501 - {  
3502 - --p;  
3503 - char ch = bash_line.at(p);  
3504 - if ((ch == ' ') || (ch == '=') || (ch == ':'))  
3505 - {  
3506 - sep = ch;  
3507 - break;  
3508 - }  
3509 - }  
3510 - if (1+p <= bash_line.length())  
3511 - {  
3512 - bash_cur = bash_line.substr(1+p, std::string::npos);  
3513 - }  
3514 - if ((sep == ':') || (sep == '='))  
3515 - {  
3516 - // Bash sets prev to the non-space separator if any.  
3517 - // Actually, if there are multiple separators in a row,  
3518 - // they are all included in prev, but that detail is not  
3519 - // important to us and not worth coding.  
3520 - bash_prev = bash_line.substr(p, 1);  
3521 - }  
3522 - else  
3523 - {  
3524 - // Go back to the last separator and set prev based on  
3525 - // that.  
3526 - size_t p1 = p;  
3527 - while (p1 > 0)  
3528 - {  
3529 - --p1;  
3530 - char ch = bash_line.at(p1);  
3531 - if ((ch == ' ') || (ch == ':') || (ch == '='))  
3532 - {  
3533 - bash_prev = bash_line.substr(p1 + 1, p - p1 - 1);  
3534 - break;  
3535 - }  
3536 - }  
3537 - }  
3538 - if (bash_prev.empty())  
3539 - {  
3540 - bash_prev = bash_line.substr(0, p);  
3541 - }  
3542 - if (argc == 1)  
3543 - {  
3544 - // This is probably zsh using bashcompinit. There are a  
3545 - // few differences in the expected output.  
3546 - zsh_completion = true;  
3547 - }  
3548 - handleBashArguments();  
3549 - bash_completion = true;  
3550 - }  
3551 -}  
3552 -  
3553 -void  
3554 ArgParser::parseOptions() 3162 ArgParser::parseOptions()
3555 { 3163 {
3556 - checkCompletion();  
3557 - handleArgFileArguments();  
3558 - for (cur_arg = 1; cur_arg < argc; ++cur_arg)  
3559 - {  
3560 - bool help_option = false;  
3561 - auto oep = this->option_table->end();  
3562 - char* arg = argv[cur_arg];  
3563 - char* parameter = nullptr;  
3564 - std::string o_arg(arg);  
3565 - std::string arg_s(arg);  
3566 - if (strcmp(arg, "--") == 0)  
3567 - {  
3568 - // Special case for -- option, which is used to break out  
3569 - // of subparsers.  
3570 - oep = this->option_table->find("--");  
3571 - if (oep == this->option_table->end())  
3572 - {  
3573 - throw std::logic_error("ArgParser: -- handler not registered");  
3574 - }  
3575 - }  
3576 - else if ((arg[0] == '-') && (strcmp(arg, "-") != 0))  
3577 - {  
3578 - ++arg;  
3579 - if (arg[0] == '-')  
3580 - {  
3581 - // Be lax about -arg vs --arg  
3582 - ++arg;  
3583 - }  
3584 - if (strlen(arg) > 0)  
3585 - {  
3586 - // Prevent --=something from being treated as an empty  
3587 - // arg since the empty string in the option table is  
3588 - // for positional arguments.  
3589 - parameter = const_cast<char*>(strchr(1 + arg, '='));  
3590 - }  
3591 - if (parameter)  
3592 - {  
3593 - *parameter++ = 0;  
3594 - }  
3595 -  
3596 - arg_s = arg;  
3597 - if (! (arg_s.empty() || (arg_s.at(0) == '-')))  
3598 - {  
3599 - oep = this->option_table->find(arg_s);  
3600 - }  
3601 -  
3602 - if ((! this->bash_completion) &&  
3603 - (argc == 2) && (cur_arg == 1) &&  
3604 - (oep == this->option_table->end()))  
3605 - {  
3606 - // Handle help option, which is only valid as the sole  
3607 - // option.  
3608 - oep = this->help_option_table.find(arg_s);  
3609 - help_option = true;  
3610 - }  
3611 - }  
3612 - else  
3613 - {  
3614 - // The empty string maps to the positional argument  
3615 - // handler.  
3616 - oep = this->option_table->find("");  
3617 - parameter = arg;  
3618 - }  
3619 -  
3620 - if (oep == this->option_table->end())  
3621 - {  
3622 - usage("unrecognized argument " + o_arg);  
3623 - }  
3624 -  
3625 - OptionEntry& oe = oep->second;  
3626 - if ((oe.parameter_needed && (0 == parameter)) ||  
3627 - ((! oe.choices.empty() &&  
3628 - ((0 == parameter) ||  
3629 - (0 == oe.choices.count(parameter))))))  
3630 - {  
3631 - std::string message =  
3632 - "--" + arg_s + " must be given as --" + arg_s + "=";  
3633 - if (! oe.choices.empty())  
3634 - {  
3635 - QTC::TC("qpdf", "qpdf required choices");  
3636 - message += "{";  
3637 - for (std::set<std::string>::iterator iter =  
3638 - oe.choices.begin();  
3639 - iter != oe.choices.end(); ++iter)  
3640 - {  
3641 - if (iter != oe.choices.begin())  
3642 - {  
3643 - message += ",";  
3644 - }  
3645 - message += *iter;  
3646 - }  
3647 - message += "}";  
3648 - }  
3649 - else if (! oe.parameter_name.empty())  
3650 - {  
3651 - QTC::TC("qpdf", "qpdf required parameter");  
3652 - message += oe.parameter_name;  
3653 - }  
3654 - else  
3655 - {  
3656 - // should not be possible  
3657 - message += "option";  
3658 - }  
3659 - usage(message);  
3660 - }  
3661 - if (oe.bare_arg_handler)  
3662 - {  
3663 - (this->*(oe.bare_arg_handler))();  
3664 - }  
3665 - else if (oe.param_arg_handler)  
3666 - {  
3667 - (this->*(oe.param_arg_handler))(parameter);  
3668 - }  
3669 - if (help_option)  
3670 - {  
3671 - exit(0);  
3672 - }  
3673 - }  
3674 - if (this->bash_completion) 3164 + try
3675 { 3165 {
3676 - handleCompletion(); 3166 + this->ap.parseArgs();
3677 } 3167 }
3678 - else 3168 + catch (QPDFArgParser::Usage& e)
3679 { 3169 {
3680 - doFinalChecks(); 3170 + usage(e.what());
3681 } 3171 }
3682 } 3172 }
3683 3173
3684 void 3174 void
3685 ArgParser::doFinalChecks() 3175 ArgParser::doFinalChecks()
3686 { 3176 {
3687 - if (this->option_table != &(this->main_option_table))  
3688 - {  
3689 - usage("missing -- at end of options");  
3690 - }  
3691 if (o.replace_input) 3177 if (o.replace_input)
3692 { 3178 {
3693 if (o.outfilename) 3179 if (o.outfilename)
@@ -3769,127 +3255,6 @@ ArgParser::doFinalChecks() @@ -3769,127 +3255,6 @@ ArgParser::doFinalChecks()
3769 } 3255 }
3770 } 3256 }
3771 3257
3772 -void  
3773 -ArgParser::addChoicesToCompletions(std::string const& option,  
3774 - std::string const& extra_prefix)  
3775 -{  
3776 - if (this->option_table->count(option) != 0)  
3777 - {  
3778 - OptionEntry& oe = (*this->option_table)[option];  
3779 - for (std::set<std::string>::iterator iter = oe.choices.begin();  
3780 - iter != oe.choices.end(); ++iter)  
3781 - {  
3782 - completions.insert(extra_prefix + *iter);  
3783 - }  
3784 - }  
3785 -}  
3786 -  
3787 -void  
3788 -ArgParser::addOptionsToCompletions()  
3789 -{  
3790 - for (std::map<std::string, OptionEntry>::iterator iter =  
3791 - this->option_table->begin();  
3792 - iter != this->option_table->end(); ++iter)  
3793 - {  
3794 - std::string const& arg = (*iter).first;  
3795 - if (arg == "--")  
3796 - {  
3797 - continue;  
3798 - }  
3799 - OptionEntry& oe = (*iter).second;  
3800 - std::string base = "--" + arg;  
3801 - if (oe.param_arg_handler)  
3802 - {  
3803 - if (zsh_completion)  
3804 - {  
3805 - // zsh doesn't treat = as a word separator, so add all  
3806 - // the options so we don't get a space after the =.  
3807 - addChoicesToCompletions(arg, base + "=");  
3808 - }  
3809 - completions.insert(base + "=");  
3810 - }  
3811 - if (! oe.parameter_needed)  
3812 - {  
3813 - completions.insert(base);  
3814 - }  
3815 - }  
3816 -}  
3817 -  
3818 -void  
3819 -ArgParser::handleCompletion()  
3820 -{  
3821 - std::string extra_prefix;  
3822 - if (this->completions.empty())  
3823 - {  
3824 - // Detect --option=... Bash treats the = as a word separator.  
3825 - std::string choice_option;  
3826 - if (bash_cur.empty() && (bash_prev.length() > 2) &&  
3827 - (bash_prev.at(0) == '-') &&  
3828 - (bash_prev.at(1) == '-') &&  
3829 - (bash_line.at(bash_line.length() - 1) == '='))  
3830 - {  
3831 - choice_option = bash_prev.substr(2, std::string::npos);  
3832 - }  
3833 - else if ((bash_prev == "=") &&  
3834 - (bash_line.length() > (bash_cur.length() + 1)))  
3835 - {  
3836 - // We're sitting at --option=x. Find previous option.  
3837 - size_t end_mark = bash_line.length() - bash_cur.length() - 1;  
3838 - char before_cur = bash_line.at(end_mark);  
3839 - if (before_cur == '=')  
3840 - {  
3841 - size_t space = bash_line.find_last_of(' ', end_mark);  
3842 - if (space != std::string::npos)  
3843 - {  
3844 - std::string candidate =  
3845 - bash_line.substr(space + 1, end_mark - space - 1);  
3846 - if ((candidate.length() > 2) &&  
3847 - (candidate.at(0) == '-') &&  
3848 - (candidate.at(1) == '-'))  
3849 - {  
3850 - choice_option =  
3851 - candidate.substr(2, std::string::npos);  
3852 - }  
3853 - }  
3854 - }  
3855 - }  
3856 - if (! choice_option.empty())  
3857 - {  
3858 - if (zsh_completion)  
3859 - {  
3860 - // zsh wants --option=choice rather than just choice  
3861 - extra_prefix = "--" + choice_option + "=";  
3862 - }  
3863 - addChoicesToCompletions(choice_option, extra_prefix);  
3864 - }  
3865 - else if ((! bash_cur.empty()) && (bash_cur.at(0) == '-'))  
3866 - {  
3867 - addOptionsToCompletions();  
3868 - if (this->argc == 1)  
3869 - {  
3870 - // Help options are valid only by themselves.  
3871 - for (std::map<std::string, OptionEntry>::iterator iter =  
3872 - this->help_option_table.begin();  
3873 - iter != this->help_option_table.end(); ++iter)  
3874 - {  
3875 - this->completions.insert("--" + (*iter).first);  
3876 - }  
3877 - }  
3878 - }  
3879 - }  
3880 - std::string prefix = extra_prefix + bash_cur;  
3881 - for (std::set<std::string>::iterator iter = completions.begin();  
3882 - iter != completions.end(); ++iter)  
3883 - {  
3884 - if (prefix.empty() ||  
3885 - ((*iter).substr(0, prefix.length()) == prefix))  
3886 - {  
3887 - std::cout << *iter << std::endl;  
3888 - }  
3889 - }  
3890 - exit(0);  
3891 -}  
3892 -  
3893 static void set_qpdf_options(QPDF& pdf, Options& o) 3258 static void set_qpdf_options(QPDF& pdf, Options& o)
3894 { 3259 {
3895 if (o.ignore_xref_streams) 3260 if (o.ignore_xref_streams)
qpdf/qpdf.testcov
@@ -255,7 +255,7 @@ QPDF not caching overridden objstm object 0 @@ -255,7 +255,7 @@ QPDF not caching overridden objstm object 0
255 QPDFWriter original obj non-zero gen 0 255 QPDFWriter original obj non-zero gen 0
256 QPDF_optimization indirect outlines 0 256 QPDF_optimization indirect outlines 0
257 QPDF xref space 2 257 QPDF xref space 2
258 -qpdf pages range omitted at end 0 258 +qpdf pages range omitted at end 1
259 qpdf pages range omitted in middle 0 259 qpdf pages range omitted in middle 0
260 qpdf npages 0 260 qpdf npages 0
261 QPDF already reserved object 0 261 QPDF already reserved object 0
@@ -273,8 +273,6 @@ QPDF resolve failure to null 0 @@ -273,8 +273,6 @@ QPDF resolve failure to null 0
273 QPDFWriter preserve unreferenced standard 0 273 QPDFWriter preserve unreferenced standard 0
274 QPDFObjectHandle errors in parsecontent 0 274 QPDFObjectHandle errors in parsecontent 0
275 qpdf same file error 0 275 qpdf same file error 0
276 -qpdf read args from stdin 0  
277 -qpdf read args from file 0  
278 qpdf split-pages %d 0 276 qpdf split-pages %d 0
279 qpdf split-pages .pdf 0 277 qpdf split-pages .pdf 0
280 qpdf split-pages other 0 278 qpdf split-pages other 0
@@ -362,8 +360,6 @@ QPDFOutlineObjectHelper named dest 0 @@ -362,8 +360,6 @@ QPDFOutlineObjectHelper named dest 0
362 QPDFOutlineDocumentHelper name named dest 0 360 QPDFOutlineDocumentHelper name named dest 0
363 QPDFOutlineDocumentHelper string named dest 0 361 QPDFOutlineDocumentHelper string named dest 0
364 QPDFOutlineObjectHelper loop 0 362 QPDFOutlineObjectHelper loop 0
365 -qpdf required parameter 0  
366 -qpdf required choices 0  
367 QPDFObjectHandle merge top type mismatch 0 363 QPDFObjectHandle merge top type mismatch 0
368 QPDFObjectHandle merge shallow copy 0 364 QPDFObjectHandle merge shallow copy 0
369 QPDFObjectHandle merge array 0 365 QPDFObjectHandle merge array 0
@@ -599,7 +595,6 @@ qpdf copy fields non-first from orig 0 @@ -599,7 +595,6 @@ qpdf copy fields non-first from orig 0
599 QPDF resolve duplicated page in insert 0 595 QPDF resolve duplicated page in insert 0
600 QPDFWriter preserve object streams 1 596 QPDFWriter preserve object streams 1
601 QPDFWriter exclude from object stream 0 597 QPDFWriter exclude from object stream 0
602 -check unclosed --pages 1  
603 QPDF_pages findPage not found 0 598 QPDF_pages findPage not found 0
604 qpdf overlay page with no resources 0 599 qpdf overlay page with no resources 0
605 QPDFObjectHandle check ownership 0 600 QPDFObjectHandle check ownership 0
@@ -629,3 +624,5 @@ qpdf-c called qpdf_oh_replace_stream_data 0 @@ -629,3 +624,5 @@ qpdf-c called qpdf_oh_replace_stream_data 0
629 qpdf-c silence oh errors 0 624 qpdf-c silence oh errors 0
630 qpdf-c called qpdf_oh_get_binary_string_value 0 625 qpdf-c called qpdf_oh_get_binary_string_value 0
631 qpdf-c called qpdf_oh_new_binary_string 0 626 qpdf-c called qpdf_oh_new_binary_string 0
  627 +qpdf duplicated pages password 0
  628 +qpdf misplaced pages password 0
qpdf/qtest/qpdf.test
@@ -149,7 +149,7 @@ foreach my $c (@completion_tests) @@ -149,7 +149,7 @@ foreach my $c (@completion_tests)
149 show_ntests(); 149 show_ntests();
150 # ---------- 150 # ----------
151 $td->notify("--- Argument Parsing ---"); 151 $td->notify("--- Argument Parsing ---");
152 -$n_tests += 9; 152 +$n_tests += 12;
153 153
154 $td->runtest("required argument", 154 $td->runtest("required argument",
155 {$td->COMMAND => "qpdf --password minimal.pdf"}, 155 {$td->COMMAND => "qpdf --password minimal.pdf"},
@@ -182,18 +182,33 @@ $td-&gt;runtest(&quot;extra overlay filename&quot;, @@ -182,18 +182,33 @@ $td-&gt;runtest(&quot;extra overlay filename&quot;,
182 $td->EXIT_STATUS => 2}, 182 $td->EXIT_STATUS => 2},
183 $td->NORMALIZE_NEWLINES); 183 $td->NORMALIZE_NEWLINES);
184 $td->runtest("multiple pages options", 184 $td->runtest("multiple pages options",
185 - {$td->COMMAND => "qpdf --pages . -- --pages . --"}, 185 + {$td->COMMAND => "qpdf --pages . --password=x -- --pages . --"},
186 {$td->REGEXP => ".*--pages may only be specified one time.*", 186 {$td->REGEXP => ".*--pages may only be specified one time.*",
187 $td->EXIT_STATUS => 2}, 187 $td->EXIT_STATUS => 2},
188 $td->NORMALIZE_NEWLINES); 188 $td->NORMALIZE_NEWLINES);
189 $td->runtest("bad numeric range detects unclosed --pages", 189 $td->runtest("bad numeric range detects unclosed --pages",
190 {$td->COMMAND => "qpdf --pages . --pages . --"}, 190 {$td->COMMAND => "qpdf --pages . --pages . --"},
191 - {$td->REGEXP => ".*--pages option must be terminated with --.*", 191 + {$td->REGEXP => ".*pages options must be terminated with --.*",
192 $td->EXIT_STATUS => 2}, 192 $td->EXIT_STATUS => 2},
193 $td->NORMALIZE_NEWLINES); 193 $td->NORMALIZE_NEWLINES);
194 $td->runtest("bad file detected as unclosed --pages", 194 $td->runtest("bad file detected as unclosed --pages",
195 {$td->COMMAND => "qpdf --pages . 1 --xyz out"}, 195 {$td->COMMAND => "qpdf --pages . 1 --xyz out"},
196 - {$td->REGEXP => ".*--pages option must be terminated with --.*", 196 + {$td->REGEXP => ".*pages options must be terminated with --.*",
  197 + $td->EXIT_STATUS => 2},
  198 + $td->NORMALIZE_NEWLINES);
  199 +$td->runtest("misplaced pages password 1",
  200 + {$td->COMMAND => "qpdf --pages . 1 --password=z --"},
  201 + {$td->REGEXP => ".*password must immediately follow a file name.*",
  202 + $td->EXIT_STATUS => 2},
  203 + $td->NORMALIZE_NEWLINES);
  204 +$td->runtest("misplaced pages password 2",
  205 + {$td->COMMAND => "qpdf --pages --password=z . 1 --"},
  206 + {$td->REGEXP => ".*password must immediately follow a file name.*",
  207 + $td->EXIT_STATUS => 2},
  208 + $td->NORMALIZE_NEWLINES);
  209 +$td->runtest("duplicated pages password",
  210 + {$td->COMMAND => "qpdf --pages . --password=z --password=z --"},
  211 + {$td->REGEXP => ".*password already specified.*",
197 $td->EXIT_STATUS => 2}, 212 $td->EXIT_STATUS => 2},
198 $td->NORMALIZE_NEWLINES); 213 $td->NORMALIZE_NEWLINES);
199 214