diff options
Diffstat (limited to 'utab')
-rw-r--r-- | utab/__init__.py | 0 | ||||
-rw-r--r-- | utab/__main__.py | 77 | ||||
-rw-r--r-- | utab/const.py | 4 | ||||
-rw-r--r-- | utab/data/config.yml | 2 | ||||
-rw-r--r-- | utab/data/css/index.css | 32 | ||||
-rw-r--r-- | utab/data/index.html | 11 | ||||
-rw-r--r-- | utab/data/sites.csv | 0 | ||||
-rw-r--r-- | utab/rendering.py | 35 |
8 files changed, 161 insertions, 0 deletions
diff --git a/utab/__init__.py b/utab/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/utab/__init__.py diff --git a/utab/__main__.py b/utab/__main__.py new file mode 100644 index 0000000..5e619ed --- /dev/null +++ b/utab/__main__.py @@ -0,0 +1,77 @@ +from flask import Flask, Response, request, redirect, abort +from appdirs import user_data_dir +from pathlib import Path +import urllib +import sys +import yaml +import csv +from .rendering import * + +app = Flask(__name__) + +# locate page template at e.g. $XDG_CONFIG_HOME/utab/index.html +# and sites json file at utab/sites.json +data_dir = user_data_dir(appname="utab") +template_fp = Path(data_dir) / "index.html" +css_dir = Path(data_dir) / "css" +try: + template = Path.open(template_fp).read() +except FileNotFoundError: + print("Template file not found.") + sys.exit(1) + +sites_fp = Path(data_dir) / "sites.csv" +config_fp = Path(data_dir) / "config.yml" +with open(config_fp) as f: + config = yaml.load(f.read()) + f.close() + + +def read_sites(): + with open(sites_fp) as f: + sites = list(csv.reader(f)) + f.close() + return sites + + +@app.route("/") +def index(): + return render_page( + template, + sites=render_sites( + read_sites(), columns=config["columns"], rows=config["rows"] + ), + ) + + +@app.route("/go/<path:url>") +def visit_site(url): + print(url) + url_unesc = urllib.parse.unquote(url) # unescaped url + print(url_unesc) + sites = read_sites() + for i, s in enumerate(sites): + if s[0] == url_unesc: + sites[i][VISITS] = str(int(sites[i][VISITS]) + 1) + with open(sites_fp, "w") as f: + # update visits + csv.writer(f).writerows(sites) + f.close() + return redirect(url_unesc, 302) + + +@app.route("/css/<string:filename>") +def serve_css(filename): + try: + with open(css_dir / filename) as f: + resp = Response(f.read(), 200, {"Content-Type": "text/css"}) + f.close() + return resp + except FileNotFoundError: + return abort(404) + except: + return abort(500) + + +# run on localhost only +app.run("127.0.0.1", 64366) diff --git a/utab/const.py b/utab/const.py new file mode 100644 index 0000000..181f49a --- /dev/null +++ b/utab/const.py @@ -0,0 +1,4 @@ +URL = 0 +TITLE = 1 +FAVICON = 2 +VISITS = 3 diff --git a/utab/data/config.yml b/utab/data/config.yml new file mode 100644 index 0000000..824eb6c --- /dev/null +++ b/utab/data/config.yml @@ -0,0 +1,2 @@ +columns: 8 +rows: 4 diff --git a/utab/data/css/index.css b/utab/data/css/index.css new file mode 100644 index 0000000..5f03851 --- /dev/null +++ b/utab/data/css/index.css @@ -0,0 +1,32 @@ +body { + text-align: center; + background-color: #222; + color: white; + font-family: sans-serif; +} + +.sites-grid { + position: relative; + left: 10%; + max-width: 80%; + max-height: 80%; +} + +.sites-item { + border: #888 2px solid; + display: inline-block; + margin: 20px; + width: 80px; + height: 80px; +} + +.sites-item:hover { + border: #ccc 2px solid; +} + +.site-favicon { + width: 64px; + height: 64px; + position: relative; + top: 8px; +} diff --git a/utab/data/index.html b/utab/data/index.html new file mode 100644 index 0000000..bc6b97b --- /dev/null +++ b/utab/data/index.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<head> + <title>utab</title> +</head> +<body> + <h2>Top Sites</h2> + <div id="sites"> + %sites% + </div> + <link rel="stylesheet" type="text/css" href="/css/index.css" /> +</body> diff --git a/utab/data/sites.csv b/utab/data/sites.csv new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/utab/data/sites.csv diff --git a/utab/rendering.py b/utab/rendering.py new file mode 100644 index 0000000..731524c --- /dev/null +++ b/utab/rendering.py @@ -0,0 +1,35 @@ +import urllib +from .const import * + +# dead simple template engine +def render_page(template: str, **kwargs): + page = template + for k, v in kwargs.items(): + page = page.replace(f"%{str(k)}%", str(v)) + return page + + +def render_sites(sites: list, columns=8, rows=4): + top_sites = sorted(sites, key=lambda s: int(s[VISITS]), reverse=True)[ + : (columns * rows) # top col*row sites, default=32 + ] + # site_rows: group sites into rows + if len(top_sites) < 32: + top_sites.extend([None] * (32 - len(top_sites))) + + site_rows = list(zip(*[top_sites[n::columns] for n in range(columns)])) + html = '<div class="sites-grid">' + for row in site_rows: + html += '<div class="sites-row">' + for col in row: + if col is not None: + html += ( + '<div class="sites-item">' + f'<a class="site" href="/go/{urllib.parse.quote(col[URL], safe="")}">' + f'<img class="site-favicon" src="{col[FAVICON]}" /></a>' + + f"<p>{col[TITLE]}</p>" + + "</div>" + ) + html += "</div>" + html += "</div>" + return html |