diff options
author | Joris | 2023-09-17 12:23:47 +0200 |
---|---|---|
committer | Joris | 2023-09-17 12:23:47 +0200 |
commit | 1ebc55c72a1a17293bbf4ad86e0177a10a794750 (patch) | |
tree | 5fce0ea3a011ccbae85b0d3927f8ac33099585fb /cli/new | |
parent | c236facb4d4c277773c83f1a4ee85b48833d7e67 (diff) |
Make app packageable
Diffstat (limited to 'cli/new')
-rw-r--r-- | cli/new/__init__.py | 0 | ||||
-rw-r--r-- | cli/new/command.py | 88 | ||||
-rw-r--r-- | cli/new/format.py | 68 | ||||
-rw-r--r-- | cli/new/reader.py | 55 |
4 files changed, 211 insertions, 0 deletions
diff --git a/cli/new/__init__.py b/cli/new/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/cli/new/__init__.py diff --git a/cli/new/command.py b/cli/new/command.py new file mode 100644 index 0000000..9f5e5dc --- /dev/null +++ b/cli/new/command.py @@ -0,0 +1,88 @@ +import PIL.Image +import cli.new.format as format +import cli.new.reader as reader +import io +import os +import pathlib +import requests +import shutil +import subprocess + +def run(books_library, book_source=None): + + # Get data + + title = reader.required('Title') + subtitle = reader.optional('Subtitle') + authors = reader.non_empty_list('Authors') + author_sort = reader.required('Authors sorting') + genres = reader.non_empty_list('Genres') + year = reader.integer('Year') + lang = reader.choices('Lang', ['fr', 'en', 'de']) + summary = format.cleanup_text(reader.multi_line('Summary'), lang) + cover_url = reader.required('Cover url') + read = reader.choices('Read', ['Read', 'Unread', 'Reading', 'Stopped']) + + # Output paths + + author_path = format.path_part(author_sort) + title_path = format.path_part(title) + + output_dir = f'{books_library}/{author_path}/{title_path}' + metadata_path = f'{output_dir}/metadata.toml' + cover_path = f'{output_dir}/cover.webp' + + if not book_source is None: + ext = format.extension(book_source) + book_path = f'{output_dir}/book{ext}' + book_source_dir = os.path.dirname(os.path.realpath(book_source)) + book_source_new = f'{book_source_dir}/{author_path}-{title_path}.mobi' + + # Metadata + + metadata = f"""title = "{title}" + subtitle = "{subtitle}" + authors = {format.format_list(authors)} + authorsSort = "{author_sort}" + genres = {format.format_list(genres)} + date = {year} + summary = \"\"\" + {summary} + \"\"\" + read = "{read}" + """ + + # Ask for confirmation + + print(f'About to create:\n\n- {metadata_path}\n- {cover_path}') + if not book_source is None: + print(f'- {book_path}') + print(f'\nAnd moving:\n\n {book_source},\n -> {book_source_new}.') + print() + + reader.confirm('OK?') + + # Create files + + pathlib.Path(output_dir).mkdir(parents=True, exist_ok=True) + download_cover(cover_url, cover_path) + with open(metadata_path, 'w') as f: + f.write(metadata) + if not book_path is None: + shutil.copyfile(book_source, book_path) + if format.extension(book_source) in ['mobi', 'azw3']: + os.rename(book_source, book_source_new) + else: + subprocess.run(['ebook-convert', book_source, book_source_new]) + os.remove(book_source) + +# Download cover as WEBP +def download_cover(url, path): + response = requests.get(url, headers={ 'User-Agent': 'python-script' }) + image = PIL.Image.open(io.BytesIO(response.content)) + width, height = image.size + if width > 300: + image = image.resize((300, int(300 * height / width)), PIL.Image.LANCZOS) + image = image.convert('RGB') + image.save(path, 'WEBP', optimize=True, quality=85) + diff --git a/cli/new/format.py b/cli/new/format.py new file mode 100644 index 0000000..c004f82 --- /dev/null +++ b/cli/new/format.py @@ -0,0 +1,68 @@ +import pathlib +import re +import unicodedata + +def format_list(xs): + return '[ ' + ', '.join([f'"{x}"' for x in xs]) + ' ]' + +def path_part(name): + simplified = ''.join([alnum_or_space(c) for c in unaccent(name.lower())]) + return '-'.join(simplified.split()) + +def unaccent(s): + return ''.join(c for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) != 'Mn') + +def alnum_or_space(c): + if c.isalnum(): + return c + else: + return ' ' + +def extension(path): + return pathlib.Path(path).suffix + +def cleanup_text(s, lang): + s = re.sub('\'', '’', s) + s = re.sub('\.\.\.', '…', s) + s = re.sub('\. \. \.', '…', s) + s = cleanup_quotes(s, lang) + + if lang == 'fr': + s = re.sub('“', '«', s) + s = re.sub('”', '»', s) + + # Replace space by insecable spaces + s = re.sub(r' ([:?\!»])', r' \1', s) + s = re.sub('« ', '« ', s) + + # Add missing insecable spaces + s = re.sub(r'([^ ])([:?\!»])', r'\1 \2', s) + s = re.sub(r'«([^ ])', r'« \1', s) + + elif lang == 'en': + s = re.sub('«', '“', s) + s = re.sub('»', '”', s) + + return s + +def cleanup_quotes(s, lang): + res = '' + quoted = False + for c in s: + if c == '"': + if quoted: + quoted = False + if lang == 'fr': + res += '»' + elif lang == 'en': + res += '”' + else: + quoted = True + if lang == 'fr': + res += '«' + elif lang == 'en': + res += '“' + else: + res += c + return res + diff --git a/cli/new/reader.py b/cli/new/reader.py new file mode 100644 index 0000000..eacd70b --- /dev/null +++ b/cli/new/reader.py @@ -0,0 +1,55 @@ +def required(label): + value = input(f'{label}: ').strip() + if value: + print() + return value + else: + return required(label) + +def multi_line(label): + lines = '' + print(f'{label}, type [end] to finish:\n') + while True: + value = input() + if value.strip() == '[end]': + break + elif value.strip(): + lines += f'{value.strip()}\n' + print() + return lines.strip() + +def optional(label): + value = input(f'{label} (optional): ').strip() + print() + return value + +def non_empty_list(label): + value = input(f'{label} (separated by commas): ') + values = [x.strip() for x in value.split(',') if x.strip()] + if len(values) > 0: + print() + return values + else: + return non_empty_list(label) + +def integer(label): + value = input(f'{label}: ').strip() + if value.isdigit(): + print() + return int(value) + else: + return integer(label) + +def choices(label, xs): + pp_choices = '/'.join(xs) + value = input(f'{label} [{pp_choices}] ') + if value in xs: + print() + return value + else: + return choices(label, xs) + +def confirm(message): + if choices(message, ['y', 'n']) == 'n': + print('\nStopping.') + exit(1) |