feat: line nrs

This commit is contained in:
Barrett Ruth 2025-10-04 23:20:54 -04:00
parent 244d261374
commit 7fd5c307e0
6 changed files with 88 additions and 72 deletions

82
app.py
View file

@ -5,21 +5,17 @@ import os
from dataclasses import dataclass
from pathlib import Path
from flask import Flask, jsonify, render_template, send_from_directory, abort, request
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import TextLexer, get_lexer_for_filename
from pygments.util import ClassNotFound
from flask import Flask, abort, jsonify, render_template, request, send_from_directory
app = Flask(__name__, static_folder=None)
GIT_REPO_PATH = str(Path.home() / "dev")
GIST_PATH = str(Path.home() / "gists")
EXPORT_MARKER = "readme.md"
git_repo_path = str(Path.home() / "dev")
gist_path = str(Path.home() / "gists")
export_marker = "readme.md"
if getpass.getuser() == "apache":
GIT_REPO_PATH = "/srv/git"
GIST_PATH = "/srv/gists"
EXPORT_MARKER = "git-daemon-export-ok"
git_repo_path = "/srv/git"
gist_path = "/srv/gists"
export_marker = "git-daemon-export-ok"
@dataclass
@ -33,11 +29,11 @@ class Repository:
def get_repositories():
repositories = []
if not os.path.exists(GIT_REPO_PATH):
if not os.path.exists(git_repo_path):
return repositories
for item in os.listdir(GIT_REPO_PATH):
repo_path = os.path.join(GIT_REPO_PATH, item)
for item in os.listdir(git_repo_path):
repo_path = os.path.join(git_repo_path, item)
if not os.path.isdir(repo_path):
continue
@ -48,7 +44,7 @@ def get_repositories():
):
continue
exported = os.path.exists(os.path.join(repo_path, EXPORT_MARKER))
exported = os.path.exists(os.path.join(repo_path, export_marker))
description = "No description available"
description_file = os.path.join(repo_path, "description")
@ -82,6 +78,24 @@ def get_repositories():
return repositories
@app.route("/gist/<path:filename>")
def serve_gist(filename):
base = Path(gist_path).resolve()
target = (base / filename).resolve()
if base not in target.parents and base != target:
abort(404)
if not target.exists() or not target.is_file():
abort(404)
try:
content = target.read_text(encoding="utf-8")
except UnicodeDecodeError:
return "Binary file cannot be displayed", 400
ext = target.suffix.lstrip(".") or "plaintext"
return render_template("gist.html", filename=filename, code=content, lang=ext)
@app.route("/")
def index():
repositories = [repo for repo in get_repositories() if repo.exported]
@ -98,43 +112,6 @@ def serve_scripts(filename):
return send_from_directory("scripts", filename)
@app.route("/gist/<path:filename>")
def serve_gist(filename):
base = Path(GIST_PATH).resolve()
target = (base / filename).resolve()
if base not in target.parents and base != target:
abort(404)
if not target.exists() or not target.is_file():
abort(404)
try:
content = target.read_text(encoding="utf-8")
except UnicodeDecodeError:
return "Binary file cannot be displayed", 400
except OSError:
abort(404)
try:
lexer = get_lexer_for_filename(target.name)
except ClassNotFound:
lexer = TextLexer()
formatter = HtmlFormatter(
style="default",
cssclass="highlight",
linenos=False,
noclasses=True,
cssstyles="padding: 20px; font-size: 18px; background-color: #f8f8f8;",
)
highlighted = highlight(content, lexer, formatter)
highlighted = highlighted.replace(
'<td class="code">', '<td class="code" style="padding-left: 20px;">'
)
return render_template("gist.html", filename=filename, highlighted_code=highlighted)
@app.route("/<path:filename>")
def serve_gist_root(filename):
head = filename.split("/", 1)[0]
@ -166,4 +143,3 @@ def get_repo(repo_id):
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)

View file

@ -7,5 +7,4 @@ requires-python = ">=3.11"
dependencies = [
"flask>=3.1.1",
"gunicorn>=23.0.0",
"pygments>=2.19.1",
]

View file

@ -50,11 +50,10 @@ li {
font-family: "Courier New", monospace;
}
/* Code styling */
:not(pre) > code {
font-family: "Courier New", Courier, monospace;
margin: 0 5px;
font-size: 0.8em; /* Match the font size in barrettruth.com */
font-size: 0.8em;
white-space: nowrap;
border: 1px solid #e1e1e1;
background-color: #f4f4f4;
@ -64,3 +63,19 @@ li {
max-width: 100%;
user-select: all;
}
pre code {
font-family: "Snowflake", "Courier New", monospace;
font-size: 0.95rem;
line-height: 1.5;
background: transparent;
white-space: pre;
overflow-x: auto;
}
.hljs-ln-numbers {
text-align: right;
color: #999;
border-right: 10px solid #ffffff;
user-select: none;
}

View file

@ -109,3 +109,4 @@ a {
margin-top: 10px;
font-size: 0.7em;
}

View file

@ -7,17 +7,53 @@
<link rel="stylesheet" href="/styles/common.css" />
<link rel="stylesheet" href="/styles/index.css" />
<link rel="icon" type="image/webp" href="/public/logo.webp" />
<!-- GitHub Light theme for Highlight.js -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css"
/>
<title>{{ filename }}</title>
</head>
<body class="graph-background">
<site-header></site-header>
<main class="main" style="flex-direction: column; align-items: center;">
<h2 style="font-weight: normal; font-size: 3em; align-self: flex-start; margin-left: 40px;">{{ filename }}</h2>
<div style="padding: 40px;">
{{ highlighted_code|safe }}
<main class="main" style="flex-direction: column; align-items: center">
<h2
style="
font-weight: normal;
font-size: 3em;
align-self: flex-start;
margin-left: 40px;
"
>
{{ filename }}
</h2>
<div style="padding: 40px; width: 100%; max-width: 80ch">
<pre
class="line-numbers"
><code class="language-{{ lang }}">{{ code | e }}</code></pre>
</div>
</main>
<site-footer></site-footer>
<script src="/scripts/common.js"></script>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/highlightjs-line-numbers.js/2.8.0/styles/line-numbers.min.css"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlightjs-line-numbers.js/2.8.0/highlightjs-line-numbers.min.js"></script>
<script>
hljs.highlightAll();
hljs.initLineNumbersOnLoad();
</script>
</body>
</html>

13
uv.lock generated
View file

@ -1,5 +1,5 @@
version = 1
revision = 2
revision = 3
requires-python = ">=3.11"
[[package]]
@ -56,14 +56,12 @@ source = { virtual = "." }
dependencies = [
{ name = "flask" },
{ name = "gunicorn" },
{ name = "pygments" },
]
[package.metadata]
requires-dist = [
{ name = "flask", specifier = ">=3.1.1" },
{ name = "gunicorn", specifier = ">=23.0.0" },
{ name = "pygments", specifier = ">=2.19.1" },
]
[[package]]
@ -156,15 +154,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
]
[[package]]
name = "pygments"
version = "2.19.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
]
[[package]]
name = "werkzeug"
version = "3.1.3"