add link parsing, back & forward controls
This commit is contained in:
parent
0e9f0aae85
commit
861fa0c8a9
|
@ -0,0 +1,2 @@
|
||||||
|
.venv
|
||||||
|
*.log
|
172
nex.py
172
nex.py
|
@ -1,45 +1,76 @@
|
||||||
import sys
|
import sys
|
||||||
import socket
|
import socket
|
||||||
|
import pytermgui as ptg
|
||||||
|
from typing import Tuple
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
PORT = 1900
|
PORT = 1900
|
||||||
|
|
||||||
|
logger = logging.getLogger('nex')
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
logger.addHandler(logging.FileHandler('nex.log'))
|
||||||
|
|
||||||
|
|
||||||
class Session:
|
class Session:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.history = [Page('home')]
|
self.history = [Page('home')]
|
||||||
self.current_page = 0
|
self.curr_page_idx = 0
|
||||||
|
|
||||||
def back(self):
|
def back(self):
|
||||||
if self.current_page > 0:
|
if self.curr_page_idx > 0:
|
||||||
self.current_page -= 1
|
self.curr_page_idx -= 1
|
||||||
self.render()
|
self.render()
|
||||||
|
|
||||||
def forward(self):
|
def forward(self):
|
||||||
if self.current_page < len(self.history) - 1:
|
if self.curr_page_idx < len(self.history) - 1:
|
||||||
self.current_page += 1
|
self.curr_page_idx += 1
|
||||||
self.render()
|
self.render()
|
||||||
|
|
||||||
def load(self, url):
|
def load(self, url):
|
||||||
self.current_page += 1
|
self.curr_page_idx += 1
|
||||||
if self.current_page >= len(self.history):
|
if self.curr_page_idx >= len(self.history):
|
||||||
self.history.append(Page(url))
|
self.history.append(Page(url))
|
||||||
else:
|
else:
|
||||||
self.history[self.current_page] = Page(url)
|
self.history[self.curr_page_idx] = Page(url)
|
||||||
self.history[self.current_page].request()
|
self.history[self.curr_page_idx].request()
|
||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
print(self.history[self.current_page].content)
|
return self.history[self.curr_page_idx].content
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
self.history[self.curr_page_idx].request()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_page(self):
|
||||||
|
return self.history[self.curr_page_idx]
|
||||||
|
|
||||||
|
def follow_link(self, link_index: int):
|
||||||
|
logger.info(link_index)
|
||||||
|
url = self.current_page.links[link_index]
|
||||||
|
if url.startswith('nex://'):
|
||||||
|
self.load(url)
|
||||||
|
else:
|
||||||
|
url = '/'.join([self.current_page.url.rstrip('/'), url])
|
||||||
|
self.load(url)
|
||||||
|
|
||||||
|
|
||||||
class Page:
|
class Page:
|
||||||
def __init__(self, url):
|
def __init__(self, url):
|
||||||
self.url = url
|
self.url = url
|
||||||
self.host, self.selector = parse_url(url)
|
self.host, self.selector = parse_url(url)
|
||||||
self.content = ''
|
self.raw_content = ''
|
||||||
|
self.content = self.raw_content
|
||||||
|
self.links = ['' for i in range(10)]
|
||||||
|
|
||||||
def request(self):
|
def request(self):
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||||
sock.connect((self.host, PORT))
|
try:
|
||||||
|
sock.connect((self.host, PORT))
|
||||||
|
except ConnectionError as e:
|
||||||
|
logger.error(e)
|
||||||
|
self.content = f'Error connecting to {self.host}'
|
||||||
|
return
|
||||||
selector = self.selector + '\r\n'
|
selector = self.selector + '\r\n'
|
||||||
sock.sendall(selector.encode('ascii'))
|
sock.sendall(selector.encode('ascii'))
|
||||||
buf = bytearray()
|
buf = bytearray()
|
||||||
|
@ -48,10 +79,33 @@ class Page:
|
||||||
if len(data) == 0:
|
if len(data) == 0:
|
||||||
break
|
break
|
||||||
buf.extend(data)
|
buf.extend(data)
|
||||||
self.content = buf.decode('ascii')
|
try:
|
||||||
|
self.raw_content = buf.decode('ascii')
|
||||||
|
except UnicodeDecodeError as e:
|
||||||
|
logger.error(e)
|
||||||
|
self.content = 'Content could not be decoded'
|
||||||
|
return
|
||||||
|
self.content = self.raw_content
|
||||||
|
self._parse_links()
|
||||||
|
for i, l in enumerate(self.links):
|
||||||
|
self.content = re.sub(l, f'[bold cyan]\[{i}][/] {l}',
|
||||||
|
self.content, 1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def title(self):
|
||||||
|
return f'nex://{self.url}'
|
||||||
|
|
||||||
|
def _parse_links(self):
|
||||||
|
pattern = re.compile(r'=>\s*([\w/:\.~-]*)\s*')
|
||||||
|
self.links = pattern.findall(self.raw_content)
|
||||||
|
# Find links
|
||||||
|
# Determine if absolute or relative
|
||||||
|
# Parse out relative directory navigation
|
||||||
|
# Store parsed URLs in array
|
||||||
|
# Add numbers to page content corresponding to array index
|
||||||
|
|
||||||
|
|
||||||
def parse_url(url: str) -> (str, str):
|
def parse_url(url: str) -> Tuple[str, str]:
|
||||||
clean_url = url.replace('nex://', '')
|
clean_url = url.replace('nex://', '')
|
||||||
split_url = clean_url.split('/', 1)
|
split_url = clean_url.split('/', 1)
|
||||||
if len(split_url) == 1:
|
if len(split_url) == 1:
|
||||||
|
@ -59,14 +113,88 @@ def parse_url(url: str) -> (str, str):
|
||||||
return split_url[0], split_url[1]
|
return split_url[0], split_url[1]
|
||||||
|
|
||||||
|
|
||||||
|
class BrowserWindow(ptg.Window):
|
||||||
|
|
||||||
|
overflow = ptg.Overflow.SCROLL
|
||||||
|
vertical_align = ptg.VerticalAlignment.TOP
|
||||||
|
|
||||||
|
def __init__(self, session: Session):
|
||||||
|
super().__init__()
|
||||||
|
self.session = session
|
||||||
|
self.set_widgets([session.render()])
|
||||||
|
|
||||||
|
def update(self) -> None:
|
||||||
|
self.set_widgets([session.render()])
|
||||||
|
|
||||||
|
def window_back(self, *args):
|
||||||
|
self.session.back()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def window_forward(self, *args):
|
||||||
|
self.session.forward()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def window_reload(self, *args):
|
||||||
|
self.session.reload()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def follow_link(self, key: str):
|
||||||
|
index = int(key)
|
||||||
|
self.session.follow_link(index)
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
|
||||||
|
def _define_layout() -> ptg.Layout:
|
||||||
|
layout = ptg.Layout()
|
||||||
|
layout.add_slot("Header", height=1)
|
||||||
|
layout.add_break()
|
||||||
|
layout.add_slot("Body")
|
||||||
|
layout.add_break()
|
||||||
|
layout.add_slot("Footer", height=1)
|
||||||
|
return layout
|
||||||
|
|
||||||
|
|
||||||
|
def _create_footer(browser: BrowserWindow, manager: ptg.WindowManager):
|
||||||
|
|
||||||
|
return ptg.Window(
|
||||||
|
ptg.Splitter(
|
||||||
|
ptg.KeyboardButton("Back", browser.window_back, bound='b'),
|
||||||
|
ptg.KeyboardButton("Forward", browser.window_forward, bound='f'),
|
||||||
|
ptg.KeyboardButton("Reload", browser.window_reload, bound='r'),
|
||||||
|
ptg.KeyboardButton("Quit", lambda *_: manager.stop(), bound='q')
|
||||||
|
),
|
||||||
|
box="EMPTY",
|
||||||
|
is_persistant=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
session = Session()
|
session = Session()
|
||||||
while True:
|
if len(sys.argv) > 1:
|
||||||
url = input('Enter a URL')
|
url = sys.argv[1]
|
||||||
|
if len(sys.argv) > 2 and sys.argv[2] == '--notui':
|
||||||
|
url = sys.argv[1]
|
||||||
session.load(url)
|
session.load(url)
|
||||||
session.render()
|
print(session.render())
|
||||||
command = input('Go [b]ack or [f]orward')
|
session.follow_link(0)
|
||||||
if command == 'b':
|
print(session.render())
|
||||||
session.back()
|
sys.exit(0)
|
||||||
elif command == 'f':
|
session.load(url)
|
||||||
session.forward()
|
browser = BrowserWindow(session)
|
||||||
|
with ptg.WindowManager() as manager:
|
||||||
|
manager.layout = _define_layout()
|
||||||
|
header = ptg.Window(f"[bold accent]{session.current_page.title}",
|
||||||
|
box="EMPTY",
|
||||||
|
is_persistant=True)
|
||||||
|
manager.add(header)
|
||||||
|
manager.add(browser, assign='body')
|
||||||
|
manager.add(_create_footer(browser, manager), assign='footer')
|
||||||
|
manager.bind('j', lambda *_: browser.scroll(5))
|
||||||
|
manager.bind(ptg.keys.DOWN, lambda *_: browser.scroll(5))
|
||||||
|
manager.bind('k', lambda *_: browser.scroll(-5))
|
||||||
|
manager.bind(ptg.keys.UP, lambda *_: browser.scroll(-5))
|
||||||
|
manager.bind('q', lambda *_: manager.stop())
|
||||||
|
|
||||||
|
for i in range(10):
|
||||||
|
manager.bind(f'{i}', lambda widget, key: browser.follow_link(key))
|
||||||
|
logger.info(manager.bindings)
|
||||||
|
|
Loading…
Reference in New Issue