Added more navigation and command paths

This commit is contained in:
asdf 2021-08-08 16:06:44 +10:00
parent 1656c0fc7b
commit 9aed41c8f7
2 changed files with 212 additions and 149 deletions

View File

@ -13,9 +13,11 @@ import textwrap
from time import time
from urllib.parse import urlparse
from datetime import datetime
from shutil import which, get_terminal_size
from shutil import which
from typing import Tuple
import curses
import curses.textpad as textpad
import locale
import data
@ -443,6 +445,12 @@ class Menu:
# TODO: if this fails it's uninitialised or something
class Status:
def __init__(self):
self.message: str
self.action: str
def term_resize(stdscr):
"""resizes the provided stdscr to the current size"""
# TODO: ensure resize can decrement bodyscrollmin by the appropriate amount
@ -463,15 +471,19 @@ def new_main_menu(stdscr):
menu.get_data(LinkData)
menu.current_level.output.Calc()
status = "".encode(code)
action = "waiting".encode(code)
status = Status()
status.message = ""
status.action = ""
while True:
# stdscr.erase()
curses.update_lines_cols()
# TODO: consider stdscr.erase()
# print title
stdscr.addstr(0, 1, title)
stdscr.clrtoeol()
# TODO: subtitle?
# TODO: print subtitle?
# stdscr.addstr(1, 4, "All categories".encode(code))
# print header
@ -490,6 +502,10 @@ def new_main_menu(stdscr):
for i, line in enumerate(
menu.current_level.output.content[menu.current_level.output.viewslice]
):
# guard just in case we try to print beyond the window because of
# some resize stuff
if i >= curses.LINES:
break
count += count
stdscr.addstr(i + menu.current_level.output.miny, 0, line.encode(code))
stdscr.clrtoeol()
@ -497,134 +513,142 @@ def new_main_menu(stdscr):
# this is meant to clear to the second last line, but...
# just erase to the bottom for now
stdscr.clrtobot()
# leftover = menu.current_level.output.maxy - count
# if leftover > 0:
# for i in range(
# (menu.current_level.output.maxy - menu.current_level.output.miny)
# - count
# ):
# stdscr.move(count + i + 1, 0)
# stdscr.clrtoeol()
stdscr.refresh()
# print status
stdscr.addstr((curses.LINES - 1), 0, status)
stdscr.clrtoeol()
if status.message:
status_text = "{} - {}".format(status.message, status.action)
stdscr.addstr((curses.LINES - 1), 0, status_text.encode(code))
stdscr.clrtoeol()
else:
stdscr.move(curses.LINES - 1, 0)
stdscr.clrtoeol()
# clear message for next loop
status.message = ""
# refresh screen output with all changes made
stdscr.refresh()
try:
k = stdscr.getkey()
except (curses.error):
status = str(curses.error)
status.message = str(curses.error)
status.action = str(k)
k = None
continue
if k == "q":
graceful_exit()
elif k == "KEY_RESIZE":
if k == "KEY_RESIZE":
term_resize(stdscr)
action = "resize"
elif k == "j":
# TODO: put controls in output class
if menu.current_level.output.scrollminy <= (
menu.current_level.output.length - menu.current_level.output.maxy
):
menu.current_level.output.scrollminy += 1
action = "down"
else:
action = "tried down"
elif k == "k":
if menu.current_level.output.scrollminy > 0:
menu.current_level.output.scrollminy -= 1
action = "up"
else:
action = "tried up"
elif k == "b":
menu.back()
elif k in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]:
action = int(k)
if menu.current_level.name == "categories":
key = menu.current_level.data[action - 1]["name"]
elif menu.current_level.name == "links":
key = menu.current_level.data[action - 1]["post_id"]
menu.forward()
menu.current_level.selected_key = key
menu.get_data(LinkData)
menu.current_level.output.Calc()
status = "{}".format(action)
status.encode(code)
stdscr.refresh()
def change_level(menu) -> int:
"""???"""
while True:
# get input
action = parse_input()
# handle input
if action in ["n", "next"]:
menu.current_level.pages.next()
elif action in ["p", "prev"]:
menu.current_level.pages.prev()
elif action in ["b", "back"]:
menu.back()
elif action in ["f", "forward"]:
menu.forward()
elif action in ["?", "help"]:
print(HELP_TEXT)
elif action in ["q", "quit", "exit"]:
graceful_exit()
elif action in ["c", "create"]:
post_id = post_link()
if post_id >= 0:
# TODO: create new post
# set category_details to the relevant category
# set the selected index to the index of the new post in category_details
# set menu level to thread index
pass
elif action in ["s", "search"]:
pass
# search()
# TODO: open search screen
# level.prior_to_search = level.current
# results are returned to a search list, displayed by setting menu level to search
elif action in ["r", "reply"] and menu.current_level.name in [
"search_result_thread_details",
"thread_details",
]:
# reply(thread_details["postid"])
pass
elif action in ["o", "open"] and menu.current_level.name in [
"search_result_thread_details",
"thread_details",
]:
pass
# open_link_in_browser(thread_details["url"])
# open link in external program
# is o the right command?
elif k in [":", " "]:
iwin = curses.newwin(1, curses.COLS, curses.LINES - 1, 0)
iwin.addch(0,0,":")
itxt = textpad.Textbox(iwin)
curses.curs_set(1)
itxt.edit()
action = itxt.gather()
# action slice to remove : at the start
# TODO: can this be avoided?
doAction(menu, action[1:], status)
curses.curs_set(0)
del itxt
del iwin
else:
doAction(menu, k, status)
def doAction(menu, action, status):
int_action = None
try:
int_action = int(action)
except ValueError:
# it's not a number but that's ok
pass
if int_action:
try:
navigate(menu, int_action)
except Exception as e:
status.message = str(e)
else:
try:
handle_command(menu, action)
except Exception as e:
status.message = str(e)
def navigate(menu, int_action):
if int_action:
if menu.current_level.name == "categories":
try:
# numeric action
action = int(action)
except (ValueError):
print("invalid input")
continue
key = menu.current_level.data[int_action - 1]["name"]
except IndexError:
raise IndexError("Sorry, that category doesn't exist")
return
elif menu.current_level.name == "links":
try:
key = menu.current_level.data[int_action - 1]["post_id"]
except IndexError:
raise IndexError("Sorry, that link doesn't exist")
return
else:
# no action because it's not a valid menu
return
menu.forward()
menu.current_level.selected_key = key
menu.get_data(LinkData)
menu.current_level.output.Calc()
selected_index = (action - 1) + (
menu.current_level.pages.length * (menu.current_level.pages.current - 1)
)
menu.forward()
menu.current_level.pages.current_page = 0
menu.current_level.selected_index = selected_index
break
# except (IndexError, ValueError):
# print("Sorry, that category does not exist. Try again.")
def handle_command(menu, action):
"""???"""
if action in ["j", "KEY_DOWN"]:
# TODO: put up/down controls in output class?
if menu.current_level.output.scrollminy <= (
menu.current_level.output.length - menu.current_level.output.maxy
):
menu.current_level.output.scrollminy += 1
elif action in ["k", "KEY_UP"]:
if menu.current_level.output.scrollminy > 0:
menu.current_level.output.scrollminy -= 1
elif action in ["b", "back"]:
menu.back()
# TODO: add forward as a command?
# it could work like a browser's forward but the command needs a guard to
# not navigate forward unless you have already been forward
# elif action in ["f", "forward"]:
# menu.forward()
elif action in ["?", "help"]:
pass
# print(HELP_TEXT)
elif action in ["q", "quit", "exit"]:
graceful_exit()
elif action in ["c", "create"]:
#post_id = post_link()
#if post_id >= 0:
# TODO: create new post
# set category_details to the relevant category
# set the selected index to the index of the new post in category_details
# set menu level to thread index
pass
elif action in ["s", "search"]:
pass
# search()
# TODO: open search screen
# level.prior_to_search = level.current
# results are returned to a search list, displayed by setting menu level to search
elif action in ["r", "reply"] and menu.current_level.name in [
"search_result_thread_details",
"thread_details",
]:
# reply(thread_details["postid"])
pass
elif action in ["o", "open"] and menu.current_level.name in [
"search_result_thread_details",
"thread_details",
]:
pass
# open_link_in_browser(thread_details["url"])
# open link in external program
# is o the right command?
def parse_input() -> str:

View File

@ -62,28 +62,46 @@ class TestPrintCategories(unittest.TestCase):
]
cols = 80
test_results = (
" ID# New Category",
[
" 1 * category 1 (1)",
" 2 * category 2 (2)",
" 3 * long category name that will be truncated because it's a long... (20)",
],
)
expected_header = " ID# New Category"
test_output = linkulator.print_categories(categories, cols)
self.assertTupleEqual(test_output, test_results)
expected_content = [
" 1 * category 1 (1)",
" 2 * category 2 (2)",
" 3 * long category name that will be truncated because it's a long... (20)",
]
actual_header, actual_content = linkulator.print_categories(categories, cols)
# confirm expected is equal to actual
self.assertEqual(expected_header, actual_header)
self.assertListEqual(expected_content, actual_content)
# confirm actual does not exceed cols
header_max_cols = max([len(line) for line in actual_header])
self.assertTrue(header_max_cols <= cols)
content_max_cols = max([len(line) for line in actual_content])
self.assertTrue(content_max_cols <= cols)
def test_empty_categories(self):
"""Test output when no categories data"""
empty_categories = []
cols = 80
test_results = (
expected_header = ""
expected_content = [
"",
["", "There are no posts yet - enter p to post a new link"],
"There are no posts yet - enter p to post a new link",
]
actual_header, actual_content = linkulator.print_categories(
empty_categories, cols
)
test_output = linkulator.print_categories(empty_categories, cols)
self.assertTupleEqual(test_output, test_results)
# confirm expected is equal to actual
self.assertEqual(expected_header, actual_header)
self.assertListEqual(expected_content, actual_content)
# confirm actual does not exceed cols
content_max_cols = max([len(line) for line in actual_content])
self.assertTrue(content_max_cols <= cols)
class TestPrintLinks(unittest.TestCase):
@ -103,8 +121,8 @@ class TestPrintLinks(unittest.TestCase):
{
"post_id": 2,
"link_timestamp": "1627549445.044661",
"link_author": "author 2",
"reply_count": 25,
"link_author": "author 2 with a long name",
"reply_count": 250,
"description": "a long description for the second post that should wrap i guess",
"has_new_replies": True,
"last_modified_timestamp": "1627549445.044661",
@ -112,16 +130,25 @@ class TestPrintLinks(unittest.TestCase):
]
cols = 80
test_results = (
" ID# Date Author #Repl Description",
[
" 1 2021-07-29 auth 1 [ 0] description 1",
" 2 2021-07-29 author 2 [ 25] a long description for the second post...*",
],
expected_header = (
" ID# Date Author #Repl Description"
)
expected_content = [
" 1 2021-07-29 auth 1 [ 0] description 1",
" 2 2021-07-29 author 2 [ 25] a long description for the second post...*",
]
test_output = linkulator.print_links(links, cols)
self.assertTupleEqual(test_output, test_results)
actual_header, actual_content = linkulator.print_links(links, cols)
# confirm expected is equal to actual
self.assertEqual(expected_header, actual_header)
self.assertListEqual(expected_content, actual_content)
# confirm actual does not exceed cols
header_max_cols = max([len(line) for line in actual_header])
self.assertTrue(header_max_cols <= cols)
content_max_cols = max([len(line) for line in actual_content])
self.assertTrue(content_max_cols <= cols)
class TestPrintPost(unittest.TestCase):
@ -139,7 +166,7 @@ class TestPrintPost(unittest.TestCase):
}
cols = 80
test_results = [
expected_content = [
" Title : A cool website",
" Link : http://asdflkjasdf",
" Category : test category 1",
@ -148,8 +175,14 @@ class TestPrintPost(unittest.TestCase):
"\n No replies yet. Be the first!",
]
test_output = linkulator.print_post(post, cols)
self.assertListEqual(test_output, test_results)
actual_content = linkulator.print_post(post, cols)
# confirm expected is equal to actual
self.assertListEqual(actual_content, expected_content)
# confirm actual does not exceed cols
content_max_cols = max([len(line) for line in actual_content])
self.assertTrue(content_max_cols <= cols)
def test_post_with_reply(self):
"""Test print_post where the post has a reply"""
@ -184,7 +217,7 @@ class TestPrintPost(unittest.TestCase):
}
cols = 80
test_results = [
expected_content = [
" Title : Website 2",
" Link : asdflkjasdf",
" Category : category 2",
@ -195,5 +228,11 @@ class TestPrintPost(unittest.TestCase):
" 1970-01-01 10:16 reply author 2 with a long long long name, a very long name: a reply with a lot of words in it, too many to read, not going to read this",
]
test_output = linkulator.print_post(post, cols)
self.assertListEqual(test_output, test_results)
actual_content = linkulator.print_post(post, cols)
# confirm expected is equal to actual
self.assertListEqual(actual_content, expected_content)
# confirm actual does not exceed cols
content_max_cols = max([len(line) for line in actual_content])
self.assertTrue(content_max_cols <= cols)