2022-03-11 13:35:37 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""Manage blogs"""
|
|
|
|
|
2022-09-23 01:29:49 +03:00
|
|
|
import hashlib
|
2022-03-11 13:35:37 +02:00
|
|
|
import os
|
|
|
|
import random
|
|
|
|
import string
|
|
|
|
import sys
|
|
|
|
from base64 import b64decode, b64encode
|
|
|
|
from datetime import datetime
|
2022-03-11 17:47:54 +02:00
|
|
|
from glob import iglob
|
2022-03-11 13:35:37 +02:00
|
|
|
from html import escape as html_escape
|
2022-09-20 04:50:25 +03:00
|
|
|
from shutil import copy as copy_file
|
2022-03-11 13:35:37 +02:00
|
|
|
from shutil import rmtree
|
2022-09-20 04:50:25 +03:00
|
|
|
from tempfile import gettempdir
|
2022-03-11 13:35:37 +02:00
|
|
|
from threading import Thread
|
|
|
|
from timeit import default_timer as code_timer
|
2022-10-31 16:50:02 +02:00
|
|
|
from typing import Callable, Dict, Iterable, List, Set, Tuple, Union
|
2022-03-11 13:35:37 +02:00
|
|
|
from warnings import filterwarnings as filter_warnings
|
|
|
|
|
2022-08-30 02:55:42 +03:00
|
|
|
import ujson # type: ignore
|
2022-03-11 17:47:54 +02:00
|
|
|
from css_html_js_minify import html_minify # type: ignore
|
|
|
|
from css_html_js_minify import process_single_css_file
|
2022-03-11 13:35:37 +02:00
|
|
|
from markdown import markdown # type: ignore
|
2022-10-31 03:28:33 +02:00
|
|
|
from markdown.extensions import Extension # type: ignore
|
|
|
|
from markdown.treeprocessors import Treeprocessor # type: ignore
|
2022-03-11 13:35:37 +02:00
|
|
|
from plumbum.commands.processes import ProcessExecutionError # type: ignore
|
|
|
|
from pyfzf import FzfPrompt # type: ignore
|
|
|
|
|
2022-08-17 01:51:03 +03:00
|
|
|
NOT_CI_BUILD: bool = not os.getenv("CI")
|
|
|
|
|
|
|
|
if NOT_CI_BUILD:
|
|
|
|
import readline
|
|
|
|
from atexit import register as fn_register
|
|
|
|
|
2022-03-11 13:35:37 +02:00
|
|
|
EXIT_OK: int = 0
|
|
|
|
EXIT_ERR: int = 1
|
|
|
|
|
2022-03-11 13:46:45 +02:00
|
|
|
DEFAULT_CONFIG: Dict = {
|
2022-03-11 13:35:37 +02:00
|
|
|
"editor-command": f"{os.environ.get('EDITOR', 'vim')} -- %s",
|
|
|
|
"blog-dir": "b",
|
|
|
|
"git-url": "/git",
|
|
|
|
"py-markdown-extensions": [
|
|
|
|
"markdown.extensions.abbr",
|
|
|
|
"markdown.extensions.def_list",
|
|
|
|
"markdown.extensions.fenced_code",
|
|
|
|
"markdown.extensions.footnotes",
|
|
|
|
"markdown.extensions.md_in_html",
|
|
|
|
"markdown.extensions.tables",
|
|
|
|
"markdown.extensions.admonition",
|
|
|
|
"markdown.extensions.sane_lists",
|
|
|
|
"markdown.extensions.toc",
|
|
|
|
"markdown.extensions.wikilinks",
|
|
|
|
"pymdownx.betterem",
|
|
|
|
"pymdownx.caret",
|
|
|
|
"pymdownx.magiclink",
|
|
|
|
"pymdownx.mark",
|
|
|
|
"pymdownx.tilde",
|
|
|
|
],
|
|
|
|
"default-keywords": ["website", "blog", "opinion", "article", "ari-web", "ari"],
|
|
|
|
"page-title": "Ari::web -> Blog",
|
|
|
|
"page-description": "My blog page",
|
|
|
|
"colourscheme-type": "dark",
|
2022-04-02 20:43:37 +03:00
|
|
|
"short-name": "Ari's blogs",
|
2022-03-11 13:35:37 +02:00
|
|
|
"home-keywords": ["ari", "ari-web", "blog", "ari-archer", "foss", "free", "linux"],
|
|
|
|
"base-homepage": "https://ari-web.xyz/",
|
2022-04-02 20:43:37 +03:00
|
|
|
"meta-icons": [{"src": "/favicon.ico", "sizes": "128x128", "type": "image/png"}],
|
|
|
|
"theme-colour": "#f9f6e8",
|
|
|
|
"background-colour": "#262220",
|
2022-06-15 00:44:21 +03:00
|
|
|
"full-name": "Ari Archer",
|
|
|
|
"locale": "en_GB",
|
2022-06-17 00:05:19 +03:00
|
|
|
"home-page-header": "My blogs",
|
2022-08-16 02:27:29 +03:00
|
|
|
"comment-url": "/c",
|
2022-03-11 13:35:37 +02:00
|
|
|
"blogs": {},
|
|
|
|
}
|
|
|
|
DEFAULT_CONFIG_FILE: str = "blog.json"
|
|
|
|
HISTORY_FILE: str = ".blog_history"
|
|
|
|
BLOG_VERSION: int = 1
|
|
|
|
|
2022-10-15 01:14:29 +03:00
|
|
|
BLOG_MARKDOWN_TEMPLATE: str = """<header role="group">
|
|
|
|
<h1 role="heading" aria-level="1">%s</h1>
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-10-15 01:14:29 +03:00
|
|
|
<nav id="info-bar" role="menubar">
|
2022-10-15 01:28:40 +03:00
|
|
|
<a role="menuitem" aria-label="jump to the main content" href="#main">\
|
2022-10-20 18:47:07 +03:00
|
|
|
skip</a>
|
2022-10-15 01:14:29 +03:00
|
|
|
<span role="seperator" aria-hidden="true">|</span>
|
|
|
|
|
2022-10-15 01:25:53 +03:00
|
|
|
<span role="menuitem"><time>%s</time> GMT</span>
|
2022-10-15 01:14:29 +03:00
|
|
|
<span role="seperator" aria-hidden="true">|</span>
|
|
|
|
|
2022-10-20 18:47:07 +03:00
|
|
|
<a role="menuitem" href="/">home</a>
|
2022-10-15 01:14:29 +03:00
|
|
|
<span role="seperator" aria-hidden="true">|</span>
|
|
|
|
|
2022-10-20 19:30:38 +03:00
|
|
|
<a role="menuitem" href="%s">comment</a>
|
2022-10-15 01:14:29 +03:00
|
|
|
<span role="seperator" aria-hidden="true">|</span>
|
|
|
|
|
2022-10-20 19:30:38 +03:00
|
|
|
<a role="menuitem" href="%s">website</a>
|
2022-10-15 01:14:29 +03:00
|
|
|
<span role="seperator" aria-hidden="true">|</span>
|
|
|
|
|
2022-10-20 19:30:38 +03:00
|
|
|
<a role="menuitem" href="%s">git</a>
|
2022-10-15 01:14:29 +03:00
|
|
|
|
|
|
|
<hr aria-hidden="true" role="seperator" />
|
2022-06-15 00:44:21 +03:00
|
|
|
</nav>
|
|
|
|
</header>
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-10-24 01:14:51 +03:00
|
|
|
<article id="main">
|
2022-10-15 01:14:29 +03:00
|
|
|
|
|
|
|
<!-- Main blog content: Begin -->
|
2022-03-12 23:44:22 +02:00
|
|
|
|
|
|
|
%s
|
|
|
|
|
2022-10-15 01:14:29 +03:00
|
|
|
<!-- Main blog content: End -->
|
|
|
|
|
2022-10-24 01:14:51 +03:00
|
|
|
</article>"""
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-06-15 00:44:21 +03:00
|
|
|
HTML_HEADER: str = f"""<head>
|
|
|
|
<meta charset="UTF-8"/>
|
|
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
|
|
<title>{{title}}</title>
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-06-15 00:44:21 +03:00
|
|
|
<meta property="og:locale" content="{{locale}}"/>
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-06-15 00:44:21 +03:00
|
|
|
<meta name="color-scheme" content="{{theme_type}}"/>
|
|
|
|
<meta name="author" content="{{author}}"/>
|
|
|
|
<meta name="keywords" content="{{keywords}}"/>
|
2022-06-17 12:57:50 +03:00
|
|
|
<meta name="robots" content="follow, index, max-snippet:-1, \
|
|
|
|
max-video-preview:-1, max-image-preview:large"/>
|
2022-06-15 00:44:21 +03:00
|
|
|
<meta name="generator" \
|
|
|
|
content="Ari-web blog generator version {BLOG_VERSION}"/>
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-08-03 02:35:11 +03:00
|
|
|
<link
|
|
|
|
rel="stylesheet"
|
|
|
|
href="/content/styles.min.css"
|
|
|
|
referrerpolicy="no-referrer"
|
|
|
|
type="text/css"
|
|
|
|
hreflang="en"
|
|
|
|
/>
|
|
|
|
"""
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-03-12 21:56:41 +02:00
|
|
|
BLOG_HTML_TEMPLATE: str = f"""<!DOCTYPE html>
|
2022-03-11 13:35:37 +02:00
|
|
|
<html lang="en">
|
|
|
|
{HTML_HEADER}
|
2022-06-15 00:44:21 +03:00
|
|
|
<meta name="description" content="{{blog_description}}"/>
|
|
|
|
<meta property="og:type" content="article"/>
|
2022-03-11 13:35:37 +02:00
|
|
|
</head>
|
|
|
|
<body>
|
2022-10-24 01:14:51 +03:00
|
|
|
<main id="blog-content">
|
2022-10-20 20:04:16 +03:00
|
|
|
|
|
|
|
{{blog}}
|
|
|
|
|
2022-10-24 01:14:51 +03:00
|
|
|
</main>
|
2022-03-11 13:35:37 +02:00
|
|
|
</body>
|
|
|
|
</html>"""
|
|
|
|
|
2022-03-12 21:56:41 +02:00
|
|
|
HOME_PAGE_HTML_TEMPLATE: str = f"""<!DOCTYPE html>
|
2022-03-11 13:35:37 +02:00
|
|
|
<html lang="en">
|
|
|
|
{HTML_HEADER}
|
2022-06-15 00:44:21 +03:00
|
|
|
<meta name="description" content="{{home_page_description}}"/>
|
|
|
|
<meta property="og:type" content="website"/>
|
2022-09-28 23:28:31 +03:00
|
|
|
|
2022-08-03 02:35:11 +03:00
|
|
|
<link
|
|
|
|
rel="manifest"
|
|
|
|
href="/manifest.json"
|
|
|
|
referrerpolicy="no-referrer"
|
|
|
|
type="application/json"
|
|
|
|
hreflang="en"
|
|
|
|
/>
|
2022-03-11 13:35:37 +02:00
|
|
|
</head>
|
|
|
|
<body>
|
2022-10-15 01:14:29 +03:00
|
|
|
<header>
|
|
|
|
<h1 role="heading" aria-level="1">{{page_header}}</h1>
|
|
|
|
|
2022-10-15 01:25:53 +03:00
|
|
|
<nav id="info-bar" role="navigation">
|
|
|
|
<p role="menubar">
|
2022-10-15 01:22:22 +03:00
|
|
|
<a
|
|
|
|
role="menuitem"
|
|
|
|
aria-label="jump to the main content"
|
|
|
|
href="#main"
|
2022-10-20 18:47:07 +03:00
|
|
|
>skip</a>
|
2022-10-15 01:14:29 +03:00
|
|
|
|
|
|
|
<span aria-hidden="true" role="seperator">|</span>
|
|
|
|
|
2022-10-15 01:25:53 +03:00
|
|
|
<span role="menuitem">
|
2022-10-15 01:14:29 +03:00
|
|
|
latest update: <time>{{lastest_blog_time}}</time> GMT
|
|
|
|
</span>
|
|
|
|
|
|
|
|
<span aria-hidden="true" role="seperator">|</span>
|
|
|
|
|
2022-10-15 01:22:22 +03:00
|
|
|
<span role="menuitem">
|
2022-10-15 01:14:29 +03:00
|
|
|
latest blog: \
|
|
|
|
<a href="{{latest_blog_url}}">{{latest_blog_title}}</a>
|
2022-10-15 01:25:53 +03:00
|
|
|
</span>
|
2022-10-15 01:14:29 +03:00
|
|
|
|
|
|
|
<span aria-hidden="true" role="seperator">|</span>
|
|
|
|
|
2022-10-15 01:22:22 +03:00
|
|
|
<a role="menuitem" href="{{git_url}}">git</a>
|
2022-10-15 01:14:29 +03:00
|
|
|
</p>
|
|
|
|
|
|
|
|
<hr aria-hidden="true" role="seperator" />
|
|
|
|
</nav>
|
|
|
|
</header>
|
|
|
|
|
|
|
|
<main id="main">
|
|
|
|
|
|
|
|
<!-- Main home page content: Begin -->
|
|
|
|
|
|
|
|
{{content}}
|
|
|
|
|
|
|
|
<!-- Main home page content: End -->
|
|
|
|
|
2022-06-14 23:01:25 +03:00
|
|
|
</main>
|
2022-03-11 13:35:37 +02:00
|
|
|
</body>
|
|
|
|
</html>"""
|
|
|
|
|
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
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):
|
2022-10-31 16:50:02 +02:00
|
|
|
ids: List[str] = []
|
|
|
|
heading_sizes_em: Dict[str, Union[float, int]] = {
|
|
|
|
"h1": 1.52,
|
|
|
|
"h2": 1.32,
|
|
|
|
"h3": 1.15,
|
|
|
|
"h4": 1,
|
|
|
|
"h5": 0.87,
|
|
|
|
"h6": 0.76,
|
|
|
|
}
|
|
|
|
all_headings: Tuple[str] = tuple(f"h{hl}" for hl in range(2, 7))
|
|
|
|
|
|
|
|
for idx, elem in enumerate(root):
|
|
|
|
if elem.tag not in all_headings:
|
|
|
|
continue
|
|
|
|
|
|
|
|
gen_id: str = sanitise_title(elem.text, ids)
|
|
|
|
ids.append(gen_id)
|
|
|
|
|
|
|
|
heading_parent = elem.makeelement(
|
|
|
|
"div",
|
|
|
|
{
|
|
|
|
"data-pl": "",
|
|
|
|
"style": f"font-size:{heading_sizes_em[elem.tag] + 0.1}em",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
heading = heading_parent.makeelement(elem.tag, {"id": gen_id})
|
|
|
|
link = heading.makeelement(
|
|
|
|
"a",
|
|
|
|
{
|
|
|
|
"href": f"#{gen_id}",
|
|
|
|
"aria-hidden": "true",
|
|
|
|
"focusable": "false",
|
|
|
|
"tabindex": "-1",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
link.text = "#"
|
|
|
|
heading_parent.append(link)
|
|
|
|
|
|
|
|
heading.text = elem.text
|
|
|
|
heading_parent.append(heading)
|
|
|
|
|
|
|
|
root.remove(elem)
|
|
|
|
root.insert(idx, heading_parent)
|
2022-10-31 03:28:33 +02:00
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
2022-03-11 13:35:37 +02:00
|
|
|
def log(message: str, header: str = "ERROR", code: int = EXIT_ERR) -> int:
|
2022-10-20 20:04:16 +03:00
|
|
|
if not (not NOT_CI_BUILD and header != "ERROR"):
|
|
|
|
sys.stderr.write(f"{header}: {message}\n")
|
|
|
|
|
2022-03-11 13:35:37 +02:00
|
|
|
return code
|
|
|
|
|
|
|
|
|
2022-09-20 04:50:25 +03:00
|
|
|
def tmp_path(path: str) -> str:
|
|
|
|
return os.path.join(gettempdir(), path)
|
|
|
|
|
|
|
|
|
|
|
|
def editor(config: Dict, file: str) -> None:
|
2022-09-23 02:00:23 +03:00
|
|
|
copy_file(".editorconfig", tmp_path(".editorconfig"))
|
2022-09-20 04:50:25 +03:00
|
|
|
os.system(config["editor-command"] % file)
|
|
|
|
|
|
|
|
|
2022-03-11 13:35:37 +02:00
|
|
|
def format_time(timestamp: float) -> str:
|
2022-03-11 14:08:05 +02:00
|
|
|
return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
|
2022-03-11 13:35:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
def iinput(prompt: str, default_text: str = "") -> str:
|
|
|
|
default_text = default_text.strip()
|
|
|
|
|
|
|
|
def hook():
|
|
|
|
if not default_text:
|
|
|
|
return
|
|
|
|
|
|
|
|
readline.insert_text(default_text)
|
|
|
|
readline.redisplay()
|
|
|
|
|
|
|
|
readline.set_pre_input_hook(hook)
|
|
|
|
user_inpt: str = input(f"({prompt}) ").strip()
|
|
|
|
readline.set_pre_input_hook()
|
|
|
|
|
|
|
|
return user_inpt
|
|
|
|
|
|
|
|
|
2022-06-01 16:19:08 +03:00
|
|
|
def yn(prompt: str, default: str = "y", current_value: str = "") -> bool:
|
|
|
|
return (
|
|
|
|
iinput(
|
|
|
|
f"{prompt}? ({'y/n'.replace(default.lower(), default.upper())})",
|
|
|
|
current_value,
|
|
|
|
)
|
|
|
|
+ default
|
|
|
|
).lower()[0] == "y"
|
|
|
|
|
|
|
|
|
2022-03-11 13:35:37 +02:00
|
|
|
def new_config() -> None:
|
|
|
|
log("Making new config...", "INFO")
|
|
|
|
|
|
|
|
with open(DEFAULT_CONFIG_FILE, "w") as cfg:
|
2022-08-30 02:55:42 +03:00
|
|
|
ujson.dump(DEFAULT_CONFIG, cfg, indent=4)
|
2022-03-11 13:35:37 +02:00
|
|
|
|
|
|
|
|
2022-03-11 13:46:45 +02:00
|
|
|
def pick_blog(config: Dict) -> str:
|
2022-03-11 13:35:37 +02:00
|
|
|
try:
|
|
|
|
blog_id: str = (
|
|
|
|
FzfPrompt()
|
|
|
|
.prompt(
|
|
|
|
map(
|
|
|
|
lambda key: f"{key} | {b64decode(config['blogs'][key]['title']).decode()!r}",
|
2022-05-26 22:06:22 +03:00
|
|
|
tuple(config["blogs"].keys())[::-1],
|
2022-03-11 13:35:37 +02:00
|
|
|
),
|
2022-03-11 15:48:17 +02:00
|
|
|
"--prompt='Pick blog: '",
|
2022-03-11 13:35:37 +02:00
|
|
|
)[0]
|
|
|
|
.split()[0]
|
|
|
|
)
|
|
|
|
except ProcessExecutionError:
|
|
|
|
log("Fzf process exited unexpectedly")
|
|
|
|
return ""
|
|
|
|
|
|
|
|
if blog_id not in config["blogs"]:
|
|
|
|
log(f"Blog {blog_id!r} does not exist")
|
|
|
|
return ""
|
|
|
|
|
|
|
|
return blog_id
|
|
|
|
|
|
|
|
|
2022-03-11 13:46:45 +02:00
|
|
|
def new_blog(config: Dict) -> Tuple[int, Dict]:
|
2022-03-11 13:35:37 +02:00
|
|
|
"""Make a new blog"""
|
|
|
|
|
|
|
|
if title := iinput("blog title"):
|
2022-04-07 22:32:55 +03:00
|
|
|
readline.add_history(title)
|
|
|
|
|
2022-03-11 13:35:37 +02:00
|
|
|
us_title: str = title
|
|
|
|
s_title: str = sanitise_title(us_title, config["blogs"])
|
|
|
|
|
|
|
|
blog = {
|
|
|
|
"title": b64encode(us_title.encode()).decode(),
|
|
|
|
"content": "",
|
|
|
|
"version": BLOG_VERSION,
|
2022-04-02 21:27:19 +03:00
|
|
|
"time": 0.0,
|
2022-03-11 13:35:37 +02:00
|
|
|
"keywords": "",
|
|
|
|
}
|
|
|
|
|
2022-09-20 04:50:25 +03:00
|
|
|
file: str = tmp_path(f"{s_title}.md")
|
2022-03-11 13:35:37 +02:00
|
|
|
|
|
|
|
open(file, "w").close()
|
2022-09-20 04:50:25 +03:00
|
|
|
editor(config, file)
|
2022-03-11 13:35:37 +02:00
|
|
|
|
|
|
|
if not os.path.isfile(file):
|
|
|
|
return log(f"{file!r} does not exist"), config
|
|
|
|
|
|
|
|
with open(file, "r") as md:
|
|
|
|
blog["content"] = b64encode(md.read().encode()).decode()
|
|
|
|
|
2022-03-12 21:56:41 +02:00
|
|
|
os.remove(file)
|
|
|
|
|
2022-03-11 13:35:37 +02:00
|
|
|
if not blog["content"].strip(): # type: ignore
|
|
|
|
return log("Blog cannot be empty"), config
|
|
|
|
|
2022-04-07 22:32:55 +03:00
|
|
|
user_keywords: str = iinput("keywords (seperated by spaces)")
|
|
|
|
readline.add_history(user_keywords)
|
|
|
|
|
2022-05-21 18:29:13 +03:00
|
|
|
blog["keywords"] = html_escape(user_keywords)
|
2022-04-17 17:06:42 +03:00
|
|
|
|
2022-03-11 13:35:37 +02:00
|
|
|
blog["time"] = datetime.now().timestamp()
|
|
|
|
config["blogs"][s_title] = blog
|
|
|
|
|
|
|
|
return EXIT_OK, config
|
|
|
|
|
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
def build_css(config: Dict) -> Tuple[int, Dict]:
|
|
|
|
"""Minify (build) the CSS"""
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
log("Minifying CSS...", "MINIFY")
|
2022-06-21 13:27:59 +03:00
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
saved_stdout = sys.stdout
|
|
|
|
sys.stdout = open(os.devnull, "w")
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
css_threads: List[Thread] = []
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
def _thread(t: Callable) -> None:
|
|
|
|
css_threads.append(Thread(target=t, daemon=True))
|
|
|
|
css_threads[-1].start()
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
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
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
log(f"Minifying font file: {font}", "MINIFY")
|
|
|
|
_thread(lambda: process_single_css_file(font))
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
for t in css_threads:
|
|
|
|
t.join()
|
2022-10-20 20:04:16 +03:00
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
sys.stdout.close()
|
|
|
|
sys.stdout = saved_stdout
|
2022-10-20 20:04:16 +03:00
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
log("Done minifying CSS", "MINIFY")
|
2022-10-20 20:04:16 +03:00
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
return EXIT_OK, config
|
2022-10-20 20:04:16 +03:00
|
|
|
|
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
def build(config: Dict) -> Tuple[int, Dict]:
|
|
|
|
"""Build, minimise and generate site"""
|
2022-10-20 20:04:16 +03:00
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
if not config["blogs"]:
|
|
|
|
return log("No blogs to build"), config
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
latest_blog_id: str = tuple(config["blogs"].keys())[-1]
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
if os.path.isdir(config["blog-dir"]):
|
|
|
|
rmtree(config["blog-dir"])
|
2022-04-17 17:18:56 +03:00
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
os.makedirs(config["blog-dir"], exist_ok=True)
|
2022-03-11 13:35:37 +02:00
|
|
|
|
|
|
|
log("Building blogs...", "INFO")
|
|
|
|
|
2022-06-01 16:19:08 +03:00
|
|
|
def thread(blog_id: str, blog_meta: Dict):
|
2022-03-11 17:47:54 +02:00
|
|
|
if blog_meta["version"] != BLOG_VERSION:
|
|
|
|
log(
|
2022-06-15 00:44:21 +03:00
|
|
|
f"{blog_id}: unmatching version between \
|
|
|
|
{blog_meta['version']} and {BLOG_VERSION}",
|
2022-03-11 17:47:54 +02:00
|
|
|
"WARNING",
|
|
|
|
)
|
|
|
|
|
2022-06-01 16:19:08 +03:00
|
|
|
blog_dir: str = os.path.join(config["blog-dir"], blog_id)
|
2022-03-11 13:35:37 +02:00
|
|
|
os.makedirs(blog_dir, exist_ok=True)
|
|
|
|
|
|
|
|
with open(os.path.join(blog_dir, "index.html"), "w") as blog_html:
|
|
|
|
blog_time: str = format_time(blog_meta["time"])
|
|
|
|
|
|
|
|
blog_title: str = html_escape(b64decode(blog_meta["title"]).decode())
|
|
|
|
|
|
|
|
blog_base_html: str = markdown(
|
|
|
|
BLOG_MARKDOWN_TEMPLATE
|
|
|
|
% (
|
|
|
|
blog_title,
|
|
|
|
blog_time,
|
2022-10-20 19:30:38 +03:00
|
|
|
config["comment-url"],
|
2022-03-11 13:35:37 +02:00
|
|
|
config["base-homepage"],
|
|
|
|
config["git-url"],
|
2022-08-19 02:12:47 +03:00
|
|
|
markdown(
|
|
|
|
b64decode(blog_meta["content"]).decode(),
|
2022-10-31 03:28:33 +02:00
|
|
|
extensions=[
|
|
|
|
*config["py-markdown-extensions"],
|
|
|
|
AddHeaderLinksExt(),
|
|
|
|
],
|
2022-08-19 02:12:47 +03:00
|
|
|
)
|
2022-03-11 13:35:37 +02:00
|
|
|
.replace("<h1>", "<h2>")
|
2022-10-15 00:25:51 +03:00
|
|
|
.replace("<h1/>", "<h2/>")
|
2022-10-31 03:28:33 +02:00
|
|
|
.replace("<pre>", '<pre focusable="true" role="code" tabindex="0">')
|
2022-10-15 01:14:29 +03:00
|
|
|
.replace(
|
2022-10-31 03:28:33 +02:00
|
|
|
"<blockquote>", '<blockquote focusable="true" tabindex="0">'
|
2022-10-15 01:14:29 +03:00
|
|
|
),
|
2022-08-19 02:12:47 +03:00
|
|
|
)
|
2022-03-11 13:35:37 +02:00
|
|
|
)
|
|
|
|
|
2022-04-17 17:06:42 +03:00
|
|
|
blog_html_full: str = BLOG_HTML_TEMPLATE.format(
|
|
|
|
title=config["page-title"],
|
|
|
|
theme_type=config["colourscheme-type"],
|
2022-06-15 00:44:21 +03:00
|
|
|
keywords=blog_meta["keywords"].replace(" ", ", ")
|
2022-06-21 00:19:14 +03:00
|
|
|
+ ", "
|
2022-06-15 00:44:21 +03:00
|
|
|
+ ", ".join(config["default-keywords"]),
|
2022-05-08 23:37:41 +03:00
|
|
|
blog_description=f"Blog on {blog_time} GMT -- {blog_title}",
|
2022-04-17 17:06:42 +03:00
|
|
|
blog=blog_base_html,
|
2022-06-15 00:44:21 +03:00
|
|
|
author=config["full-name"],
|
|
|
|
locale=config["locale"],
|
2022-03-11 13:35:37 +02:00
|
|
|
)
|
|
|
|
|
2022-08-30 02:55:42 +03:00
|
|
|
log(f"Minifying {blog_id!r} HTML", "MINIFY")
|
|
|
|
blog_html_full = html_minify(blog_html_full)
|
|
|
|
log(f"Done minifying the HTML of {blog_id!r}", "MINIFY")
|
2022-04-17 17:06:42 +03:00
|
|
|
|
|
|
|
blog_html.write(blog_html_full)
|
|
|
|
|
2022-06-01 16:19:08 +03:00
|
|
|
log(f"Finished building blog {blog_id!r}", "BUILD")
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-08-16 04:30:58 +03:00
|
|
|
_tmp_threads: List[Thread] = []
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-06-01 16:19:08 +03:00
|
|
|
for blog_id, blog_meta in config["blogs"].items():
|
2022-08-16 04:35:00 +03:00
|
|
|
t: Thread = Thread(target=thread, args=(blog_id, blog_meta), daemon=True)
|
2022-03-11 13:35:37 +02:00
|
|
|
t.start()
|
|
|
|
|
2022-08-16 04:30:58 +03:00
|
|
|
_tmp_threads.append(t)
|
|
|
|
|
2022-03-11 13:35:37 +02:00
|
|
|
for awaiting_thread in _tmp_threads:
|
|
|
|
awaiting_thread.join()
|
|
|
|
|
|
|
|
log("Building blog index...", "INFO")
|
|
|
|
|
|
|
|
with open("index.html", "w") as index:
|
2022-03-11 13:46:45 +02:00
|
|
|
lastest_blog: Dict = config["blogs"][latest_blog_id]
|
2022-03-11 13:35:37 +02:00
|
|
|
lastest_blog_time: str = format_time(lastest_blog["time"])
|
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
blog_list = '<ol reversed="true" aria-label="latest blogs">'
|
2022-03-11 13:35:37 +02:00
|
|
|
|
|
|
|
for blog_id, blog_meta in reversed(config["blogs"].items()):
|
2022-10-31 03:28:33 +02:00
|
|
|
blog_list += f'<li><a href="{os.path.join(config["blog-dir"], blog_id)}">{html_escape(b64decode(blog_meta["title"]).decode())}</a></li>'
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
blog_list += "</ol>"
|
2022-03-11 13:35:37 +02:00
|
|
|
|
|
|
|
index.write(
|
|
|
|
html_minify(
|
|
|
|
HOME_PAGE_HTML_TEMPLATE.format(
|
|
|
|
title=config["page-title"],
|
|
|
|
theme_type=config["colourscheme-type"],
|
2022-06-21 00:19:14 +03:00
|
|
|
keywords=", ".join(config["home-keywords"])
|
|
|
|
+ ", "
|
|
|
|
+ ", ".join(config["default-keywords"]),
|
2022-03-11 13:35:37 +02:00
|
|
|
home_page_description=config["page-description"],
|
|
|
|
lastest_blog_time=lastest_blog_time,
|
|
|
|
latest_blog_url=os.path.join(config["blog-dir"], latest_blog_id),
|
2022-10-31 03:28:33 +02:00
|
|
|
latest_blog_title=truncate_str(
|
|
|
|
b64decode(html_escape(lastest_blog["title"])).decode(), 20
|
|
|
|
),
|
2022-03-11 13:35:37 +02:00
|
|
|
git_url=config["git-url"],
|
|
|
|
content=blog_list,
|
2022-06-15 00:44:21 +03:00
|
|
|
author=config["full-name"],
|
|
|
|
locale=config["locale"],
|
2022-06-17 00:05:19 +03:00
|
|
|
page_header=config["home-page-header"],
|
2022-03-11 13:35:37 +02:00
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
return EXIT_OK, config
|
|
|
|
|
|
|
|
|
2022-03-11 13:46:45 +02:00
|
|
|
def list_blogs(config: Dict) -> Tuple[int, Dict]:
|
2022-03-11 13:35:37 +02:00
|
|
|
"""List blogs"""
|
|
|
|
|
2022-03-11 17:47:54 +02:00
|
|
|
if not config["blogs"]:
|
|
|
|
return log("No blogs to list"), config
|
|
|
|
|
2022-03-11 13:35:37 +02:00
|
|
|
for blog_id, blog_meta in config["blogs"].items():
|
|
|
|
print(
|
|
|
|
f"""ID: {blog_id}
|
2022-06-15 00:44:21 +03:00
|
|
|
Title: {b64decode(blog_meta["title"]).decode()!r}
|
|
|
|
Version: {blog_meta["version"]}
|
|
|
|
Time_of_creation: {format_time(blog_meta["time"])}
|
|
|
|
Keywords: {blog_meta['keywords'].replace(" ", ", ")}
|
2022-03-11 13:35:37 +02:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
|
|
|
|
return EXIT_OK, config
|
|
|
|
|
|
|
|
|
2022-03-11 13:46:45 +02:00
|
|
|
def remove_blog(config: Dict) -> Tuple[int, Dict]:
|
2022-03-11 13:35:37 +02:00
|
|
|
"""Remove a blog page"""
|
|
|
|
|
2022-03-11 17:47:54 +02:00
|
|
|
if not config["blogs"]:
|
|
|
|
return log("No blogs to remove"), config
|
|
|
|
|
2022-03-11 13:35:37 +02:00
|
|
|
blog_id: str = pick_blog(config)
|
|
|
|
|
|
|
|
if not blog_id:
|
|
|
|
return EXIT_ERR, config
|
|
|
|
|
|
|
|
del config["blogs"][blog_id]
|
|
|
|
return EXIT_OK, config
|
|
|
|
|
|
|
|
|
|
|
|
def dummy() -> None:
|
|
|
|
"""Print help/usage information"""
|
|
|
|
|
|
|
|
|
2022-03-11 13:46:45 +02:00
|
|
|
def edit_title(blog: str, config: Dict) -> int:
|
2022-03-11 13:35:37 +02:00
|
|
|
new_title: str = iinput(
|
|
|
|
"edit title", b64decode(config["blogs"][blog]["title"]).decode()
|
|
|
|
)
|
|
|
|
|
|
|
|
if not new_title.strip():
|
|
|
|
return log("New title cannot be empty")
|
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
# Made it not change the slug
|
|
|
|
|
|
|
|
# old_blog: dict = config["blogs"][blog].copy()
|
|
|
|
# old_blog["title"] = b64encode(new_title.encode()).decode()
|
|
|
|
# del config["blogs"][blog]
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-10-31 03:28:33 +02:00
|
|
|
# config["blogs"][sanitise_title(new_title, config["blogs"])] = old_blog
|
|
|
|
# del old_blog
|
|
|
|
|
|
|
|
config["blogs"][blog]["title"] = b64encode(new_title.encode()).decode()
|
2022-03-14 23:24:28 +02:00
|
|
|
|
2022-03-11 13:35:37 +02:00
|
|
|
return EXIT_OK
|
|
|
|
|
|
|
|
|
2022-03-11 13:46:45 +02:00
|
|
|
def edit_keywords(blog: str, config: Dict) -> int:
|
2022-03-11 13:35:37 +02:00
|
|
|
new_keywords: str = iinput("edit keywords", config["blogs"][blog]["keywords"])
|
|
|
|
|
|
|
|
if not new_keywords.strip():
|
|
|
|
return log("Keywords cannot be empty")
|
|
|
|
|
|
|
|
config["blogs"][blog]["keywords"] = new_keywords
|
|
|
|
|
|
|
|
return EXIT_OK
|
|
|
|
|
|
|
|
|
2022-03-11 13:46:45 +02:00
|
|
|
def edit_content(blog: str, config: Dict) -> int:
|
2022-09-20 04:50:25 +03:00
|
|
|
file: str = tmp_path(f"{blog}.md")
|
2022-03-11 13:35:37 +02:00
|
|
|
|
|
|
|
with open(file, "w") as blog_md:
|
|
|
|
blog_md.write(b64decode(config["blogs"][blog]["content"]).decode())
|
|
|
|
|
2022-09-20 04:50:25 +03:00
|
|
|
editor(config, file)
|
2022-03-11 13:35:37 +02:00
|
|
|
|
|
|
|
with open(file, "r") as blog_md_new:
|
|
|
|
content: str = blog_md_new.read()
|
|
|
|
|
|
|
|
if not content.strip():
|
|
|
|
blog_md_new.close()
|
|
|
|
return log("Content of a blog cannot be empty")
|
|
|
|
|
|
|
|
config["blogs"][blog]["content"] = b64encode(content.encode()).decode()
|
|
|
|
|
|
|
|
return EXIT_OK
|
|
|
|
|
|
|
|
|
2022-03-11 13:46:45 +02:00
|
|
|
EDIT_HOOKS: Dict = {
|
2022-05-26 22:06:22 +03:00
|
|
|
"quit": lambda *_: EXIT_OK,
|
2022-03-11 13:35:37 +02:00
|
|
|
"title": edit_title,
|
|
|
|
"keywords": edit_keywords,
|
|
|
|
"content": edit_content,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-03-11 13:46:45 +02:00
|
|
|
def edit(config: Dict) -> Tuple[int, Dict]:
|
2022-03-11 13:35:37 +02:00
|
|
|
"""Edit a blog"""
|
|
|
|
|
2022-03-11 17:47:54 +02:00
|
|
|
if not config["blogs"]:
|
|
|
|
return log("No blogs to edit"), config
|
|
|
|
|
2022-03-11 13:35:37 +02:00
|
|
|
blog_id: str = pick_blog(config)
|
|
|
|
|
|
|
|
if not blog_id:
|
|
|
|
return EXIT_ERR, config
|
|
|
|
|
|
|
|
try:
|
2022-03-11 15:48:17 +02:00
|
|
|
hook: str = FzfPrompt().prompt(EDIT_HOOKS.keys(), "--prompt='What to edit: '")[
|
|
|
|
0
|
|
|
|
]
|
2022-03-11 13:35:37 +02:00
|
|
|
|
|
|
|
if hook not in EDIT_HOOKS:
|
|
|
|
return log(f"Hook {hook!r} does not exist"), config
|
|
|
|
|
|
|
|
EDIT_HOOKS[hook](blog_id, config)
|
|
|
|
except ProcessExecutionError:
|
2022-06-01 16:19:08 +03:00
|
|
|
return log("No blog selected"), config
|
2022-03-11 13:35:37 +02:00
|
|
|
|
|
|
|
return EXIT_OK, config
|
|
|
|
|
|
|
|
|
2022-06-05 21:49:56 +03:00
|
|
|
def gen_def_config(config: Dict) -> Tuple[int, Dict]:
|
|
|
|
"""Generate default config"""
|
2022-03-11 13:35:37 +02:00
|
|
|
|
|
|
|
if os.path.exists(DEFAULT_CONFIG_FILE):
|
|
|
|
if iinput("Do you want to overwite config? (y/n)").lower()[0] != "y":
|
|
|
|
return log("Not overwritting config", "INFO", EXIT_OK), config
|
|
|
|
|
|
|
|
new_config()
|
|
|
|
|
|
|
|
with open(DEFAULT_CONFIG_FILE, "r") as cfg:
|
2022-08-30 02:55:42 +03:00
|
|
|
config = ujson.load(cfg)
|
2022-03-11 13:35:37 +02:00
|
|
|
|
|
|
|
return EXIT_OK, config
|
|
|
|
|
|
|
|
|
2022-03-11 17:47:54 +02:00
|
|
|
def clean(config: Dict) -> Tuple[int, Dict]:
|
|
|
|
"""Clean up current directory"""
|
|
|
|
|
|
|
|
TRASH: Set[str] = {
|
|
|
|
HISTORY_FILE,
|
|
|
|
config["blog-dir"],
|
|
|
|
"index.html",
|
|
|
|
"content/*.min.*",
|
2022-09-23 02:28:23 +03:00
|
|
|
"blog_json_hash.txt",
|
2022-04-02 20:43:37 +03:00
|
|
|
"manifest.json",
|
2022-03-11 17:47:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
def remove(file: str) -> None:
|
|
|
|
log(f"Removing {file!r}", "REMOVE")
|
|
|
|
|
|
|
|
try:
|
|
|
|
os.remove(file)
|
|
|
|
except IsADirectoryError:
|
|
|
|
rmtree(file)
|
|
|
|
|
|
|
|
for glob_ex in TRASH:
|
|
|
|
for file in iglob(glob_ex, recursive=True):
|
|
|
|
remove(file)
|
|
|
|
|
|
|
|
open(HISTORY_FILE, "w").close()
|
|
|
|
|
|
|
|
return EXIT_OK, config
|
|
|
|
|
|
|
|
|
2022-04-02 20:43:37 +03:00
|
|
|
def generate_metadata(config: Dict) -> Tuple[int, Dict]:
|
|
|
|
"""Generate metadata"""
|
|
|
|
|
|
|
|
with open("manifest.json", "w") as manifest:
|
|
|
|
log(f"Generating {manifest.name}...", "GENERATE")
|
2022-08-30 02:55:42 +03:00
|
|
|
ujson.dump(
|
2022-04-02 20:43:37 +03:00
|
|
|
{
|
|
|
|
"$schema": "https://json.schemastore.org/web-manifest-combined.json",
|
|
|
|
"short_name": config["short-name"],
|
|
|
|
"name": config["page-title"],
|
|
|
|
"description": config["page-description"],
|
|
|
|
"icons": config["meta-icons"],
|
|
|
|
"start_url": ".",
|
|
|
|
"display": "standalone",
|
|
|
|
"theme_color": config["theme-colour"],
|
|
|
|
"background_color": config["background-colour"],
|
|
|
|
},
|
|
|
|
manifest,
|
|
|
|
)
|
|
|
|
|
2022-09-23 01:29:49 +03:00
|
|
|
with open(DEFAULT_CONFIG_FILE, "rb") as blog_api_file:
|
2022-09-23 02:28:23 +03:00
|
|
|
log(f"Generating hash for {DEFAULT_CONFIG_FILE!r}", "HASH")
|
2022-09-23 01:29:49 +03:00
|
|
|
|
|
|
|
with open(
|
|
|
|
f"{DEFAULT_CONFIG_FILE.replace('.', '_')}_hash.txt", "w"
|
|
|
|
) as blog_api_hash:
|
|
|
|
blog_api_hash.write(hashlib.sha256(blog_api_file.read()).hexdigest())
|
|
|
|
|
2022-04-02 20:43:37 +03:00
|
|
|
return EXIT_OK, config
|
|
|
|
|
|
|
|
|
|
|
|
def generate_static_full(config: Dict) -> Tuple[int, Dict]:
|
|
|
|
"""Generate full static site"""
|
|
|
|
|
2022-04-02 20:55:43 +03:00
|
|
|
BUILD_CFG: Dict = {
|
2022-04-02 20:43:37 +03:00
|
|
|
"Cleaning up": clean,
|
2022-10-31 03:28:33 +02:00
|
|
|
"Building CSS": build_css,
|
2022-04-02 20:43:37 +03:00
|
|
|
"Building static site": build,
|
|
|
|
"Generating metatata": generate_metadata,
|
|
|
|
}
|
|
|
|
|
2022-04-02 20:55:43 +03:00
|
|
|
for logger_msg, function in BUILD_CFG.items():
|
2022-04-02 20:43:37 +03:00
|
|
|
log(f"{logger_msg}...", "STATIC")
|
2022-04-06 20:51:31 +03:00
|
|
|
code, config = function(config)
|
|
|
|
|
|
|
|
if code != EXIT_OK:
|
|
|
|
log("Failed to generate static site")
|
|
|
|
return EXIT_ERR, config
|
2022-04-02 20:43:37 +03:00
|
|
|
|
|
|
|
return EXIT_OK, config
|
|
|
|
|
|
|
|
|
2022-03-11 13:46:45 +02:00
|
|
|
SUBCOMMANDS: Dict = {
|
2022-03-11 13:35:37 +02:00
|
|
|
"help": dummy,
|
|
|
|
"new": new_blog,
|
|
|
|
"build": build,
|
2022-06-05 21:49:56 +03:00
|
|
|
"ls": list_blogs,
|
2022-03-11 13:35:37 +02:00
|
|
|
"rm": remove_blog,
|
|
|
|
"edit": edit,
|
2022-06-05 21:49:56 +03:00
|
|
|
"defcfg": gen_def_config,
|
2022-03-11 17:47:54 +02:00
|
|
|
"clean": clean,
|
2022-04-02 20:43:37 +03:00
|
|
|
"metadata": generate_metadata,
|
|
|
|
"static": generate_static_full,
|
2022-10-31 03:28:33 +02:00
|
|
|
"css": build_css,
|
2022-03-11 13:35:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-03-11 13:46:45 +02:00
|
|
|
def usage(code: int = EXIT_ERR, config: Dict = None) -> int:
|
2022-03-11 13:35:37 +02:00
|
|
|
sys.stderr.write(f"Usage: {sys.argv[0]} <subcommand>\n")
|
|
|
|
|
|
|
|
for subcommand, func in SUBCOMMANDS.items():
|
|
|
|
sys.stderr.write(f" {subcommand:20s}{func.__doc__ or ''}\n")
|
|
|
|
|
|
|
|
return code
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> int:
|
|
|
|
"""Entry/main function"""
|
|
|
|
|
2022-08-17 01:51:03 +03:00
|
|
|
if NOT_CI_BUILD:
|
|
|
|
if not os.path.isfile(HISTORY_FILE):
|
|
|
|
open(HISTORY_FILE, "w").close()
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-08-17 01:51:03 +03:00
|
|
|
readline.parse_and_bind("tab: complete")
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-08-17 01:51:03 +03:00
|
|
|
fn_register(readline.write_history_file, HISTORY_FILE)
|
|
|
|
fn_register(readline.read_history_file, HISTORY_FILE)
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-08-17 01:51:03 +03:00
|
|
|
readline.read_history_file(HISTORY_FILE)
|
|
|
|
readline.set_history_length(5000)
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-08-17 01:51:03 +03:00
|
|
|
readline.set_auto_history(False)
|
2022-04-07 22:32:55 +03:00
|
|
|
|
2022-03-11 13:35:37 +02:00
|
|
|
if not os.path.isfile(DEFAULT_CONFIG_FILE):
|
|
|
|
new_config()
|
2022-08-17 01:51:03 +03:00
|
|
|
log(f"PLease configure {DEFAULT_CONFIG_FILE!r}")
|
2022-03-11 13:35:37 +02:00
|
|
|
return EXIT_ERR
|
|
|
|
|
|
|
|
if len(sys.argv) != 2:
|
|
|
|
return usage()
|
|
|
|
elif sys.argv[1] not in SUBCOMMANDS:
|
|
|
|
return log(f"{sys.argv[1]!r} is not a subcommand, try `{sys.argv[0]} help`")
|
|
|
|
elif sys.argv[1] == "help":
|
|
|
|
return usage(EXIT_OK)
|
|
|
|
|
|
|
|
with open(DEFAULT_CONFIG_FILE, "r") as lcfg:
|
2022-04-02 20:43:37 +03:00
|
|
|
cmd_time_init = code_timer()
|
|
|
|
|
2022-08-30 02:55:42 +03:00
|
|
|
code, config = SUBCOMMANDS[sys.argv[1]](config=ujson.load(lcfg))
|
2022-03-11 13:35:37 +02:00
|
|
|
|
2022-04-02 20:55:43 +03:00
|
|
|
log(
|
|
|
|
f"Finished in {code_timer() - cmd_time_init} seconds with code {code}",
|
|
|
|
"TIME",
|
|
|
|
)
|
2022-04-02 20:43:37 +03:00
|
|
|
|
2022-08-17 01:51:03 +03:00
|
|
|
if config["blogs"] and NOT_CI_BUILD:
|
2022-03-11 17:47:54 +02:00
|
|
|
log("Sorting blogs by creation time...", "CLEANUP")
|
2022-04-07 22:32:55 +03:00
|
|
|
|
|
|
|
sort_timer = code_timer()
|
|
|
|
|
2022-03-11 17:47:54 +02:00
|
|
|
config["blogs"] = dict(
|
|
|
|
map(
|
|
|
|
lambda k: (k, config["blogs"][k]),
|
|
|
|
sorted(config["blogs"], key=lambda k: config["blogs"][k]["time"]),
|
|
|
|
)
|
2022-03-11 13:35:37 +02:00
|
|
|
)
|
|
|
|
|
2022-04-07 22:32:55 +03:00
|
|
|
log(f"Sorted in {code_timer() - sort_timer} seconds", "TIME")
|
|
|
|
|
2022-08-17 01:51:03 +03:00
|
|
|
log("Redumping config", "CONFIG")
|
2022-04-07 22:32:55 +03:00
|
|
|
|
2022-08-17 01:51:03 +03:00
|
|
|
dump_timer = code_timer()
|
2022-04-07 22:32:55 +03:00
|
|
|
|
2022-08-17 01:51:03 +03:00
|
|
|
with open(DEFAULT_CONFIG_FILE, "w") as dcfg:
|
2022-08-30 02:57:37 +03:00
|
|
|
ujson.dump(config, dcfg, indent=(4 if NOT_CI_BUILD else 0))
|
2022-04-07 22:32:55 +03:00
|
|
|
|
2022-08-17 01:51:03 +03:00
|
|
|
log(f"Dumped config in {code_timer() - dump_timer} seconds", "TIME")
|
2022-03-11 13:35:37 +02:00
|
|
|
|
|
|
|
return code
|
|
|
|
|
|
|
|
return EXIT_OK
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
assert main.__annotations__.get("return") is int, "main() should return an integer"
|
|
|
|
|
|
|
|
filter_warnings("error", category=Warning)
|
|
|
|
sys.exit(main())
|