summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrederick Yin <fkfd@macaw.me>2020-07-04 14:23:36 +0800
committerFrederick Yin <fkfd@macaw.me>2020-07-04 14:23:36 +0800
commit0be69d4b5db18b1e0794fd3dc3297da0a16b1ccf (patch)
tree00c8f50f381d4531a3a45eeed0f9cdd1bf18f3b1
Minimal viable product
-rw-r--r--.gitignore5
-rw-r--r--README.md1
-rw-r--r--setup.py33
-rw-r--r--utab/__init__.py0
-rw-r--r--utab/__main__.py77
-rw-r--r--utab/const.py4
-rw-r--r--utab/data/config.yml2
-rw-r--r--utab/data/css/index.css32
-rw-r--r--utab/data/index.html11
-rw-r--r--utab/data/sites.csv0
-rw-r--r--utab/rendering.py35
11 files changed, 200 insertions, 0 deletions
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
--- /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