Commit 53ba65eb59d0bced37e73d8bf96a0d7a7285f662

Authored by Jay Berkenbilt
1 parent a301cc53

QPDFArgParser: handle optional choices including help

Handle optional choices in addition to required choices. Refactor the
way help options are added to completion to make it work with optional
help choices.
generate_auto_job
... ... @@ -119,7 +119,7 @@ class Main:
119 119 self.check_keys('top', o, set(
120 120 ['table', 'prefix', 'bare', 'positional',
121 121 'optional_parameter', 'required_parameter',
122   - 'required_choices', 'from_table']))
  122 + 'required_choices', 'optional_choices', 'from_table']))
123 123  
124 124 def to_identifier(self, label, prefix, const):
125 125 identifier = re.sub(r'[^a-zA-Z0-9]', '_', label)
... ... @@ -157,6 +157,9 @@ class Main:
157 157 for i in o.get('required_choices', {}):
158 158 identifier = self.to_identifier(i, prefix, False)
159 159 print(f'void {identifier}(char *);', file=f)
  160 + for i in o.get('optional_choices', {}):
  161 + identifier = self.to_identifier(i, prefix, False)
  162 + print(f'void {identifier}(char *);', file=f)
160 163 if table not in ('main', 'help'):
161 164 identifier = self.to_identifier(table, 'argEnd', False)
162 165 print(f'void {identifier}();', file=f)
... ... @@ -204,9 +207,14 @@ class Main:
204 207 f', "{v}");', file=f)
205 208 for k, v in o.get('required_choices', {}).items():
206 209 identifier = self.to_identifier(k, prefix, False)
207   - print(f'this->ap.addRequiredChoices("{k}", '
  210 + print(f'this->ap.addChoices("{k}", '
  211 + f'p(&ArgParser::{identifier})'
  212 + f', true, {v}_choices);', file=f)
  213 + for k, v in o.get('optional_choices', {}).items():
  214 + identifier = self.to_identifier(k, prefix, False)
  215 + print(f'this->ap.addChoices("{k}", '
208 216 f'p(&ArgParser::{identifier})'
209   - f', {v}_choices);', file=f)
  217 + f', false, {v}_choices);', file=f)
210 218 for o in data['options']:
211 219 table = o['table']
212 220 if 'from_table' not in o:
... ...
include/qpdf/QPDFArgParser.hh
... ... @@ -115,13 +115,14 @@ class QPDFArgParser
115 115 QPDF_DLL
116 116 void addOptionalParameter(std::string const& arg, param_arg_handler_t);
117 117 QPDF_DLL
118   - void addRequiredChoices(
119   - std::string const& arg, param_arg_handler_t, char const** choices);
120   - QPDF_DLL
  118 + void addChoices(
  119 + std::string const& arg, param_arg_handler_t,
  120 + bool required, char const** choices);
121 121  
122 122 // If an option is shared among multiple tables and uses identical
123 123 // handlers, you can just copy it instead of repeating the
124 124 // registration call.
  125 + QPDF_DLL
125 126 void copyFromOtherTable(std::string const& arg,
126 127 std::string const& other_table);
127 128  
... ... @@ -179,7 +180,7 @@ class QPDFArgParser
179 180 bare_arg_handler_t bare_arg_handler;
180 181 param_arg_handler_t param_arg_handler;
181 182 };
182   - friend struct OptionEntry;
  183 + typedef std::map<std::string, OptionEntry> option_table_t;
183 184  
184 185 OptionEntry& registerArg(std::string const& arg);
185 186  
... ... @@ -187,18 +188,20 @@ class QPDFArgParser
187 188  
188 189 void argCompletionBash();
189 190 void argCompletionZsh();
  191 + void argHelp(char*);
190 192  
191 193 void checkCompletion();
192 194 void handleArgFileArguments();
193 195 void handleBashArguments();
194 196 void readArgsFromFile(char const* filename);
195 197 void doFinalChecks();
196   - void addOptionsToCompletions();
197   - void addChoicesToCompletions(std::string const&, std::string const&);
  198 + void addOptionsToCompletions(option_table_t&);
  199 + void addChoicesToCompletions(
  200 + option_table_t&, std::string const&, std::string const&);
  201 + void insertCompletions(
  202 + option_table_t&, std::string const&, std::string const&);
198 203 void handleCompletion();
199 204  
200   - typedef std::map<std::string, OptionEntry> option_table_t;
201   -
202 205 class Members
203 206 {
204 207 friend class QPDFArgParser;
... ...
job.sums
1 1 # Generated by generate_auto_job
2   -generate_auto_job 575569edf2ab0036ed7f810bf506968c73c9209a64ec0ae2ed959f0426291447
3   -job.yml 5de5f1cd3f998274ed4aafa234e61b726a8f96157148ad463970439a96e897bd
4   -libqpdf/qpdf/auto_job_decl.hh fca37543c1a2b7f675374e23b1ab34b30a7f5f2d843c53d4bc7e9a12bf4c3615
5   -libqpdf/qpdf/auto_job_init.hh 7d7dfe96d4da765b8defb646058027584ea8924c604c558402aa7f2d2e61a005
  2 +generate_auto_job 019081046f1bc19f498134eae00344ecfc65b4e52442ee5f1bc80bff99689443
  3 +job.yml 8e5b3fe5a6abea64a5a33977c440a7ac9ecc4516d2a131ed38fd4bc1a73445d7
  4 +libqpdf/qpdf/auto_job_decl.hh 97395ecbe590b23ae04d6cce2080dbd0e998917ff5eeaa5c6aafa91041d3cd6a
  5 +libqpdf/qpdf/auto_job_init.hh 465bf46769559ceb77110d1b9d3293ba9b3595850b49848c31aeabd10aadb4ad
... ...
... ... @@ -51,7 +51,6 @@ choices:
51 51 options:
52 52 - table: help
53 53 bare:
54   - - help
55 54 - version
56 55 - copyright
57 56 - json-help
... ...
libqpdf/QPDFArgParser.cc
... ... @@ -35,6 +35,9 @@ QPDFArgParser::QPDFArgParser(int argc, char* argv[], char const* progname_env) :
35 35 m(new Members(argc, argv, progname_env))
36 36 {
37 37 selectHelpOptionTable();
  38 + char const* help_choices[] = {"all", 0};
  39 + addChoices(
  40 + "help", bindParam(&QPDFArgParser::argHelp, this), false, help_choices);
38 41 addBare("completion-bash",
39 42 std::bind(std::mem_fn(&QPDFArgParser::argCompletionBash), this));
40 43 addBare("completion-zsh",
... ... @@ -139,13 +142,14 @@ QPDFArgParser::addOptionalParameter(
139 142 }
140 143  
141 144 void
142   -QPDFArgParser::addRequiredChoices(
  145 +QPDFArgParser::addChoices(
143 146 std::string const& arg,
144 147 param_arg_handler_t handler,
  148 + bool required,
145 149 char const** choices)
146 150 {
147 151 OptionEntry& oe = registerArg(arg);
148   - oe.parameter_needed = true;
  152 + oe.parameter_needed = required;
149 153 oe.param_arg_handler = handler;
150 154 for (char const** i = choices; *i; ++i)
151 155 {
... ... @@ -254,6 +258,12 @@ QPDFArgParser::argCompletionZsh()
254 258 }
255 259  
256 260 void
  261 +QPDFArgParser::argHelp(char*)
  262 +{
  263 + // QXXXQ
  264 +}
  265 +
  266 +void
257 267 QPDFArgParser::handleArgFileArguments()
258 268 {
259 269 // Support reading arguments from files. Create a new argv. Ensure
... ... @@ -624,10 +634,9 @@ QPDFArgParser::parseArgs()
624 634 }
625 635  
626 636 OptionEntry& oe = oep->second;
627   - if ((oe.parameter_needed && (0 == parameter)) ||
628   - ((! oe.choices.empty() &&
629   - ((0 == parameter) ||
630   - (0 == oe.choices.count(parameter))))))
  637 + if ((oe.parameter_needed && (nullptr == parameter)) ||
  638 + ((! oe.choices.empty() && (nullptr != parameter) &&
  639 + (0 == oe.choices.count(parameter)))))
631 640 {
632 641 std::string message =
633 642 "--" + arg_s + " must be given as --" + arg_s + "=";
... ... @@ -708,12 +717,13 @@ QPDFArgParser::doFinalChecks()
708 717 }
709 718  
710 719 void
711   -QPDFArgParser::addChoicesToCompletions(std::string const& option,
  720 +QPDFArgParser::addChoicesToCompletions(option_table_t& option_table,
  721 + std::string const& option,
712 722 std::string const& extra_prefix)
713 723 {
714   - if (this->m->option_table->count(option) != 0)
  724 + if (option_table.count(option) != 0)
715 725 {
716   - OptionEntry& oe = (*this->m->option_table)[option];
  726 + OptionEntry& oe = option_table[option];
717 727 for (std::set<std::string>::iterator iter = oe.choices.begin();
718 728 iter != oe.choices.end(); ++iter)
719 729 {
... ... @@ -724,18 +734,16 @@ QPDFArgParser::addChoicesToCompletions(std::string const&amp; option,
724 734 }
725 735  
726 736 void
727   -QPDFArgParser::addOptionsToCompletions()
  737 +QPDFArgParser::addOptionsToCompletions(option_table_t& option_table)
728 738 {
729   - for (std::map<std::string, OptionEntry>::iterator iter =
730   - this->m->option_table->begin();
731   - iter != this->m->option_table->end(); ++iter)
  739 + for (auto& iter: option_table)
732 740 {
733   - std::string const& arg = (*iter).first;
  741 + std::string const& arg = iter.first;
734 742 if (arg == "--")
735 743 {
736 744 continue;
737 745 }
738   - OptionEntry& oe = (*iter).second;
  746 + OptionEntry& oe = iter.second;
739 747 std::string base = "--" + arg;
740 748 if (oe.param_arg_handler)
741 749 {
... ... @@ -743,7 +751,7 @@ QPDFArgParser::addOptionsToCompletions()
743 751 {
744 752 // zsh doesn't treat = as a word separator, so add all
745 753 // the options so we don't get a space after the =.
746   - addChoicesToCompletions(arg, base + "=");
  754 + addChoicesToCompletions(option_table, arg, base + "=");
747 755 }
748 756 this->m->completions.insert(base + "=");
749 757 }
... ... @@ -755,6 +763,22 @@ QPDFArgParser::addOptionsToCompletions()
755 763 }
756 764  
757 765 void
  766 +QPDFArgParser::insertCompletions(option_table_t& option_table,
  767 + std::string const& choice_option,
  768 + std::string const& extra_prefix)
  769 +{
  770 + if (! choice_option.empty())
  771 + {
  772 + addChoicesToCompletions(option_table, choice_option, extra_prefix);
  773 + }
  774 + else if ((! this->m->bash_cur.empty()) &&
  775 + (this->m->bash_cur.at(0) == '-'))
  776 + {
  777 + addOptionsToCompletions(option_table);
  778 + }
  779 +}
  780 +
  781 +void
758 782 QPDFArgParser::handleCompletion()
759 783 {
760 784 std::string extra_prefix;
... ... @@ -795,29 +819,17 @@ QPDFArgParser::handleCompletion()
795 819 }
796 820 }
797 821 }
798   - if (! choice_option.empty())
  822 + if (this->m->zsh_completion && (! choice_option.empty()))
799 823 {
800   - if (this->m->zsh_completion)
801   - {
802   - // zsh wants --option=choice rather than just choice
803   - extra_prefix = "--" + choice_option + "=";
804   - }
805   - addChoicesToCompletions(choice_option, extra_prefix);
  824 + // zsh wants --option=choice rather than just choice
  825 + extra_prefix = "--" + choice_option + "=";
806 826 }
807   - else if ((! this->m->bash_cur.empty()) &&
808   - (this->m->bash_cur.at(0) == '-'))
  827 + insertCompletions(*this->m->option_table, choice_option, extra_prefix);
  828 + if (this->m->argc == 1)
809 829 {
810   - addOptionsToCompletions();
811   - if (this->m->argc == 1)
812   - {
813   - // Help options are valid only by themselves.
814   - for (std::map<std::string, OptionEntry>::iterator iter =
815   - this->m->help_option_table.begin();
816   - iter != this->m->help_option_table.end(); ++iter)
817   - {
818   - this->m->completions.insert("--" + (*iter).first);
819   - }
820   - }
  830 + // Help options are valid only by themselves.
  831 + insertCompletions(
  832 + this->m->help_option_table, choice_option, extra_prefix);
821 833 }
822 834 }
823 835 std::string prefix = extra_prefix + this->m->bash_cur;
... ...
libqpdf/QPDFJob_argv.cc
... ... @@ -127,9 +127,11 @@ ArgParser::argCopyright()
127 127 << std::endl;
128 128 }
129 129  
  130 +#if 0
130 131 void
131 132 ArgParser::argHelp()
132 133 {
  134 + // QXXXQ
133 135 std::cout
134 136 // 12345678901234567890123456789012345678901234567890123456789012345678901234567890
135 137 << "Usage: qpdf [options] {infile | --empty} [page_selection_options] outfile\n"
... ... @@ -630,6 +632,7 @@ ArgParser::argHelp()
630 632 << "qpdf to completely ignore warnings. qpdf does not use exit status 1,\n"
631 633 << "since that is used by the shell if it can't execute qpdf.\n";
632 634 }
  635 +#endif
633 636  
634 637 void
635 638 ArgParser::argJsonHelp()
... ...
libqpdf/qpdf/auto_job_decl.hh
... ... @@ -12,7 +12,6 @@ static constexpr char const* O_UNDERLAY_OVERLAY = &quot;underlay/overlay&quot;;
12 12 static constexpr char const* O_ATTACHMENT = "attachment";
13 13 static constexpr char const* O_COPY_ATTACHMENT = "copy attachment";
14 14  
15   -void argHelp();
16 15 void argVersion();
17 16 void argCopyright();
18 17 void argJsonHelp();
... ...
libqpdf/qpdf/auto_job_init.hh
... ... @@ -22,7 +22,6 @@ char const* print128_choices[] = {&quot;full&quot;, &quot;low&quot;, &quot;none&quot;, 0};
22 22 char const* modify128_choices[] = {"all", "annotate", "form", "assembly", "none", 0};
23 23  
24 24 this->ap.selectHelpOptionTable();
25   -this->ap.addBare("help", b(&ArgParser::argHelp));
26 25 this->ap.addBare("version", b(&ArgParser::argVersion));
27 26 this->ap.addBare("copyright", b(&ArgParser::argCopyright));
28 27 this->ap.addBare("json-help", b(&ArgParser::argJsonHelp));
... ... @@ -99,38 +98,38 @@ this-&gt;ap.addRequiredParameter(&quot;remove-attachment&quot;, p(&amp;ArgParser::argRemoveAttach
99 98 this->ap.addRequiredParameter("rotate", p(&ArgParser::argRotate), "[+|-]angle");
100 99 this->ap.addRequiredParameter("show-attachment", p(&ArgParser::argShowAttachment), "attachment");
101 100 this->ap.addRequiredParameter("show-object", p(&ArgParser::argShowObject), "trailer");
102   -this->ap.addRequiredChoices("compress-streams", p(&ArgParser::argCompressStreams), yn_choices);
103   -this->ap.addRequiredChoices("decode-level", p(&ArgParser::argDecodeLevel), decode_level_choices);
104   -this->ap.addRequiredChoices("flatten-annotations", p(&ArgParser::argFlattenAnnotations), flatten_choices);
105   -this->ap.addRequiredChoices("json-key", p(&ArgParser::argJsonKey), json_key_choices);
106   -this->ap.addRequiredChoices("keep-files-open", p(&ArgParser::argKeepFilesOpen), yn_choices);
107   -this->ap.addRequiredChoices("normalize-content", p(&ArgParser::argNormalizeContent), yn_choices);
108   -this->ap.addRequiredChoices("object-streams", p(&ArgParser::argObjectStreams), object_streams_choices);
109   -this->ap.addRequiredChoices("password-mode", p(&ArgParser::argPasswordMode), password_mode_choices);
110   -this->ap.addRequiredChoices("remove-unreferenced-resources", p(&ArgParser::argRemoveUnreferencedResources), remove_unref_choices);
111   -this->ap.addRequiredChoices("stream-data", p(&ArgParser::argStreamData), stream_data_choices);
  101 +this->ap.addChoices("compress-streams", p(&ArgParser::argCompressStreams), true, yn_choices);
  102 +this->ap.addChoices("decode-level", p(&ArgParser::argDecodeLevel), true, decode_level_choices);
  103 +this->ap.addChoices("flatten-annotations", p(&ArgParser::argFlattenAnnotations), true, flatten_choices);
  104 +this->ap.addChoices("json-key", p(&ArgParser::argJsonKey), true, json_key_choices);
  105 +this->ap.addChoices("keep-files-open", p(&ArgParser::argKeepFilesOpen), true, yn_choices);
  106 +this->ap.addChoices("normalize-content", p(&ArgParser::argNormalizeContent), true, yn_choices);
  107 +this->ap.addChoices("object-streams", p(&ArgParser::argObjectStreams), true, object_streams_choices);
  108 +this->ap.addChoices("password-mode", p(&ArgParser::argPasswordMode), true, password_mode_choices);
  109 +this->ap.addChoices("remove-unreferenced-resources", p(&ArgParser::argRemoveUnreferencedResources), true, remove_unref_choices);
  110 +this->ap.addChoices("stream-data", p(&ArgParser::argStreamData), true, stream_data_choices);
112 111 this->ap.registerOptionTable("pages", b(&ArgParser::argEndPages));
113 112 this->ap.addPositional(p(&ArgParser::argPagesPositional));
114 113 this->ap.addRequiredParameter("password", p(&ArgParser::argPagesPassword), "password");
115 114 this->ap.registerOptionTable("encryption", b(&ArgParser::argEndEncryption));
116 115 this->ap.addPositional(p(&ArgParser::argEncPositional));
117 116 this->ap.registerOptionTable("40-bit encryption", b(&ArgParser::argEnd40BitEncryption));
118   -this->ap.addRequiredChoices("extract", p(&ArgParser::argEnc40Extract), yn_choices);
119   -this->ap.addRequiredChoices("annotate", p(&ArgParser::argEnc40Annotate), yn_choices);
120   -this->ap.addRequiredChoices("print", p(&ArgParser::argEnc40Print), yn_choices);
121   -this->ap.addRequiredChoices("modify", p(&ArgParser::argEnc40Modify), yn_choices);
  117 +this->ap.addChoices("extract", p(&ArgParser::argEnc40Extract), true, yn_choices);
  118 +this->ap.addChoices("annotate", p(&ArgParser::argEnc40Annotate), true, yn_choices);
  119 +this->ap.addChoices("print", p(&ArgParser::argEnc40Print), true, yn_choices);
  120 +this->ap.addChoices("modify", p(&ArgParser::argEnc40Modify), true, yn_choices);
122 121 this->ap.registerOptionTable("128-bit encryption", b(&ArgParser::argEnd128BitEncryption));
123 122 this->ap.addBare("cleartext-metadata", b(&ArgParser::argEnc128CleartextMetadata));
124 123 this->ap.addBare("force-V4", b(&ArgParser::argEnc128ForceV4));
125   -this->ap.addRequiredChoices("accessibility", p(&ArgParser::argEnc128Accessibility), yn_choices);
126   -this->ap.addRequiredChoices("extract", p(&ArgParser::argEnc128Extract), yn_choices);
127   -this->ap.addRequiredChoices("print", p(&ArgParser::argEnc128Print), print128_choices);
128   -this->ap.addRequiredChoices("assemble", p(&ArgParser::argEnc128Assemble), yn_choices);
129   -this->ap.addRequiredChoices("annotate", p(&ArgParser::argEnc128Annotate), yn_choices);
130   -this->ap.addRequiredChoices("form", p(&ArgParser::argEnc128Form), yn_choices);
131   -this->ap.addRequiredChoices("modify-other", p(&ArgParser::argEnc128ModifyOther), yn_choices);
132   -this->ap.addRequiredChoices("modify", p(&ArgParser::argEnc128Modify), modify128_choices);
133   -this->ap.addRequiredChoices("use-aes", p(&ArgParser::argEnc128UseAes), yn_choices);
  124 +this->ap.addChoices("accessibility", p(&ArgParser::argEnc128Accessibility), true, yn_choices);
  125 +this->ap.addChoices("extract", p(&ArgParser::argEnc128Extract), true, yn_choices);
  126 +this->ap.addChoices("print", p(&ArgParser::argEnc128Print), true, print128_choices);
  127 +this->ap.addChoices("assemble", p(&ArgParser::argEnc128Assemble), true, yn_choices);
  128 +this->ap.addChoices("annotate", p(&ArgParser::argEnc128Annotate), true, yn_choices);
  129 +this->ap.addChoices("form", p(&ArgParser::argEnc128Form), true, yn_choices);
  130 +this->ap.addChoices("modify-other", p(&ArgParser::argEnc128ModifyOther), true, yn_choices);
  131 +this->ap.addChoices("modify", p(&ArgParser::argEnc128Modify), true, modify128_choices);
  132 +this->ap.addChoices("use-aes", p(&ArgParser::argEnc128UseAes), true, yn_choices);
134 133 this->ap.registerOptionTable("256-bit encryption", b(&ArgParser::argEnd256BitEncryption));
135 134 this->ap.addBare("force-R5", b(&ArgParser::argEnc256ForceR5));
136 135 this->ap.addBare("allow-insecure", b(&ArgParser::argEnc256AllowInsecure));
... ...
libtests/arg_parser.cc
... ... @@ -51,7 +51,7 @@ ArgParser::initOptions()
51 51 ap.addRequiredParameter("salad", p(&ArgParser::handleSalad), "tossed");
52 52 ap.addOptionalParameter("moo", p(&ArgParser::handleMoo));
53 53 char const* choices[] = {"pig", "boar", "sow", 0};
54   - ap.addRequiredChoices("oink", p(&ArgParser::handleOink), choices);
  54 + ap.addChoices("oink", p(&ArgParser::handleOink), true, choices);
55 55 ap.selectHelpOptionTable();
56 56 ap.addBare("version", [this](){ output("3.14159"); });
57 57 ap.selectMainOptionTable();
... ...
libtests/qtest/arg_parser/completion-top-arg-zsh.out
1 1 --baaa
2 2 --completion-zsh
  3 +--help
  4 +--help=
  5 +--help=all
3 6 --moo
4 7 --moo=
5 8 --oink=
... ...