Commit 1c8d53465ff4d8e732498b39e49595b16d6754af
1 parent
b9cd693a
Incorporate job schema generation into generate_auto_job
Showing
5 changed files
with
459 additions
and
7 deletions
README-maintainer
| @@ -128,7 +128,10 @@ Command-line arguments are closely coupled with QPDFJob. To add a new | @@ -128,7 +128,10 @@ Command-line arguments are closely coupled with QPDFJob. To add a new | ||
| 128 | command-line argument, add the option to the appropriate table in | 128 | command-line argument, add the option to the appropriate table in |
| 129 | job.yml. This will automatically declare a method in the private | 129 | job.yml. This will automatically declare a method in the private |
| 130 | ArgParser class in QPDFJob_argv.cc which you have to implement. The | 130 | ArgParser class in QPDFJob_argv.cc which you have to implement. The |
| 131 | -implementation should make calls to methods in QPDFJob. | 131 | +implementation should make calls to methods in QPDFJob. Then, add the |
| 132 | +same option to either the no-json section of job.yml if it is to be | ||
| 133 | +excluded from the job json structure, or add it under the json | ||
| 134 | +structure to the place where it should appear in the json structure. | ||
| 132 | 135 | ||
| 133 | The build will fail until the new option is documented in | 136 | The build will fail until the new option is documented in |
| 134 | manual/cli.rst. To do that, create documentation for the option by | 137 | manual/cli.rst. To do that, create documentation for the option by |
generate_auto_job
| @@ -5,6 +5,7 @@ import argparse | @@ -5,6 +5,7 @@ import argparse | ||
| 5 | import hashlib | 5 | import hashlib |
| 6 | import re | 6 | import re |
| 7 | import yaml | 7 | import yaml |
| 8 | +import json | ||
| 8 | 9 | ||
| 9 | whoami = os.path.basename(sys.argv[0]) | 10 | whoami = os.path.basename(sys.argv[0]) |
| 10 | BANNER = f'''// | 11 | BANNER = f'''// |
| @@ -29,6 +30,7 @@ class Main: | @@ -29,6 +30,7 @@ class Main: | ||
| 29 | 'decl': 'libqpdf/qpdf/auto_job_decl.hh', | 30 | 'decl': 'libqpdf/qpdf/auto_job_decl.hh', |
| 30 | 'init': 'libqpdf/qpdf/auto_job_init.hh', | 31 | 'init': 'libqpdf/qpdf/auto_job_init.hh', |
| 31 | 'help': 'libqpdf/qpdf/auto_job_help.hh', | 32 | 'help': 'libqpdf/qpdf/auto_job_help.hh', |
| 33 | + 'schema': 'libqpdf/qpdf/auto_job_schema.hh', | ||
| 32 | } | 34 | } |
| 33 | SUMS = 'job.sums' | 35 | SUMS = 'job.sums' |
| 34 | 36 | ||
| @@ -199,6 +201,9 @@ class Main: | @@ -199,6 +201,9 @@ class Main: | ||
| 199 | raise Exception( | 201 | raise Exception( |
| 200 | f'help for unknown option {option},' | 202 | f'help for unknown option {option},' |
| 201 | f' lineno={lineno}') | 203 | f' lineno={lineno}') |
| 204 | + if option not in self.help_options: | ||
| 205 | + # QXXXQ also need to exclude help table | ||
| 206 | + self.jdata[option[2:]]['help'] = short_text | ||
| 202 | print(f'ap.addOptionHelp("{option}", "{topic}",' | 207 | print(f'ap.addOptionHelp("{option}", "{topic}",' |
| 203 | f' "{short_text}", R"({long_text})");', file=f) | 208 | f' "{short_text}", R"({long_text})");', file=f) |
| 204 | help_lines += 1 | 209 | help_lines += 1 |
| @@ -226,9 +231,12 @@ class Main: | @@ -226,9 +231,12 @@ class Main: | ||
| 226 | with open('job.yml', 'r') as f: | 231 | with open('job.yml', 'r') as f: |
| 227 | data = yaml.safe_load(f.read()) | 232 | data = yaml.safe_load(f.read()) |
| 228 | self.validate(data) | 233 | self.validate(data) |
| 229 | - self.options_without_help = set( | 234 | + # Add the built-in help options to tables that we populate as |
| 235 | + # we read job.yml since we won't encounter these in job.yml | ||
| 236 | + self.help_options = set( | ||
| 230 | ['--completion-bash', '--completion-zsh', '--help'] | 237 | ['--completion-bash', '--completion-zsh', '--help'] |
| 231 | ) | 238 | ) |
| 239 | + self.options_without_help = set(self.help_options) | ||
| 232 | self.prepare(data) | 240 | self.prepare(data) |
| 233 | with open(self.DESTS['decl'], 'w') as f: | 241 | with open(self.DESTS['decl'], 'w') as f: |
| 234 | print(BANNER, file=f) | 242 | print(BANNER, file=f) |
| @@ -242,6 +250,11 @@ class Main: | @@ -242,6 +250,11 @@ class Main: | ||
| 242 | with open('manual/cli.rst', 'r') as df: | 250 | with open('manual/cli.rst', 'r') as df: |
| 243 | print(BANNER, file=f) | 251 | print(BANNER, file=f) |
| 244 | self.generate_doc(df, f) | 252 | self.generate_doc(df, f) |
| 253 | + self.generate_schema(data) | ||
| 254 | + with open(self.DESTS['schema'], 'w') as f: | ||
| 255 | + print('static constexpr char const* JOB_SCHEMA_DATA = R"(' + | ||
| 256 | + json.dumps(self.schema, indent=2, separators=(',', ': ')) + | ||
| 257 | + ')";', file=f) | ||
| 245 | 258 | ||
| 246 | # Update hashes last to ensure that this will be rerun in the | 259 | # Update hashes last to ensure that this will be rerun in the |
| 247 | # event of a failure. | 260 | # event of a failure. |
| @@ -251,6 +264,24 @@ class Main: | @@ -251,6 +264,24 @@ class Main: | ||
| 251 | def prepare(self, data): | 264 | def prepare(self, data): |
| 252 | self.decls = [] | 265 | self.decls = [] |
| 253 | self.init = [] | 266 | self.init = [] |
| 267 | + self.jdata = { | ||
| 268 | + # option: { | ||
| 269 | + # tables: set(), | ||
| 270 | + # help: string, | ||
| 271 | + # QXXXQ something for registering handler | ||
| 272 | + # } | ||
| 273 | + } | ||
| 274 | + | ||
| 275 | + def add_jdata(flag, table): | ||
| 276 | + nonlocal self | ||
| 277 | + if table == 'help': | ||
| 278 | + self.help_options.add(f'--{flag}') | ||
| 279 | + elif flag in self.jdata: | ||
| 280 | + self.jdata[flag]['tables'].add(table) | ||
| 281 | + else: | ||
| 282 | + self.jdata[flag] = { | ||
| 283 | + 'tables': set([table]), | ||
| 284 | + } | ||
| 254 | 285 | ||
| 255 | self.init.append('auto b = [this](void (ArgParser::*f)()) {') | 286 | self.init.append('auto b = [this](void (ArgParser::*f)()) {') |
| 256 | self.init.append(' return QPDFArgParser::bindBare(f, this);') | 287 | self.init.append(' return QPDFArgParser::bindBare(f, this);') |
| @@ -275,7 +306,7 @@ class Main: | @@ -275,7 +306,7 @@ class Main: | ||
| 275 | self.decls.append('') | 306 | self.decls.append('') |
| 276 | for o in data['options']: | 307 | for o in data['options']: |
| 277 | table = o['table'] | 308 | table = o['table'] |
| 278 | - | 309 | + table_prefix = o.get('prefix', table) |
| 279 | if table == 'main': | 310 | if table == 'main': |
| 280 | self.init.append('this->ap.selectMainOptionTable();') | 311 | self.init.append('this->ap.selectMainOptionTable();') |
| 281 | elif table == 'help': | 312 | elif table == 'help': |
| @@ -296,12 +327,14 @@ class Main: | @@ -296,12 +327,14 @@ class Main: | ||
| 296 | self.decls.append(f'void {identifier}();') | 327 | self.decls.append(f'void {identifier}();') |
| 297 | self.init.append(f'this->ap.addBare("{i}", ' | 328 | self.init.append(f'this->ap.addBare("{i}", ' |
| 298 | f'b(&ArgParser::{identifier}));') | 329 | f'b(&ArgParser::{identifier}));') |
| 330 | + add_jdata(i, table_prefix) | ||
| 299 | for i in o.get('optional_parameter', []): | 331 | for i in o.get('optional_parameter', []): |
| 300 | self.options_without_help.add(f'--{i}') | 332 | self.options_without_help.add(f'--{i}') |
| 301 | identifier = self.to_identifier(i, prefix, False) | 333 | identifier = self.to_identifier(i, prefix, False) |
| 302 | self.decls.append(f'void {identifier}(char *);') | 334 | self.decls.append(f'void {identifier}(char *);') |
| 303 | self.init.append(f'this->ap.addOptionalParameter("{i}", ' | 335 | self.init.append(f'this->ap.addOptionalParameter("{i}", ' |
| 304 | f'p(&ArgParser::{identifier}));') | 336 | f'p(&ArgParser::{identifier}));') |
| 337 | + add_jdata(i, table_prefix) | ||
| 305 | for i, v in o.get('required_parameter', {}).items(): | 338 | for i, v in o.get('required_parameter', {}).items(): |
| 306 | self.options_without_help.add(f'--{i}') | 339 | self.options_without_help.add(f'--{i}') |
| 307 | identifier = self.to_identifier(i, prefix, False) | 340 | identifier = self.to_identifier(i, prefix, False) |
| @@ -309,6 +342,7 @@ class Main: | @@ -309,6 +342,7 @@ class Main: | ||
| 309 | self.init.append(f'this->ap.addRequiredParameter("{i}", ' | 342 | self.init.append(f'this->ap.addRequiredParameter("{i}", ' |
| 310 | f'p(&ArgParser::{identifier})' | 343 | f'p(&ArgParser::{identifier})' |
| 311 | f', "{v}");') | 344 | f', "{v}");') |
| 345 | + add_jdata(i, table_prefix) | ||
| 312 | for i, v in o.get('required_choices', {}).items(): | 346 | for i, v in o.get('required_choices', {}).items(): |
| 313 | self.options_without_help.add(f'--{i}') | 347 | self.options_without_help.add(f'--{i}') |
| 314 | identifier = self.to_identifier(i, prefix, False) | 348 | identifier = self.to_identifier(i, prefix, False) |
| @@ -316,6 +350,7 @@ class Main: | @@ -316,6 +350,7 @@ class Main: | ||
| 316 | self.init.append(f'this->ap.addChoices("{i}", ' | 350 | self.init.append(f'this->ap.addChoices("{i}", ' |
| 317 | f'p(&ArgParser::{identifier})' | 351 | f'p(&ArgParser::{identifier})' |
| 318 | f', true, {v}_choices);') | 352 | f', true, {v}_choices);') |
| 353 | + add_jdata(i, table_prefix) | ||
| 319 | for i, v in o.get('optional_choices', {}).items(): | 354 | for i, v in o.get('optional_choices', {}).items(): |
| 320 | self.options_without_help.add(f'--{i}') | 355 | self.options_without_help.add(f'--{i}') |
| 321 | identifier = self.to_identifier(i, prefix, False) | 356 | identifier = self.to_identifier(i, prefix, False) |
| @@ -323,11 +358,13 @@ class Main: | @@ -323,11 +358,13 @@ class Main: | ||
| 323 | self.init.append(f'this->ap.addChoices("{i}", ' | 358 | self.init.append(f'this->ap.addChoices("{i}", ' |
| 324 | f'p(&ArgParser::{identifier})' | 359 | f'p(&ArgParser::{identifier})' |
| 325 | f', false, {v}_choices);') | 360 | f', false, {v}_choices);') |
| 361 | + add_jdata(i, table_prefix) | ||
| 326 | if table not in ('main', 'help'): | 362 | if table not in ('main', 'help'): |
| 327 | identifier = self.to_identifier(table, 'argEnd', False) | 363 | identifier = self.to_identifier(table, 'argEnd', False) |
| 328 | self.decls.append(f'void {identifier}();') | 364 | self.decls.append(f'void {identifier}();') |
| 329 | for o in data['options']: | 365 | for o in data['options']: |
| 330 | table = o['table'] | 366 | table = o['table'] |
| 367 | + table_prefix = o.get('prefix', table) | ||
| 331 | if 'from_table' not in o: | 368 | if 'from_table' not in o: |
| 332 | continue | 369 | continue |
| 333 | if table == 'main': | 370 | if table == 'main': |
| @@ -341,6 +378,79 @@ class Main: | @@ -341,6 +378,79 @@ class Main: | ||
| 341 | for j in ft['options']: | 378 | for j in ft['options']: |
| 342 | self.init.append('this->ap.copyFromOtherTable' | 379 | self.init.append('this->ap.copyFromOtherTable' |
| 343 | f'("{j}", "{other_table}");') | 380 | f'("{j}", "{other_table}");') |
| 381 | + add_jdata(j, table_prefix) | ||
| 382 | + | ||
| 383 | + def generate_schema(self, data): | ||
| 384 | + # XXX check data['json'] against what we know from jdata. | ||
| 385 | + # Ultimately be able to generate a schema as well as | ||
| 386 | + # JSONHandler and registering stuff. | ||
| 387 | + | ||
| 388 | + # Check to make sure that every command-line option is | ||
| 389 | + # represented either in data['json'] or data['no-json']. | ||
| 390 | + | ||
| 391 | + # Build a list of options that we expect. If an option appears | ||
| 392 | + # once, we just expect to see it once. If it appears in more | ||
| 393 | + # than one options table, we need to see a separate version of | ||
| 394 | + # it for each option table. It is represented prepended in | ||
| 395 | + # job.yml with the table prefix. The table prefix is removed | ||
| 396 | + # in the schema. | ||
| 397 | + expected = {} | ||
| 398 | + for k, v in self.jdata.items(): | ||
| 399 | + tables = v['tables'] | ||
| 400 | + if len(tables) == 1: | ||
| 401 | + expected[k] = {**v} | ||
| 402 | + else: | ||
| 403 | + for t in sorted(tables): | ||
| 404 | + expected[f'{t}.{k}'] = {**v} | ||
| 405 | + for _, v in expected.items(): | ||
| 406 | + del v['tables'] | ||
| 407 | + options_seen = set(data['no-json']) | ||
| 408 | + | ||
| 409 | + self.schema = {} | ||
| 410 | + | ||
| 411 | + def option_to_json_key(s): | ||
| 412 | + return self.to_identifier(s, '', False) | ||
| 413 | + | ||
| 414 | + # Walk through the json information building the schema as we | ||
| 415 | + # go. This verifies consistency between command-line options | ||
| 416 | + # and the json section of the data and builds up a schema by | ||
| 417 | + # populating with help information as available. | ||
| 418 | + def build_schema(j, s): | ||
| 419 | + for k, v in j.items(): | ||
| 420 | + if not (k in expected or | ||
| 421 | + k.startswith('_') or | ||
| 422 | + isinstance(v, str)): | ||
| 423 | + raise Exception(f'json: unknown key {k}') | ||
| 424 | + if k.startswith('_'): | ||
| 425 | + schema_key = k[1:] | ||
| 426 | + else: | ||
| 427 | + schema_key = re.sub(r'[^\.]+\.', '', k) | ||
| 428 | + schema_key = option_to_json_key(schema_key) | ||
| 429 | + schema_value = v | ||
| 430 | + if k in expected: | ||
| 431 | + options_seen.add(re.sub('^_', '', k)) | ||
| 432 | + if v is None: | ||
| 433 | + schema_value = re.sub( | ||
| 434 | + r'--(\S+)', | ||
| 435 | + lambda x: option_to_json_key(x.group(1)), | ||
| 436 | + expected[k]['help']) | ||
| 437 | + if (isinstance(v, dict)): | ||
| 438 | + schema_value = {} | ||
| 439 | + build_schema(v, schema_value) | ||
| 440 | + elif (isinstance(v, list)): | ||
| 441 | + if len(v) != 1: | ||
| 442 | + raise Exception('json contains array with length != 1') | ||
| 443 | + if isinstance(v[0], dict): | ||
| 444 | + schema_value = [{}] | ||
| 445 | + build_schema(v[0], schema_value[0]) | ||
| 446 | + elif schema_value is None: | ||
| 447 | + raise Exception(f'unknown schema value for {k}') | ||
| 448 | + s[schema_key] = schema_value | ||
| 449 | + | ||
| 450 | + build_schema(data['json'], self.schema) | ||
| 451 | + if options_seen != set(expected.keys()): | ||
| 452 | + raise Exception('missing from json: ' + | ||
| 453 | + str(set(expected.keys()) - options_seen)) | ||
| 344 | 454 | ||
| 345 | def check_keys(self, what, d, exp): | 455 | def check_keys(self, what, d, exp): |
| 346 | if not isinstance(d, dict): | 456 | if not isinstance(d, dict): |
| @@ -351,7 +461,8 @@ class Main: | @@ -351,7 +461,8 @@ class Main: | ||
| 351 | exit(f'{what}: unknown keys = {extra}') | 461 | exit(f'{what}: unknown keys = {extra}') |
| 352 | 462 | ||
| 353 | def validate(self, data): | 463 | def validate(self, data): |
| 354 | - self.check_keys('top', data, set(['choices', 'options'])) | 464 | + self.check_keys('top', data, set( |
| 465 | + ['choices', 'options', 'no-json', 'json'])) | ||
| 355 | for o in data['options']: | 466 | for o in data['options']: |
| 356 | self.check_keys('top', o, set( | 467 | self.check_keys('top', o, set( |
| 357 | ['table', 'prefix', 'bare', 'positional', | 468 | ['table', 'prefix', 'bare', 'positional', |
| @@ -363,7 +474,10 @@ class Main: | @@ -363,7 +474,10 @@ class Main: | ||
| 363 | if const: | 474 | if const: |
| 364 | identifier = f'{prefix}_{identifier.upper()}' | 475 | identifier = f'{prefix}_{identifier.upper()}' |
| 365 | else: | 476 | else: |
| 366 | - identifier = f'{prefix}_{identifier.lower()}' | 477 | + if prefix: |
| 478 | + identifier = f'{prefix}_{identifier.lower()}' | ||
| 479 | + else: | ||
| 480 | + identifier = identifier.lower() | ||
| 367 | identifier = re.sub(r'_([a-z])', | 481 | identifier = re.sub(r'_([a-z])', |
| 368 | lambda x: x.group(1).upper(), | 482 | lambda x: x.group(1).upper(), |
| 369 | identifier).replace('_', '') | 483 | identifier).replace('_', '') |
job.sums
| 1 | # Generated by generate_auto_job | 1 | # Generated by generate_auto_job |
| 2 | -generate_auto_job b70f64314f1ae1f100fa6a11975dee5f7669038e2a619b6c9da1e5230db1dd1b | ||
| 3 | -job.yml 8177cadf41096efdc174f04daadfe5d98c592ad44ad10cb96537521fd79a801a | 2 | +generate_auto_job 0758b244fc4e2d3e440883072d2740bc4cdb26c5aa8de938f028afd7d83fad79 |
| 3 | +job.yml 2856c2635d42f0a58717d3ffce3125816d8f98ff17245c4b7a0669d70cd68b84 | ||
| 4 | libqpdf/qpdf/auto_job_decl.hh 97395ecbe590b23ae04d6cce2080dbd0e998917ff5eeaa5c6aafa91041d3cd6a | 4 | libqpdf/qpdf/auto_job_decl.hh 97395ecbe590b23ae04d6cce2080dbd0e998917ff5eeaa5c6aafa91041d3cd6a |
| 5 | libqpdf/qpdf/auto_job_help.hh 2653faaf59415bec81c3a85d426239d52b609ac24faba34ec2d26f00710dd2c6 | 5 | libqpdf/qpdf/auto_job_help.hh 2653faaf59415bec81c3a85d426239d52b609ac24faba34ec2d26f00710dd2c6 |
| 6 | libqpdf/qpdf/auto_job_init.hh 465bf46769559ceb77110d1b9d3293ba9b3595850b49848c31aeabd10aadb4ad | 6 | libqpdf/qpdf/auto_job_init.hh 465bf46769559ceb77110d1b9d3293ba9b3595850b49848c31aeabd10aadb4ad |
| 7 | +libqpdf/qpdf/auto_job_schema.hh c91a4e182e088797b70dda94af03ca32d360f3564890132da2a8bdc3c4432423 | ||
| 7 | manual/_ext/qpdf.py 855fe12de5af7a10bb24be6ecc4d5dff4c84ac58cf388a13be6bbb394346a67d | 8 | manual/_ext/qpdf.py 855fe12de5af7a10bb24be6ecc4d5dff4c84ac58cf388a13be6bbb394346a67d |
| 8 | manual/cli.rst b136c7f33a538c580b081a7e802c27635aad2a4229efa0eb0736466116b7aa90 | 9 | manual/cli.rst b136c7f33a538c580b081a7e802c27635aad2a4229efa0eb0736466116b7aa90 |
job.yml
| @@ -217,3 +217,161 @@ options: | @@ -217,3 +217,161 @@ options: | ||
| 217 | required_parameter: | 217 | required_parameter: |
| 218 | prefix: prefix | 218 | prefix: prefix |
| 219 | password: password | 219 | password: password |
| 220 | +no-json: | ||
| 221 | + - preserve-unreferenced-resources | ||
| 222 | +json: | ||
| 223 | + # The structure of this section defines what the json input to | ||
| 224 | + # QPDFJob looks like. If a key starts with underscore or has a value | ||
| 225 | + # that is a string, it does not map to a command-line argument. If | ||
| 226 | + # value is null, its properties and help come from other information | ||
| 227 | + # known by generate_auto_job. This information is used to construct | ||
| 228 | + # a "schema" (as in JSON.hh) for the json input to QPDFJob. The | ||
| 229 | + # leading underscore is removed. | ||
| 230 | + _input: | ||
| 231 | + _file: | ||
| 232 | + _name: "input filename" | ||
| 233 | + main.password: | ||
| 234 | + password-file: | ||
| 235 | + empty: | ||
| 236 | + _output: | ||
| 237 | + _file: | ||
| 238 | + _name: "output filename" | ||
| 239 | + replace-input: | ||
| 240 | + split-pages: | ||
| 241 | + _options: | ||
| 242 | + qdf: | ||
| 243 | + preserve-unreferenced: | ||
| 244 | + newline-before-endstream: | ||
| 245 | + normalize-content: | ||
| 246 | + stream-data: | ||
| 247 | + compress-streams: | ||
| 248 | + recompress-flate: | ||
| 249 | + decode-level: | ||
| 250 | + decrypt: | ||
| 251 | + static-aes-iv: | ||
| 252 | + static-id: | ||
| 253 | + no-original-object-ids: | ||
| 254 | + copy-encryption: | ||
| 255 | + encryption-file-password: | ||
| 256 | + linearize: | ||
| 257 | + linearize-pass1: | ||
| 258 | + object-streams: | ||
| 259 | + min-version: | ||
| 260 | + force-version: | ||
| 261 | + progress: | ||
| 262 | + encrypt: | ||
| 263 | + user-password: "user password" | ||
| 264 | + owner-password: "owner password" | ||
| 265 | + key-length: "key length: 48, 128, 256" | ||
| 266 | + _40-bit: | ||
| 267 | + Enc40.annotate: | ||
| 268 | + Enc40.extract: | ||
| 269 | + Enc40.modify: | ||
| 270 | + Enc40.print: | ||
| 271 | + _128-bit: | ||
| 272 | + Enc128.accessibility: | ||
| 273 | + Enc128.annotate: | ||
| 274 | + Enc128.assemble: | ||
| 275 | + Enc128.cleartext-metadata: | ||
| 276 | + Enc128.extract: | ||
| 277 | + Enc128.form: | ||
| 278 | + Enc128.modify-other: | ||
| 279 | + Enc128.modify: | ||
| 280 | + Enc128.print: | ||
| 281 | + force-V4: | ||
| 282 | + use-aes: | ||
| 283 | + _256-bit: | ||
| 284 | + Enc256.accessibility: | ||
| 285 | + Enc256.annotate: | ||
| 286 | + Enc256.assemble: | ||
| 287 | + Enc256.cleartext-metadata: | ||
| 288 | + Enc256.extract: | ||
| 289 | + Enc256.form: | ||
| 290 | + Enc256.modify-other: | ||
| 291 | + Enc256.modify: | ||
| 292 | + Enc256.print: | ||
| 293 | + allow-insecure: | ||
| 294 | + force-R5: | ||
| 295 | + _options: | ||
| 296 | + allow-weak-crypto: | ||
| 297 | + deterministic-id: | ||
| 298 | + keep-files-open: | ||
| 299 | + keep-files-open-threshold: | ||
| 300 | + no-warn: | ||
| 301 | + verbose: | ||
| 302 | + warning-exit-0: | ||
| 303 | + ignore-xref-streams: | ||
| 304 | + password-is-hex-key: | ||
| 305 | + password-mode: | ||
| 306 | + suppress-password-recovery: | ||
| 307 | + suppress-recovery: | ||
| 308 | + _inspect: | ||
| 309 | + check: | ||
| 310 | + check-linearization: | ||
| 311 | + filtered-stream-data: | ||
| 312 | + is-encrypted: | ||
| 313 | + raw-stream-data: | ||
| 314 | + requires-password: | ||
| 315 | + show-encryption: | ||
| 316 | + show-encryption-key: | ||
| 317 | + show-linearization: | ||
| 318 | + show-npages: | ||
| 319 | + show-object: | ||
| 320 | + show-pages: | ||
| 321 | + show-xref: | ||
| 322 | + with-images: | ||
| 323 | + list-attachments: | ||
| 324 | + show-attachment: | ||
| 325 | + json: | ||
| 326 | + json-key: | ||
| 327 | + - null | ||
| 328 | + json-object: | ||
| 329 | + - null | ||
| 330 | + _transform: | ||
| 331 | + coalesce-contents: | ||
| 332 | + compression-level: | ||
| 333 | + externalize-inline-images: | ||
| 334 | + ii-min-bytes: | ||
| 335 | + remove-unreferenced-resources: | ||
| 336 | + _modify: | ||
| 337 | + add-attachment: | ||
| 338 | + - file: "attachment to add" | ||
| 339 | + creationdate: | ||
| 340 | + description: | ||
| 341 | + filename: | ||
| 342 | + key: | ||
| 343 | + mimetype: | ||
| 344 | + moddate: | ||
| 345 | + replace: | ||
| 346 | + remove-attachment: | ||
| 347 | + copy-attachments-from: | ||
| 348 | + - file: "attachment source filename" | ||
| 349 | + CopyAtt.password: | ||
| 350 | + prefix: | ||
| 351 | + collate: | ||
| 352 | + flatten-annotations: | ||
| 353 | + flatten-rotation: | ||
| 354 | + generate-appearances: | ||
| 355 | + keep-inline-images: | ||
| 356 | + oi-min-area: | ||
| 357 | + oi-min-height: | ||
| 358 | + oi-min-width: | ||
| 359 | + optimize-images: | ||
| 360 | + pages: | ||
| 361 | + - file: "source for for pages" | ||
| 362 | + Pages.password: | ||
| 363 | + range: "page range" | ||
| 364 | + remove-page-labels: | ||
| 365 | + rotate: | ||
| 366 | + overlay: | ||
| 367 | + file: "source file for overlay" | ||
| 368 | + UO.password: | ||
| 369 | + from: | ||
| 370 | + repeat: | ||
| 371 | + to: | ||
| 372 | + underlay: | ||
| 373 | + file: "source file for underlay" | ||
| 374 | + UO.password: | ||
| 375 | + from: | ||
| 376 | + repeat: | ||
| 377 | + to: |
libqpdf/qpdf/auto_job_schema.hh
0 โ 100644
| 1 | +static constexpr char const* JOB_SCHEMA_DATA = R"({ | ||
| 2 | + "input": { | ||
| 3 | + "file": { | ||
| 4 | + "name": "input filename", | ||
| 5 | + "password": "specify password", | ||
| 6 | + "passwordFile": "read password from a file" | ||
| 7 | + }, | ||
| 8 | + "empty": "empty input file" | ||
| 9 | + }, | ||
| 10 | + "output": { | ||
| 11 | + "file": { | ||
| 12 | + "name": "output filename" | ||
| 13 | + }, | ||
| 14 | + "replaceInput": "replace input with output", | ||
| 15 | + "splitPages": "write pages to separate files", | ||
| 16 | + "options": { | ||
| 17 | + "qdf": "enable viewing PDF code in a text editor", | ||
| 18 | + "preserveUnreferenced": "preserve unreferenced objects", | ||
| 19 | + "newlineBeforeEndstream": "force a newline before endstream", | ||
| 20 | + "normalizeContent": "fix newlines in content streams", | ||
| 21 | + "streamData": "control stream compression", | ||
| 22 | + "compressStreams": "compress uncompressed streams", | ||
| 23 | + "recompressFlate": "uncompress and recompress flate", | ||
| 24 | + "decodeLevel": "control which streams to uncompress", | ||
| 25 | + "decrypt": "remove encryption from input file", | ||
| 26 | + "staticAesIv": "use a fixed AES vector", | ||
| 27 | + "staticId": "use a fixed document ID", | ||
| 28 | + "noOriginalObjectIds": "omit original object ID in qdf", | ||
| 29 | + "copyEncryption": "copy another file's encryption details", | ||
| 30 | + "encryptionFilePassword": "supply password for copyEncryption", | ||
| 31 | + "linearize": "linearize (web-optimize) output", | ||
| 32 | + "linearizePass1": "save pass 1 of linearization", | ||
| 33 | + "objectStreams": "control use of object streams", | ||
| 34 | + "minVersion": "set minimum PDF version", | ||
| 35 | + "forceVersion": "set output PDF version", | ||
| 36 | + "progress": "show progress when writing", | ||
| 37 | + "encrypt": { | ||
| 38 | + "userPassword": "user password", | ||
| 39 | + "ownerPassword": "owner password", | ||
| 40 | + "keyLength": "key length: 48, 128, 256", | ||
| 41 | + "40Bit": { | ||
| 42 | + "annotate": "restrict document annotation", | ||
| 43 | + "extract": "restrict text/graphic extraction", | ||
| 44 | + "modify": "restrict document modification", | ||
| 45 | + "print": "restrict printing" | ||
| 46 | + }, | ||
| 47 | + "128Bit": { | ||
| 48 | + "accessibility": "restrict document accessibility", | ||
| 49 | + "annotate": "restrict document annotation", | ||
| 50 | + "assemble": "restrict document assembly", | ||
| 51 | + "cleartextMetadata": "don't encrypt metadata", | ||
| 52 | + "extract": "restrict text/graphic extraction", | ||
| 53 | + "form": "restrict form filling", | ||
| 54 | + "modifyOther": "restrict other modifications", | ||
| 55 | + "modify": "restrict document modification", | ||
| 56 | + "print": "restrict printing", | ||
| 57 | + "forceV4": "force V=4 in encryption dictionary", | ||
| 58 | + "useAes": "use AES with 128-bit encryption" | ||
| 59 | + }, | ||
| 60 | + "256Bit": { | ||
| 61 | + "accessibility": "restrict document accessibility", | ||
| 62 | + "annotate": "restrict document annotation", | ||
| 63 | + "assemble": "restrict document assembly", | ||
| 64 | + "cleartextMetadata": "don't encrypt metadata", | ||
| 65 | + "extract": "restrict text/graphic extraction", | ||
| 66 | + "form": "restrict form filling", | ||
| 67 | + "modifyOther": "restrict other modifications", | ||
| 68 | + "modify": "restrict document modification", | ||
| 69 | + "print": "restrict printing", | ||
| 70 | + "allowInsecure": "allow empty owner passwords", | ||
| 71 | + "forceR5": "use unsupported R=5 encryption" | ||
| 72 | + } | ||
| 73 | + } | ||
| 74 | + } | ||
| 75 | + }, | ||
| 76 | + "options": { | ||
| 77 | + "allowWeakCrypto": "allow insecure cryptographic algorithms", | ||
| 78 | + "deterministicId": "generate ID deterministically", | ||
| 79 | + "keepFilesOpen": "manage keeping multiple files open", | ||
| 80 | + "keepFilesOpenThreshold": "set threshold for keepFilesOpen", | ||
| 81 | + "noWarn": "suppress printing warning messages", | ||
| 82 | + "verbose": "print additional information", | ||
| 83 | + "warningExit0": "exit 0 even with warnings", | ||
| 84 | + "ignoreXrefStreams": "use xref tables rather than streams", | ||
| 85 | + "passwordIsHexKey": "provide hex-encoded encryption key", | ||
| 86 | + "passwordMode": "tweak how qpdf encodes passwords", | ||
| 87 | + "suppressPasswordRecovery": "don't try different password encodings", | ||
| 88 | + "suppressRecovery": "suppress error recovery" | ||
| 89 | + }, | ||
| 90 | + "inspect": { | ||
| 91 | + "check": "partially check whether PDF is valid", | ||
| 92 | + "checkLinearization": "check linearization tables", | ||
| 93 | + "filteredStreamData": "show filtered stream data", | ||
| 94 | + "isEncrypted": "silently test whether a file is encrypted", | ||
| 95 | + "rawStreamData": "show raw stream data", | ||
| 96 | + "requiresPassword": "silently test a file's password", | ||
| 97 | + "showEncryption": "information about encrypted files", | ||
| 98 | + "showEncryptionKey": "show key with showEncryption", | ||
| 99 | + "showLinearization": "show linearization hint tables", | ||
| 100 | + "showNpages": "show number of pages", | ||
| 101 | + "showObject": "show contents of an object", | ||
| 102 | + "showPages": "display page dictionary information", | ||
| 103 | + "showXref": "show cross reference data", | ||
| 104 | + "withImages": "include image details with showPages", | ||
| 105 | + "listAttachments": "list embedded files", | ||
| 106 | + "showAttachment": "export an embedded file", | ||
| 107 | + "json": "show file in json format", | ||
| 108 | + "jsonKey": [ | ||
| 109 | + null | ||
| 110 | + ], | ||
| 111 | + "jsonObject": [ | ||
| 112 | + null | ||
| 113 | + ] | ||
| 114 | + }, | ||
| 115 | + "transform": { | ||
| 116 | + "coalesceContents": "combine content streams", | ||
| 117 | + "compressionLevel": "set compression level for flate", | ||
| 118 | + "externalizeInlineImages": "convert inline to regular images", | ||
| 119 | + "iiMinBytes": "set minimum size for externalizeInlineImages", | ||
| 120 | + "removeUnreferencedResources": "remove unreferenced page resources" | ||
| 121 | + }, | ||
| 122 | + "modify": { | ||
| 123 | + "addAttachment": [ | ||
| 124 | + { | ||
| 125 | + "file": "attachment to add", | ||
| 126 | + "creationdate": "set attachment's creation date", | ||
| 127 | + "description": "set attachment's description", | ||
| 128 | + "filename": "set attachment's displayed filename", | ||
| 129 | + "key": "specify attachment key", | ||
| 130 | + "mimetype": "attachment mime type, e.g. application/pdf", | ||
| 131 | + "moddate": "set attachment's modification date", | ||
| 132 | + "replace": "replace attachment with same key" | ||
| 133 | + } | ||
| 134 | + ], | ||
| 135 | + "removeAttachment": "remove an embedded file", | ||
| 136 | + "copyAttachmentsFrom": [ | ||
| 137 | + { | ||
| 138 | + "file": "attachment source filename", | ||
| 139 | + "password": "specify password", | ||
| 140 | + "prefix": "key prefix for copying attachments" | ||
| 141 | + } | ||
| 142 | + ], | ||
| 143 | + "collate": "collate with pages", | ||
| 144 | + "flattenAnnotations": "push annotations into content", | ||
| 145 | + "flattenRotation": "remove rotation from page dictionary", | ||
| 146 | + "generateAppearances": "generate appearances for form fields", | ||
| 147 | + "keepInlineImages": "exclude inline images from optimization", | ||
| 148 | + "oiMinArea": "minimum area for optimizeImages", | ||
| 149 | + "oiMinHeight": "minimum height for optimizeImages", | ||
| 150 | + "oiMinWidth": "minimum width for optimizeImages", | ||
| 151 | + "optimizeImages": "use efficient compression for images", | ||
| 152 | + "pages": [ | ||
| 153 | + { | ||
| 154 | + "file": "source for for pages", | ||
| 155 | + "password": "specify password", | ||
| 156 | + "range": "page range" | ||
| 157 | + } | ||
| 158 | + ], | ||
| 159 | + "removePageLabels": "remove explicit page numbers", | ||
| 160 | + "rotate": "rotate pages", | ||
| 161 | + "overlay": { | ||
| 162 | + "file": "source file for overlay", | ||
| 163 | + "password": "specify password", | ||
| 164 | + "from": "source pages for underlay/overlay", | ||
| 165 | + "repeat": "overlay/underlay pages to repeat", | ||
| 166 | + "to": "destination pages for underlay/overlay" | ||
| 167 | + }, | ||
| 168 | + "underlay": { | ||
| 169 | + "file": "source file for underlay", | ||
| 170 | + "password": "specify password", | ||
| 171 | + "from": "source pages for underlay/overlay", | ||
| 172 | + "repeat": "overlay/underlay pages to repeat", | ||
| 173 | + "to": "destination pages for underlay/overlay" | ||
| 174 | + } | ||
| 175 | + } | ||
| 176 | +})"; |