This repository has been archived on 2024-03-28. You can view files and clone it, but cannot push or open issues or pull requests.
upload-generator/main.py
Bad Manners f3fabf2d8a Further improvements to descriptions/config
- Allow alt. keys to be used in config (eg. `eka` or `eka_portal` => `aryion`) and refactor out this logic
- Refactor duplicated config parsing logic
- Add `-D --define-option` args for script invokation conditions
- Allow `-f --file-path` arg to be used several times
- Allow `-f --file-path` to be used without setting up an input story or description
2024-01-11 15:32:42 -03:00

124 lines
6.1 KiB
Python
Executable file

#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
import argcomplete
from argcomplete.completers import FilesCompleter, DirectoriesCompleter
import argparse
import json
import os
import re
from subprocess import CalledProcessError
import shutil
import tempfile
from description import parse_description
from story import parse_story
from sites import INVERSE_SUPPORTED_SITE_TAGS
def main(out_dir_path=None, story_path=None, description_path=None, file_paths=[], config_path=None, keep_out_dir=False, ignore_empty_files=False, define_options=[]):
if not out_dir_path:
raise ValueError('Missing out_dir_path')
if not config_path:
raise ValueError('Missing config_path')
if not file_paths:
file_paths = []
if not define_options:
define_options = []
config = None
if story_path or description_path:
with open(config_path, 'r') as f:
config_json = json.load(f)
if type(config_json) is not dict:
raise ValueError('The configuration file must contain a valid JSON object')
config = {}
for k, v in config_json.items():
if type(v) is not str:
raise ValueError(f'Invalid configuration value for entry "{k}": expected string, got {type(v)}')
new_k = INVERSE_SUPPORTED_SITE_TAGS.get(k)
if not new_k:
print(f'Ignoring unknown configuration key "{k}"...')
if new_k in config:
raise ValueError(f'Duplicate configuration entry for website "{new_key}": found collision with key "{k}"')
config[new_k] = v
if len(config) == 0:
raise ValueError(f'Invalid configuration file "{config_path}": no valid sites defined')
remove_out_dir = not keep_out_dir and os.path.isdir(out_dir_path)
with tempfile.TemporaryDirectory() as tdir:
# Clear output dir if it exists and shouldn't be kept
if remove_out_dir:
os.rename(out_dir_path, os.path.join(tdir, 'old_out'))
if not os.path.isdir(out_dir_path):
os.mkdir(out_dir_path)
try:
# Convert original file to .rtf (Aryion) and .txt (all others)
if story_path:
parse_story(story_path, config, out_dir_path, tdir, ignore_empty_files)
# Parse FA description and convert for each website
if description_path:
define_options_set = set(define_options)
if len(define_options_set) < len(define_options):
print('WARNING: duplicated entries defined with -D / --define-option')
parse_description(description_path, config, out_dir_path, ignore_empty_files, define_options)
# Copy generic files over to output
for file_path in file_paths:
shutil.copy(file_path, out_dir_path)
except CalledProcessError as e:
if remove_out_dir:
# Revert directory removal on error
os.rename(out_dir_path, os.path.join(tdir, 'get_rid_of_this'))
os.rename(os.path.join(tdir, 'old_out'), out_dir_path)
print(f'Command exited with code {e.returncode}: {e.stderr.decode("utf-8-sig")}')
exit(1)
except Exception as e:
if remove_out_dir:
# Revert directory removal on error
os.rename(out_dir_path, os.path.join(tdir, 'get_rid_of_this'))
os.rename(os.path.join(tdir, 'old_out'), out_dir_path)
raise e
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='generate multi-gallery upload-ready files')
parser.add_argument('-o', '--output-dir', dest='out_dir_path', default='./out',
help='path of output directory').completer = DirectoriesCompleter
parser.add_argument('-c', '--config', dest='config_path', default='./config.json',
help='path of JSON configuration file').completer = FilesCompleter
parser.add_argument('-D', '--define-option', dest='define_options', action='append',
help='options to define as a truthy value when parsing descriptions')
parser.add_argument('-s', '--story', dest='story_path',
help='path of LibreOffice-readable story file').completer = FilesCompleter
parser.add_argument('-d', '--description', dest='description_path',
help='path of BBCode-formatted description file').completer = FilesCompleter
parser.add_argument('-f', '--file', dest='file_paths', action='append',
help='path(s) of generic file(s) to include in output (i.e. an image or thumbnail)').completer = FilesCompleter
parser.add_argument('-k', '--keep-out-dir', dest='keep_out_dir', action='store_true',
help='whether output directory contents should be kept.\nif set, a script error may leave partial files behind')
parser.add_argument('-I', '--ignore-empty-files', dest='ignore_empty_files', action='store_true',
help='do not raise an error if any input file is empty or whitespace-only')
argcomplete.autocomplete(parser)
args = parser.parse_args()
file_paths = args.file_paths or []
if not (args.story_path or args.description_path or any(file_paths)):
parser.error('at least one of ( --story | --description | --file ) must be set')
if args.out_dir_path and os.path.exists(args.out_dir_path) and not os.path.isdir(args.out_dir_path):
parser.error(f'--output-dir {args.out_dir_path} must be an existing directory or inexistent; found a file instead')
if args.story_path and not os.path.isfile(args.story_path):
parser.error(f'--story {args.story_path} is not a valid file')
if args.description_path and not os.path.isfile(args.description_path):
parser.error(f'--description {args.description_path} is not a valid file')
for file_path in file_paths:
if not os.path.isfile(file_path):
parser.error(f'--file {file_path} is not a valid file')
if (args.story_path or args.description_path) and args.config_path and not os.path.isfile(args.config_path):
parser.error('--config must be a valid file')
if args.define_options:
for option in args.define_options:
if not re.match(r'^[a-zA-Z0-9_-]+$', option):
parser.error(f'--define-option {option} is not a valid option; it must only contain alphanumeric characters, dashes, or underlines')
main(**vars(args))