diff options
Diffstat (limited to 'src/epy_reader/cli.py')
-rw-r--r-- | src/epy_reader/cli.py | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/src/epy_reader/cli.py b/src/epy_reader/cli.py new file mode 100644 index 0000000..e43b51c --- /dev/null +++ b/src/epy_reader/cli.py @@ -0,0 +1,171 @@ +import argparse +import os +import shutil +import sys +import textwrap +from difflib import SequenceMatcher as SM +from typing import List, Optional, Tuple + +from epy_reader import __version__ +from epy_reader.lib import coerce_to_int, is_url, truncate +from epy_reader.models import LibraryItem +from epy_reader.parser import parse_html +from epy_reader.state import State +from epy_reader.utils import get_ebook_obj + + +def cleanup_library(state: State) -> None: + """Cleanup non-existent file from library""" + library_items = state.get_from_history() + for item in library_items: + if not os.path.isfile(item.filepath) and not is_url(item.filepath): + state.delete_from_library(item.filepath) + + +def get_nth_file_from_library(state: State, n) -> Optional[LibraryItem]: + library_items = state.get_from_history() + try: + return library_items[n - 1] + except IndexError: + return None + + +def get_matching_library_item( + state: State, pattern: str, threshold: float = 0.5 +) -> Optional[LibraryItem]: + matches: List[Tuple[LibraryItem, float]] = [] # [(library_item, match_value), ...] + library_items = state.get_from_history() + if not library_items: + return None + + for item in library_items: + tomatch = f"{item.title} - {item.author}" # item.filepath + match_value = sum( + [i.size for i in SM(None, tomatch.lower(), pattern.lower()).get_matching_blocks()] + ) / float(len(pattern)) + matches.append( + ( + item, + match_value, + ) + ) + + sorted_matches = sorted(matches, key=lambda x: -x[1]) + first_match_item, first_match_value = sorted_matches[0] + if first_match_item and first_match_value >= threshold: + return first_match_item + else: + return None + + +def print_reading_history(state: State) -> None: + termc, _ = shutil.get_terminal_size() + library_items = state.get_from_history() + if not library_items: + print("No Reading History.") + return + + print("Reading History:") + dig = len(str(len(library_items) + 1)) + tcols = termc - dig - 2 + for n, item in enumerate(library_items): + print( + "{} {}".format( + str(n + 1).rjust(dig), + truncate(str(item), "...", tcols, tcols - 3), + ) + ) + + +def parse_cli_args() -> argparse.Namespace: + prog = "epy" + positional_arg_help_str = "[PATH | # | PATTERN | URL]" + args_parser = argparse.ArgumentParser( + prog=prog, + usage=f"%(prog)s [-h] [-r] [-d] [-v] {positional_arg_help_str}", + formatter_class=argparse.RawDescriptionHelpFormatter, + description="Read ebook in terminal", + epilog=textwrap.dedent( + f"""\ + examples: + {prog} /path/to/ebook read /path/to/ebook file + {prog} 3 read #3 file from reading history + {prog} count monte read file matching 'count monte' + from reading history + """ + ), + ) + args_parser.add_argument("-r", "--history", action="store_true", help="print reading history") + args_parser.add_argument("-d", "--dump", action="store_true", help="dump the content of ebook") + args_parser.add_argument( + "-v", + "--version", + action="version", + version=f"v{__version__}", + help="print version and exit", + ) + args_parser.add_argument( + "ebook", + action="store", + nargs="*", + metavar=positional_arg_help_str, + help="ebook path, history number, pattern or URL", + ) + return args_parser.parse_args() + + +def find_file() -> Tuple[str, bool]: + args = parse_cli_args() + state = State() + cleanup_library(state) + + if args.history: + print_reading_history(state) + sys.exit() + + if len(args.ebook) == 0: + last_read = state.get_last_read() + if last_read: + return last_read, args.dump + else: + sys.exit("ERROR: Found no last read ebook file.") + + elif len(args.ebook) == 1: + nth = coerce_to_int(args.ebook[0]) + if nth is not None: + file = get_nth_file_from_library(state, nth) + if file: + return file.filepath, args.dump + else: + print(f"ERROR: #{nth} file not found.") + print_reading_history(state) + sys.exit(1) + elif is_url(args.ebook[0]): + return args.ebook[0], args.dump + elif os.path.isfile(args.ebook[0]): + return args.ebook[0], args.dump + + pattern = " ".join(args.ebook) + match = get_matching_library_item(state, pattern) + if match: + return match.filepath, args.dump + else: + sys.exit("ERROR: Found no matching ebook from history.") + + +def dump_ebook_content(filepath: str) -> None: + ebook = get_ebook_obj(filepath) + try: + try: + ebook.initialize() + except Exception as e: + sys.exit("ERROR: Badly-structured ebook.\n" + str(e)) + for i in ebook.contents: + content = ebook.get_raw_text(i) + src_lines = parse_html(content) + assert isinstance(src_lines, tuple) + # sys.stdout.reconfigure(encoding="utf-8") # Python>=3.7 + for j in src_lines: + sys.stdout.buffer.write((j + "\n\n").encode("utf-8")) + finally: + ebook.cleanup() |