From 0be69d4b5db18b1e0794fd3dc3297da0a16b1ccf Mon Sep 17 00:00:00 2001 From: Frederick Yin Date: Sat, 4 Jul 2020 14:23:36 +0800 Subject: Minimal viable product --- .gitignore | 5 ++++ README.md | 1 + setup.py | 33 +++++++++++++++++++++ utab/__init__.py | 0 utab/__main__.py | 77 +++++++++++++++++++++++++++++++++++++++++++++++++ utab/const.py | 4 +++ utab/data/config.yml | 2 ++ utab/data/css/index.css | 32 ++++++++++++++++++++ utab/data/index.html | 11 +++++++ utab/data/sites.csv | 0 utab/rendering.py | 35 ++++++++++++++++++++++ 11 files changed, 200 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 setup.py create mode 100644 utab/__init__.py create mode 100644 utab/__main__.py create mode 100644 utab/const.py create mode 100644 utab/data/config.yml create mode 100644 utab/data/css/index.css create mode 100644 utab/data/index.html create mode 100644 utab/data/sites.csv create mode 100644 utab/rendering.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb8c8fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build/ +dist/ +*.egg-info/ +.vscode/ +__pycache__/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..361f7f1 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# uTab \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..91a284d --- /dev/null +++ b/setup.py @@ -0,0 +1,33 @@ +import setuptools +import os, shutil +from pathlib import Path +from appdirs import user_data_dir + +# copy templates, data storage and default config +data_dir = user_data_dir("utab") +try: + shutil.copytree("utab/data/", data_dir) +except FileExistsError: + pass # do not overwrite existent data + + +with open("README.md", "r") as f: + long_description = f.read() + +setuptools.setup( + name="utab", # Replace with your own username + version="0.1.0", + author="Frederick Yin", + author_email="fkfd@macaw.me", + description="Web browser new tab dashboard HTTP daemon", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://git.sr.ht/~fkfd/utab", + packages=setuptools.find_packages(), + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + ], + python_requires=">=3.6", +) diff --git a/utab/__init__.py b/utab/__init__.py new file mode 100644 index 0000000..e69de29 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/") +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/") +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 @@ + + + utab + + +

Top Sites

+
+ %sites% +
+ + diff --git a/utab/data/sites.csv b/utab/data/sites.csv new file mode 100644 index 0000000..e69de29 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 = '
' + for row in site_rows: + html += '
' + for col in row: + if col is not None: + html += ( + '
' + f'' + f'' + + f"

{col[TITLE]}

" + + "
" + ) + html += "
" + html += "
" + return html -- cgit v1.2.3