From 17287cfcb3afa1eedcc98d342794b77854b4ebc9 Mon Sep 17 00:00:00 2001 From: veclav talica Date: Sat, 24 Feb 2024 19:15:37 +0500 Subject: [PATCH] pager-based markdown portable browser --- browse.sh | 22 ++++++++ tools/widgets/list_selector.py | 95 ++++++++++++++++++++++++++++++++++ tools/widgets/wrapper.py | 52 +++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100755 browse.sh create mode 100755 tools/widgets/list_selector.py create mode 100644 tools/widgets/wrapper.py diff --git a/browse.sh b/browse.sh new file mode 100755 index 0000000..102a535 --- /dev/null +++ b/browse.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set +e + +printf "%s" "Enter URL: " +read URL + +articles=$(curl -s "$URL/articles.txt") + +# list='' +# while IFS= read article; do +# list+="$article " +# done < <(printf '%s' "$articles") + +while : +do + article=$(./tools/widgets/list_selector.py --desc="Select an article:" --result="line" -- $articles) + if [ -z "$article" ]; then + break + fi + curl -s "$URL/markdown/$article.md" | pager +done diff --git a/tools/widgets/list_selector.py b/tools/widgets/list_selector.py new file mode 100755 index 0000000..7dd3eaf --- /dev/null +++ b/tools/widgets/list_selector.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 + +from sys import argv +import curses + +from wrapper import widget_wrapper + +list_starting_arg = 1 +for i, arg in enumerate(argv[1:]): + if arg == '--': + if i + 2 == len(argv): + print("Empty list given") + exit(-1) + list_starting_arg = i + 2 + break +else: + print("List starting -- wasn't given") + exit(-1) + +desc_arg = '' +result_arg = 'index' + +# todo: Generalize and simplify over descriptor object. +for arg in argv[1:]: + if arg.startswith('--'): + if arg == '--': + break + elif arg.startswith('--result='): + result_arg = arg[arg.find('=') + 1:] + if result_arg not in ['index', 'line']: + print("Invalid --result=") + exit(-1) + elif arg.startswith('--desc='): + desc_arg = arg[arg.find('=') + 1:] + else: + print("Unknown parameter ", arg) + exit(-1) + else: + print("Unknown parameter ", arg) + exit(-1) + +current = 0 +lines = argv[list_starting_arg:] + +list_box = None + +def init(screen): + global list_box + + curses.start_color() + curses.curs_set(0) + + y = 0 + if desc_arg != '': + list_box = screen.subwin(y + 1, 0) + y += 1 + +def draw_list_box(): + y = 0 + + list_box.border() + y += 1 + + for i, line in enumerate(lines): + list_box.addstr(y, 1, line, curses.A_REVERSE if i == current else curses.A_NORMAL) + y += 1 + + list_box.refresh() + +def driver(screen): + global current + + y = 0 + + if desc_arg != '': + screen.addstr(y, 0, desc_arg) + y += 1 + + draw_list_box() + + key = screen.getch() + if key == curses.KEY_DOWN: + current = (current + 1) % len(lines) + elif key == curses.KEY_UP: + current = len(lines) - 1 if current == 0 else current - 1 + elif key == curses.KEY_ENTER or key == 10 or key == 13: + if result_arg == 'index': + return str(current) + elif result_arg == 'line': + return lines[current] + + screen.refresh() + +if __name__ == "__main__": + print(widget_wrapper(init, driver)) diff --git a/tools/widgets/wrapper.py b/tools/widgets/wrapper.py new file mode 100644 index 0000000..82247c1 --- /dev/null +++ b/tools/widgets/wrapper.py @@ -0,0 +1,52 @@ +import curses +import signal +import atexit +import os, sys +from sys import argv, exit + +def handler(signum, frame): + curses.endwin() + exit(1) + +# def exit_handler(): +# curses.endwin() + +init = None +driver = None + +def curses_wrapper(screen): + curses.noecho() + curses.cbreak() + screen.keypad(True) + + init(screen) + + while True: + result = driver(screen) + if result != None: + return result + +def widget_wrapper(p_init, p_driver): + signal.signal(signal.SIGINT, handler) + # atexit.register(exit_handler) + + global init, driver + init = p_init + driver = p_driver + + with open('/dev/tty', 'rb') as inf, open('/dev/tty', 'wb') as outf: + saved_stdin = os.dup(0) + saved_stdout = os.dup(1) + saved_stderr = os.dup(2) + + os.dup2(inf.fileno(), 0) + os.dup2(outf.fileno(), 1) + os.dup2(outf.fileno(), 2) + + result = curses.wrapper(curses_wrapper) + + os.dup2(saved_stdin, 0) + os.dup2(saved_stdout, 1) + os.dup2(saved_stderr, 2) + + return result