mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-22 02:49:00 +00:00
233 lines
8.7 KiB
Python
Executable File
233 lines
8.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import os
|
|
import sys
|
|
import argparse
|
|
import hashlib
|
|
import re
|
|
import yaml
|
|
|
|
whoami = os.path.basename(sys.argv[0])
|
|
BANNER = f'''//
|
|
// This file is automatically generated by {whoami}.
|
|
// Edits will be automatically overwritten if the build is
|
|
// run in maintainer mode.
|
|
//'''
|
|
|
|
|
|
def warn(*args, **kwargs):
|
|
print(*args, file=sys.stderr, **kwargs)
|
|
|
|
|
|
class Main:
|
|
SOURCES = [whoami, 'job.yml']
|
|
DESTS = {
|
|
'decl': 'libqpdf/qpdf/auto_job_decl.hh',
|
|
'init': 'libqpdf/qpdf/auto_job_init.hh',
|
|
}
|
|
SUMS = 'job.sums'
|
|
|
|
def main(self, args=sys.argv[1:], prog=whoami):
|
|
options = self.parse_args(args, prog)
|
|
self.top(options)
|
|
|
|
def parse_args(self, args, prog):
|
|
parser = argparse.ArgumentParser(
|
|
prog=prog,
|
|
description='Generate files for QPDFJob',
|
|
)
|
|
mxg = parser.add_mutually_exclusive_group(required=True)
|
|
mxg.add_argument('--check',
|
|
help='update checksums if files are not up to date',
|
|
action='store_true', default=False)
|
|
mxg.add_argument('--generate',
|
|
help='generate files from sources',
|
|
action='store_true', default=False)
|
|
return parser.parse_args(args)
|
|
|
|
def top(self, options):
|
|
if options.check:
|
|
self.check()
|
|
elif options.generate:
|
|
self.generate()
|
|
else:
|
|
exit(f'{whoami} unknown mode')
|
|
|
|
def get_hashes(self):
|
|
hashes = {}
|
|
for i in sorted([*self.SOURCES, *self.DESTS.values()]):
|
|
m = hashlib.sha256()
|
|
try:
|
|
with open(i, 'rb') as f:
|
|
m.update(f.read())
|
|
hashes[i] = m.hexdigest()
|
|
except FileNotFoundError:
|
|
pass
|
|
return hashes
|
|
|
|
def check(self):
|
|
hashes = self.get_hashes()
|
|
match = False
|
|
try:
|
|
old_hashes = {}
|
|
with open(self.SUMS, 'r') as f:
|
|
for line in f.readlines():
|
|
m = re.match(r'^(\S+) (\S+)\s*$', line)
|
|
if m:
|
|
old_hashes[m.group(1)] = m.group(2)
|
|
match = old_hashes == hashes
|
|
except Exception:
|
|
pass
|
|
if not match:
|
|
exit(f'{whoami}: auto job inputs have changed')
|
|
|
|
def update_hashes(self):
|
|
hashes = self.get_hashes()
|
|
with open(self.SUMS, 'w') as f:
|
|
print(f'# Generated by {whoami}', file=f)
|
|
for k, v in hashes.items():
|
|
print(f'{k} {v}', file=f)
|
|
|
|
def generate(self):
|
|
warn(f'{whoami}: regenerating auto job files')
|
|
|
|
with open('job.yml', 'r') as f:
|
|
data = yaml.safe_load(f.read())
|
|
self.validate(data)
|
|
with open(self.DESTS['decl'], 'w') as f:
|
|
print(BANNER, file=f)
|
|
self.generate_decl(data, f)
|
|
with open(self.DESTS['init'], 'w') as f:
|
|
print(BANNER, file=f)
|
|
self.generate_init(data, f)
|
|
|
|
# Update hashes last to ensure that this will be rerun in the
|
|
# event of a failure.
|
|
self.update_hashes()
|
|
# DON'T ADD CODE TO generate AFTER update_hashes
|
|
|
|
def check_keys(self, what, d, exp):
|
|
if not isinstance(d, dict):
|
|
exit(f'{what} is not a dictionary')
|
|
actual = set(d.keys())
|
|
extra = actual - exp
|
|
if extra:
|
|
exit(f'{what}: unknown keys = {extra}')
|
|
|
|
def validate(self, data):
|
|
self.check_keys('top', data, set(['choices', 'options']))
|
|
for o in data['options']:
|
|
self.check_keys('top', o, set(
|
|
['table', 'prefix', 'bare', 'positional',
|
|
'optional_parameter', 'required_parameter',
|
|
'required_choices', 'from_table']))
|
|
|
|
def to_identifier(self, label, prefix, const):
|
|
identifier = re.sub(r'[^a-zA-Z0-9]', '_', label)
|
|
if const:
|
|
identifier = identifier.upper()
|
|
else:
|
|
identifier = identifier.lower()
|
|
identifier = re.sub(r'(?:^|_)([a-z])',
|
|
lambda x: x.group(1).upper(),
|
|
identifier).replace('_', '')
|
|
return prefix + identifier
|
|
|
|
def generate_decl(self, data, f):
|
|
for o in data['options']:
|
|
table = o['table']
|
|
if table in ('main', 'help'):
|
|
continue
|
|
i = self.to_identifier(table, 'O_', True)
|
|
print(f'static constexpr char const* {i} = "{table}";', file=f)
|
|
print('', file=f)
|
|
for o in data['options']:
|
|
table = o['table']
|
|
prefix = 'arg' + o.get('prefix', '')
|
|
if o.get('positional', False):
|
|
print(f'void {prefix}Positional(char*);', file=f)
|
|
for i in o.get('bare', []):
|
|
identifier = self.to_identifier(i, prefix, False)
|
|
print(f'void {identifier}();', file=f)
|
|
for i in o.get('optional_parameter', []):
|
|
identifier = self.to_identifier(i, prefix, False)
|
|
print(f'void {identifier}(char *);', file=f)
|
|
for i in o.get('required_parameter', {}):
|
|
identifier = self.to_identifier(i, prefix, False)
|
|
print(f'void {identifier}(char *);', file=f)
|
|
for i in o.get('required_choices', {}):
|
|
identifier = self.to_identifier(i, prefix, False)
|
|
print(f'void {identifier}(char *);', file=f)
|
|
if table not in ('main', 'help'):
|
|
identifier = self.to_identifier(table, 'argEnd', False)
|
|
print(f'void {identifier}();', file=f)
|
|
|
|
def generate_init(self, data, f):
|
|
print('auto b = [this](void (ArgParser::*f)()) {', file=f)
|
|
print(' return QPDFArgParser::bindBare(f, this);', file=f)
|
|
print('};', file=f)
|
|
print('auto p = [this](void (ArgParser::*f)(char *)) {', file=f)
|
|
print(' return QPDFArgParser::bindParam(f, this);', file=f)
|
|
print('};', file=f)
|
|
print('', file=f)
|
|
for k, v in data['choices'].items():
|
|
print(f'char const* {k}_choices[] = {{', file=f, end='')
|
|
for i in v:
|
|
print(f'"{i}", ', file=f, end='')
|
|
print('0};', file=f)
|
|
print('', file=f)
|
|
for o in data['options']:
|
|
table = o['table']
|
|
if table == 'main':
|
|
print('this->ap.selectMainOptionTable();', file=f)
|
|
elif table == 'help':
|
|
print('this->ap.selectHelpOptionTable();', file=f)
|
|
else:
|
|
identifier = self.to_identifier(table, 'argEnd', False)
|
|
print(f'this->ap.registerOptionTable("{table}",'
|
|
f' b(&ArgParser::{identifier}));', file=f)
|
|
prefix = 'arg' + o.get('prefix', '')
|
|
if o.get('positional', False):
|
|
print('this->ap.addPositional('
|
|
f'p(&ArgParser::{prefix}Positional));', file=f)
|
|
for i in o.get('bare', []):
|
|
identifier = self.to_identifier(i, prefix, False)
|
|
print(f'this->ap.addBare("{i}", '
|
|
f'b(&ArgParser::{identifier}));', file=f)
|
|
for i in o.get('optional_parameter', []):
|
|
identifier = self.to_identifier(i, prefix, False)
|
|
print(f'this->ap.addOptionalParameter("{i}", '
|
|
f'p(&ArgParser::{identifier}));', file=f)
|
|
for k, v in o.get('required_parameter', {}).items():
|
|
identifier = self.to_identifier(k, prefix, False)
|
|
print(f'this->ap.addRequiredParameter("{k}", '
|
|
f'p(&ArgParser::{identifier})'
|
|
f', "{v}");', file=f)
|
|
for k, v in o.get('required_choices', {}).items():
|
|
identifier = self.to_identifier(k, prefix, False)
|
|
print(f'this->ap.addRequiredChoices("{k}", '
|
|
f'p(&ArgParser::{identifier})'
|
|
f', {v}_choices);', file=f)
|
|
for o in data['options']:
|
|
table = o['table']
|
|
if 'from_table' not in o:
|
|
continue
|
|
if table == 'main':
|
|
print('this->ap.selectMainOptionTable();', file=f)
|
|
elif table == 'help':
|
|
print('this->ap.selectHelpOptionTable();', file=f)
|
|
else:
|
|
print(f'this->ap.selectOptionTable("{table}");', file=f)
|
|
ft = o['from_table']
|
|
other_table = ft['table']
|
|
for j in ft['options']:
|
|
print('this->ap.copyFromOtherTable'
|
|
f'("{j}", "{other_table}");', file=f)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
try:
|
|
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
|
Main().main()
|
|
except KeyboardInterrupt:
|
|
exit(130)
|