mirror of
https://git.ari.lt/ari.lt/blog.ari.lt.git
synced 2025-02-04 09:39:25 +01:00
update @ Mon 31 Oct 03:28:33 EET 2022
Signed-off-by: Ari Archer <ari.web.xyz@gmail.com>
This commit is contained in:
parent
7becf050e5
commit
483fc41c0b
5 changed files with 199 additions and 228 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -7,4 +7,5 @@ venv/
|
||||||
/backups/
|
/backups/
|
||||||
.ccls-cache/
|
.ccls-cache/
|
||||||
/blog_json_hash.txt
|
/blog_json_hash.txt
|
||||||
|
/manifest.json
|
||||||
|
|
||||||
|
|
|
@ -55,12 +55,9 @@ in that context, for example sorting blogs.
|
||||||
|
|
||||||
`CI` can have any value.
|
`CI` can have any value.
|
||||||
|
|
||||||
## The API and hidden blogs
|
## The API
|
||||||
|
|
||||||
Some blogs might be hidden for a day or two if
|
Nobody is stopping you from using the static API,
|
||||||
I'm working on it or just need to hide it to
|
|
||||||
not show some stuff on the main page, though
|
|
||||||
nobody is stopping you from using the static API,
|
|
||||||
it's located at [/blog.json](https://blog.ari-web.xyz/blog.json), also if you're using
|
it's located at [/blog.json](https://blog.ari-web.xyz/blog.json), also if you're using
|
||||||
this API generally, this API is **extremely** large, so
|
this API generally, this API is **extremely** large, so
|
||||||
I implemented cache validation, just cache it,
|
I implemented cache validation, just cache it,
|
||||||
|
|
180
blog.json
180
blog.json
File diff suppressed because one or more lines are too long
|
@ -180,6 +180,11 @@ blockquote * {
|
||||||
color: var(--clr-blockquote-fg);
|
color: var(--clr-blockquote-fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a[data-pl] {
|
||||||
|
margin-right: 0.3em;
|
||||||
|
float: left !important;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
*,
|
*,
|
||||||
*::before,
|
*::before,
|
||||||
|
|
234
scripts/blog
234
scripts/blog
|
@ -16,13 +16,15 @@ from shutil import rmtree
|
||||||
from tempfile import gettempdir
|
from tempfile import gettempdir
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from timeit import default_timer as code_timer
|
from timeit import default_timer as code_timer
|
||||||
from typing import Callable, Dict, List, Optional, Set, Tuple
|
from typing import Callable, Dict, Iterable, List, Set, Tuple
|
||||||
from warnings import filterwarnings as filter_warnings
|
from warnings import filterwarnings as filter_warnings
|
||||||
|
|
||||||
import ujson # type: ignore
|
import ujson # type: ignore
|
||||||
from css_html_js_minify import html_minify # type: ignore
|
from css_html_js_minify import html_minify # type: ignore
|
||||||
from css_html_js_minify import process_single_css_file
|
from css_html_js_minify import process_single_css_file
|
||||||
from markdown import markdown # type: ignore
|
from markdown import markdown # type: ignore
|
||||||
|
from markdown.extensions import Extension # type: ignore
|
||||||
|
from markdown.treeprocessors import Treeprocessor # type: ignore
|
||||||
from plumbum.commands.processes import ProcessExecutionError # type: ignore
|
from plumbum.commands.processes import ProcessExecutionError # type: ignore
|
||||||
from pyfzf import FzfPrompt # type: ignore
|
from pyfzf import FzfPrompt # type: ignore
|
||||||
|
|
||||||
|
@ -213,6 +215,69 @@ HOME_PAGE_HTML_TEMPLATE: str = f"""<!DOCTYPE html>
|
||||||
</html>"""
|
</html>"""
|
||||||
|
|
||||||
|
|
||||||
|
def sanitise_title(title: str, titleset: Iterable, _nosep: bool = False) -> str:
|
||||||
|
_title: str = ""
|
||||||
|
|
||||||
|
for char in title:
|
||||||
|
_title += (
|
||||||
|
char
|
||||||
|
if char not in string.whitespace + string.punctuation
|
||||||
|
else "-"
|
||||||
|
if _title and _title[-1] != "-"
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
|
||||||
|
_title = _title.lower().rstrip("-")
|
||||||
|
|
||||||
|
return (
|
||||||
|
_title
|
||||||
|
if _title not in titleset and _title.strip()
|
||||||
|
else sanitise_title(
|
||||||
|
_title + ("" if _nosep else "-") + random.choice(string.digits),
|
||||||
|
titleset,
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def truncate_str(string: str, length: int) -> str:
|
||||||
|
if len(string) <= length:
|
||||||
|
return string
|
||||||
|
|
||||||
|
return string[:length] + "..."
|
||||||
|
|
||||||
|
|
||||||
|
class AddHeaderLinks(Treeprocessor):
|
||||||
|
def run(self, root):
|
||||||
|
self.ids: List[str] = []
|
||||||
|
|
||||||
|
for h_level in range(2, 7):
|
||||||
|
for h in root.findall(f"h{h_level}"):
|
||||||
|
gen_id: str = sanitise_title(h.text, self.ids)
|
||||||
|
self.ids.append(gen_id)
|
||||||
|
|
||||||
|
link = h.makeelement(
|
||||||
|
"a",
|
||||||
|
{
|
||||||
|
"href": f"#{gen_id}",
|
||||||
|
"data-pl": "",
|
||||||
|
"aria-hidden": "true",
|
||||||
|
"focusable": "false",
|
||||||
|
"tabindex": "-1",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
link.text = "#"
|
||||||
|
|
||||||
|
h.append(link)
|
||||||
|
h.set("id", gen_id)
|
||||||
|
|
||||||
|
|
||||||
|
class AddHeaderLinksExt(Extension):
|
||||||
|
def extendMarkdown(self, md, key: str = "add_header_links", index: int = int(1e8)):
|
||||||
|
md.registerExtension(self)
|
||||||
|
md.treeprocessors.register(AddHeaderLinks(md.parser), key, index)
|
||||||
|
|
||||||
|
|
||||||
def log(message: str, header: str = "ERROR", code: int = EXIT_ERR) -> int:
|
def log(message: str, header: str = "ERROR", code: int = EXIT_ERR) -> int:
|
||||||
if not (not NOT_CI_BUILD and header != "ERROR"):
|
if not (not NOT_CI_BUILD and header != "ERROR"):
|
||||||
sys.stderr.write(f"{header}: {message}\n")
|
sys.stderr.write(f"{header}: {message}\n")
|
||||||
|
@ -220,10 +285,6 @@ def log(message: str, header: str = "ERROR", code: int = EXIT_ERR) -> int:
|
||||||
return code
|
return code
|
||||||
|
|
||||||
|
|
||||||
def yesno(cond: bool) -> str:
|
|
||||||
return "Yes" if cond else "No"
|
|
||||||
|
|
||||||
|
|
||||||
def tmp_path(path: str) -> str:
|
def tmp_path(path: str) -> str:
|
||||||
return os.path.join(gettempdir(), path)
|
return os.path.join(gettempdir(), path)
|
||||||
|
|
||||||
|
@ -233,21 +294,6 @@ def editor(config: Dict, file: str) -> None:
|
||||||
os.system(config["editor-command"] % file)
|
os.system(config["editor-command"] % file)
|
||||||
|
|
||||||
|
|
||||||
def sanitise_title(title: str, titleset: Dict) -> str:
|
|
||||||
_title: str = ""
|
|
||||||
|
|
||||||
for char in title:
|
|
||||||
_title += char if char not in string.whitespace + string.punctuation else "-"
|
|
||||||
|
|
||||||
_title = _title.lower()
|
|
||||||
|
|
||||||
return (
|
|
||||||
_title
|
|
||||||
if _title not in titleset and _title.strip()
|
|
||||||
else sanitise_title(_title + random.choice(string.digits), titleset)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def format_time(timestamp: float) -> str:
|
def format_time(timestamp: float) -> str:
|
||||||
return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
|
return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
@ -347,7 +393,6 @@ def new_blog(config: Dict) -> Tuple[int, Dict]:
|
||||||
readline.add_history(user_keywords)
|
readline.add_history(user_keywords)
|
||||||
|
|
||||||
blog["keywords"] = html_escape(user_keywords)
|
blog["keywords"] = html_escape(user_keywords)
|
||||||
blog["hidden"] = yn("Hide blog", "n")
|
|
||||||
|
|
||||||
blog["time"] = datetime.now().timestamp()
|
blog["time"] = datetime.now().timestamp()
|
||||||
config["blogs"][s_title] = blog
|
config["blogs"][s_title] = blog
|
||||||
|
@ -355,61 +400,58 @@ def new_blog(config: Dict) -> Tuple[int, Dict]:
|
||||||
return EXIT_OK, config
|
return EXIT_OK, config
|
||||||
|
|
||||||
|
|
||||||
|
def build_css(config: Dict) -> Tuple[int, Dict]:
|
||||||
|
"""Minify (build) the CSS"""
|
||||||
|
|
||||||
|
log("Minifying CSS...", "MINIFY")
|
||||||
|
|
||||||
|
saved_stdout = sys.stdout
|
||||||
|
sys.stdout = open(os.devnull, "w")
|
||||||
|
|
||||||
|
css_threads: List[Thread] = []
|
||||||
|
|
||||||
|
def _thread(t: Callable) -> None:
|
||||||
|
css_threads.append(Thread(target=t, daemon=True))
|
||||||
|
css_threads[-1].start()
|
||||||
|
|
||||||
|
if os.path.isfile("content/styles.css"):
|
||||||
|
log("Minifying main styles", "MINIFY")
|
||||||
|
_thread(lambda: process_single_css_file("content/styles.css"))
|
||||||
|
|
||||||
|
if os.path.isdir("content/fonts"):
|
||||||
|
log("Minifying fonts...", "MINIFY")
|
||||||
|
|
||||||
|
for font in iglob("content/fonts/*.css"):
|
||||||
|
if font.endswith(".min.css"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
log(f"Minifying font file: {font}", "MINIFY")
|
||||||
|
_thread(lambda: process_single_css_file(font))
|
||||||
|
|
||||||
|
for t in css_threads:
|
||||||
|
t.join()
|
||||||
|
|
||||||
|
sys.stdout.close()
|
||||||
|
sys.stdout = saved_stdout
|
||||||
|
|
||||||
|
log("Done minifying CSS", "MINIFY")
|
||||||
|
|
||||||
|
return EXIT_OK, config
|
||||||
|
|
||||||
|
|
||||||
def build(config: Dict) -> Tuple[int, Dict]:
|
def build(config: Dict) -> Tuple[int, Dict]:
|
||||||
"""Build, minimise and generate site"""
|
"""Build, minimise and generate site"""
|
||||||
|
|
||||||
latest_blog_id: Optional[str] = next(
|
if not config["blogs"]:
|
||||||
filter(
|
return log("No blogs to build"), config
|
||||||
lambda bid: bid if not config["blogs"][bid]["hidden"] else None,
|
|
||||||
tuple(config["blogs"].keys())[::-1],
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not config["blogs"] or latest_blog_id is None:
|
latest_blog_id: str = tuple(config["blogs"].keys())[-1]
|
||||||
return log("Cannot build no blogs"), config
|
|
||||||
|
|
||||||
if os.path.isdir(config["blog-dir"]):
|
if os.path.isdir(config["blog-dir"]):
|
||||||
rmtree(config["blog-dir"])
|
rmtree(config["blog-dir"])
|
||||||
|
|
||||||
os.makedirs(config["blog-dir"], exist_ok=True)
|
os.makedirs(config["blog-dir"], exist_ok=True)
|
||||||
|
|
||||||
log("Minifying CSS...", "MINIFY")
|
|
||||||
|
|
||||||
def build_css() -> None:
|
|
||||||
saved_stdout = sys.stdout
|
|
||||||
sys.stdout = open(os.devnull, "w")
|
|
||||||
|
|
||||||
css_threads: List[Thread] = []
|
|
||||||
|
|
||||||
def _thread(t: Callable) -> None:
|
|
||||||
css_threads.append(Thread(target=t, daemon=True))
|
|
||||||
css_threads[-1].start()
|
|
||||||
|
|
||||||
if os.path.isfile("content/styles.css"):
|
|
||||||
log("Minifying main styles", "MINIFY")
|
|
||||||
_thread(lambda: process_single_css_file("content/styles.css"))
|
|
||||||
|
|
||||||
if os.path.isdir("content/fonts"):
|
|
||||||
log("Minifying fonts...", "MINIFY")
|
|
||||||
|
|
||||||
for font in iglob("content/fonts/*.css"):
|
|
||||||
if font.endswith(".min.css"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
log(f"Minifying font file: {font}", "MINIFY")
|
|
||||||
_thread(lambda: process_single_css_file(font))
|
|
||||||
|
|
||||||
for t in css_threads:
|
|
||||||
t.join()
|
|
||||||
|
|
||||||
sys.stdout.close()
|
|
||||||
sys.stdout = saved_stdout
|
|
||||||
|
|
||||||
log("Done minifying CSS", "MINIFY")
|
|
||||||
|
|
||||||
Thread(target=build_css, daemon=True).start()
|
|
||||||
|
|
||||||
log("Building blogs...", "INFO")
|
log("Building blogs...", "INFO")
|
||||||
|
|
||||||
def thread(blog_id: str, blog_meta: Dict):
|
def thread(blog_id: str, blog_meta: Dict):
|
||||||
|
@ -438,12 +480,16 @@ def build(config: Dict) -> Tuple[int, Dict]:
|
||||||
config["git-url"],
|
config["git-url"],
|
||||||
markdown(
|
markdown(
|
||||||
b64decode(blog_meta["content"]).decode(),
|
b64decode(blog_meta["content"]).decode(),
|
||||||
extensions=config["py-markdown-extensions"],
|
extensions=[
|
||||||
|
*config["py-markdown-extensions"],
|
||||||
|
AddHeaderLinksExt(),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.replace("<h1>", "<h2>")
|
.replace("<h1>", "<h2>")
|
||||||
.replace("<h1/>", "<h2/>")
|
.replace("<h1/>", "<h2/>")
|
||||||
|
.replace("<pre>", '<pre focusable="true" role="code" tabindex="0">')
|
||||||
.replace(
|
.replace(
|
||||||
"<pre>", '<pre focusable="true" role="code" tabindex="0">'
|
"<blockquote>", '<blockquote focusable="true" tabindex="0">'
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -471,10 +517,6 @@ def build(config: Dict) -> Tuple[int, Dict]:
|
||||||
_tmp_threads: List[Thread] = []
|
_tmp_threads: List[Thread] = []
|
||||||
|
|
||||||
for blog_id, blog_meta in config["blogs"].items():
|
for blog_id, blog_meta in config["blogs"].items():
|
||||||
if blog_meta["hidden"]:
|
|
||||||
log(f"Hidden blog: {blog_id!r}", "HIDE")
|
|
||||||
continue
|
|
||||||
|
|
||||||
t: Thread = Thread(target=thread, args=(blog_id, blog_meta), daemon=True)
|
t: Thread = Thread(target=thread, args=(blog_id, blog_meta), daemon=True)
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
|
@ -489,20 +531,12 @@ def build(config: Dict) -> Tuple[int, Dict]:
|
||||||
lastest_blog: Dict = config["blogs"][latest_blog_id]
|
lastest_blog: Dict = config["blogs"][latest_blog_id]
|
||||||
lastest_blog_time: str = format_time(lastest_blog["time"])
|
lastest_blog_time: str = format_time(lastest_blog["time"])
|
||||||
|
|
||||||
blog_list = '<ul aria-label="latest blogs">'
|
blog_list = '<ol reversed="true" aria-label="latest blogs">'
|
||||||
|
|
||||||
for blog_id, blog_meta in reversed(config["blogs"].items()):
|
for blog_id, blog_meta in reversed(config["blogs"].items()):
|
||||||
blog_list += "<li>"
|
blog_list += f'<li><a href="{os.path.join(config["blog-dir"], blog_id)}">{html_escape(b64decode(blog_meta["title"]).decode())}</a></li>'
|
||||||
|
|
||||||
if blog_meta["hidden"]:
|
blog_list += "</ol>"
|
||||||
blog_list += '<i aria-label="hidden blog">Blog hidden by the owner</i>'
|
|
||||||
continue
|
|
||||||
|
|
||||||
blog_list += f'<a href="{os.path.join(config["blog-dir"], blog_id)}">{html_escape(b64decode(blog_meta["title"]).decode())}</a>'
|
|
||||||
|
|
||||||
blog_list += "</li>"
|
|
||||||
|
|
||||||
blog_list += "</ul>"
|
|
||||||
|
|
||||||
index.write(
|
index.write(
|
||||||
html_minify(
|
html_minify(
|
||||||
|
@ -515,10 +549,9 @@ def build(config: Dict) -> Tuple[int, Dict]:
|
||||||
home_page_description=config["page-description"],
|
home_page_description=config["page-description"],
|
||||||
lastest_blog_time=lastest_blog_time,
|
lastest_blog_time=lastest_blog_time,
|
||||||
latest_blog_url=os.path.join(config["blog-dir"], latest_blog_id),
|
latest_blog_url=os.path.join(config["blog-dir"], latest_blog_id),
|
||||||
latest_blog_title=b64decode(
|
latest_blog_title=truncate_str(
|
||||||
html_escape(lastest_blog["title"])
|
b64decode(html_escape(lastest_blog["title"])).decode(), 20
|
||||||
).decode()[:20]
|
),
|
||||||
+ "...",
|
|
||||||
git_url=config["git-url"],
|
git_url=config["git-url"],
|
||||||
content=blog_list,
|
content=blog_list,
|
||||||
author=config["full-name"],
|
author=config["full-name"],
|
||||||
|
@ -544,7 +577,6 @@ Title: {b64decode(blog_meta["title"]).decode()!r}
|
||||||
Version: {blog_meta["version"]}
|
Version: {blog_meta["version"]}
|
||||||
Time_of_creation: {format_time(blog_meta["time"])}
|
Time_of_creation: {format_time(blog_meta["time"])}
|
||||||
Keywords: {blog_meta['keywords'].replace(" ", ", ")}
|
Keywords: {blog_meta['keywords'].replace(" ", ", ")}
|
||||||
Hidden: {yesno(blog_meta["hidden"])}
|
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -578,12 +610,16 @@ def edit_title(blog: str, config: Dict) -> int:
|
||||||
if not new_title.strip():
|
if not new_title.strip():
|
||||||
return log("New title cannot be empty")
|
return log("New title cannot be empty")
|
||||||
|
|
||||||
old_blog: dict = config["blogs"][blog].copy()
|
# Made it not change the slug
|
||||||
old_blog["title"] = b64encode(new_title.encode()).decode()
|
|
||||||
del config["blogs"][blog]
|
|
||||||
|
|
||||||
config["blogs"][sanitise_title(new_title, config["blogs"])] = old_blog
|
# old_blog: dict = config["blogs"][blog].copy()
|
||||||
del old_blog
|
# old_blog["title"] = b64encode(new_title.encode()).decode()
|
||||||
|
# del config["blogs"][blog]
|
||||||
|
|
||||||
|
# config["blogs"][sanitise_title(new_title, config["blogs"])] = old_blog
|
||||||
|
# del old_blog
|
||||||
|
|
||||||
|
config["blogs"][blog]["title"] = b64encode(new_title.encode()).decode()
|
||||||
|
|
||||||
return EXIT_OK
|
return EXIT_OK
|
||||||
|
|
||||||
|
@ -619,21 +655,11 @@ def edit_content(blog: str, config: Dict) -> int:
|
||||||
return EXIT_OK
|
return EXIT_OK
|
||||||
|
|
||||||
|
|
||||||
def edit_hidden(blog: str, config: Dict) -> int:
|
|
||||||
hidden: bool = config["blogs"][blog]["hidden"]
|
|
||||||
str_hidden: str = "y" if hidden else "n"
|
|
||||||
|
|
||||||
config["blogs"][blog]["hidden"] = yn("Hide this blog", str_hidden, str_hidden)
|
|
||||||
|
|
||||||
return EXIT_OK
|
|
||||||
|
|
||||||
|
|
||||||
EDIT_HOOKS: Dict = {
|
EDIT_HOOKS: Dict = {
|
||||||
"quit": lambda *_: EXIT_OK,
|
"quit": lambda *_: EXIT_OK,
|
||||||
"title": edit_title,
|
"title": edit_title,
|
||||||
"keywords": edit_keywords,
|
"keywords": edit_keywords,
|
||||||
"content": edit_content,
|
"content": edit_content,
|
||||||
"hidden": edit_hidden,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -743,6 +769,7 @@ def generate_static_full(config: Dict) -> Tuple[int, Dict]:
|
||||||
|
|
||||||
BUILD_CFG: Dict = {
|
BUILD_CFG: Dict = {
|
||||||
"Cleaning up": clean,
|
"Cleaning up": clean,
|
||||||
|
"Building CSS": build_css,
|
||||||
"Building static site": build,
|
"Building static site": build,
|
||||||
"Generating metatata": generate_metadata,
|
"Generating metatata": generate_metadata,
|
||||||
}
|
}
|
||||||
|
@ -769,6 +796,7 @@ SUBCOMMANDS: Dict = {
|
||||||
"clean": clean,
|
"clean": clean,
|
||||||
"metadata": generate_metadata,
|
"metadata": generate_metadata,
|
||||||
"static": generate_static_full,
|
"static": generate_static_full,
|
||||||
|
"css": build_css,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue