~fkfd/utab

0be69d4b5db18b1e0794fd3dc3297da0a16b1ccf — Frederick Yin 2 years ago v0.1.0
Minimal viable product
A  => .gitignore +5 -0
@@ 1,5 @@
build/
dist/
*.egg-info/
.vscode/
__pycache__/
\ No newline at end of file

A  => README.md +1 -0
@@ 1,1 @@
# uTab
\ No newline at end of file

A  => setup.py +33 -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",
)

A  => utab/__init__.py +0 -0
A  => utab/__main__.py +77 -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)

A  => utab/const.py +4 -0
@@ 1,4 @@
URL = 0
TITLE = 1
FAVICON = 2
VISITS = 3

A  => utab/data/config.yml +2 -0
@@ 1,2 @@
columns: 8
rows: 4

A  => utab/data/css/index.css +32 -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;
}

A  => utab/data/index.html +11 -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>

A  => utab/data/sites.csv +0 -0
A  => utab/rendering.py +35 -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