6 Commits

Author SHA1 Message Date
de8cbefb8e feat: task lists
All checks were successful
Lint / shellcheck (push) Successful in 18s
2026-03-20 08:32:28 +01:00
cc7fee573f feat: custom admonitions 2026-03-20 08:31:58 +01:00
137be9579a feat: incremental rebuilds 2026-03-20 08:30:25 +01:00
5afd0170e5 fix: less tmp file spam 2026-03-20 08:29:05 +01:00
5a2053cfb4 fix: publish-aur-git shoudln't run so often now
All checks were successful
Lint / shellcheck (push) Successful in 19s
2026-03-19 22:34:31 +01:00
2fc3d6fc6f fix: AUR links
All checks were successful
Lint / shellcheck (push) Successful in 18s
Publish kewt-git to AUR / publish-aur-git (push) Successful in 29s
Release Standalone Builder / build (release) Successful in 30s
Release Standalone Builder / publish-aur (release) Successful in 44s
2026-03-19 21:50:34 +01:00
12 changed files with 138 additions and 41 deletions

View File

@@ -2,6 +2,8 @@ name: Publish kewt-git to AUR
on:
push:
branches:
- main
paths:
- 'packaging/AUR/PKGBUILD.git'
- 'packaging/AUR/.SRCINFO.git'

View File

@@ -22,7 +22,19 @@ END {
sub(/^\[!/, "", kind)
sub(/\]$/, "", kind)
lkind = tolower(kind)
if (lkind == "note" || lkind == "tip" || lkind == "important" || lkind == "warning" || lkind == "caution") {
is_valid = 0
if (custom_admonitions != "") {
n = split(tolower(custom_admonitions), adms, ",")
for (idx = 1; idx <= n; idx++) {
adm = adms[idx]
sub(/^[ \t]+/, "", adm)
sub(/[ \t]+$/, "", adm)
if (lkind == adm) { is_valid = 1; break }
}
} else if (lkind == "note" || lkind == "tip" || lkind == "important" || lkind == "warning" || lkind == "caution") {
is_valid = 1
}
if (is_valid) {
print "<div class=\"admonition admonition-" lkind "\">"
print "<p class=\"admonition-title\">" cap(lkind) "</p>"
has_body = 0

View File

@@ -48,7 +48,26 @@ BEGIN {
}
}
print "<li>" content "</li>"
has_checkbox = 0
if (content ~ /^\[[ \t]\] /) {
has_checkbox = 1
is_checked = 0
sub(/^\[[ \t]\] /, "", content)
} else if (content ~ /^\[[xX]\] /) {
has_checkbox = 1
is_checked = 1
sub(/^\[[xX]\] /, "", content)
}
if (has_checkbox) {
if (is_checked) {
print "<li class=\"task-list-item\"><input type=\"checkbox\" class=\"task-list-item-checkbox\" checked disabled> " content "</li>"
} else {
print "<li class=\"task-list-item\"><input type=\"checkbox\" class=\"task-list-item-checkbox\" disabled> " content "</li>"
}
} else {
print "<li>" content "</li>"
}
} else {
while (depth > 0) {
print "</" cur_type[depth] ">"

106
kewt.sh
View File

@@ -33,9 +33,17 @@ awk_dir="$script_dir/awk"
KEWT_TMPDIR=$(mktemp -d "/tmp/kewt_run.XXXXXX")
trap 'rm -rf "$KEWT_TMPDIR"' EXIT HUP INT TERM
ensure_root_defaults() {
if [ ! -f "./site.conf" ]; then
cat > "./site.conf" <<'EOF'
create_new_site() {
new_title="$1"
new_dir="site"
[ -n "$new_title" ] && new_dir="$new_title"
[ -e "$new_dir" ] && die "Target '$new_dir' already exists."
mkdir -p "$new_dir"
cat > "$new_dir/site.conf" <<'EOF'
title = "kewt"
style = "kewt"
dir_indexes = true
@@ -60,11 +68,10 @@ base_url = ""
generate_feed = false
feed_file = "rss.xml"
posts_dir = ""
custom_admonitions = ""
EOF
fi
if [ ! -f "./template.html" ]; then
cat > "./template.html" <<'EOF'
cat > "$new_dir/template.html" <<'EOF'
<!doctype html>
<html>
<head>
@@ -88,20 +95,6 @@ EOF
</body>
</html>
EOF
fi
}
create_new_site() {
new_title="$1"
new_dir="site"
[ -n "$new_title" ] && new_dir="$new_title"
[ -e "$new_dir" ] && die "Target '$new_dir' already exists."
ensure_root_defaults
mkdir -p "$new_dir"
cp "./site.conf" "$new_dir/site.conf"
printf "# _kewt_ website\n" > "$new_dir/index.md"
if [ -n "$new_title" ]; then
@@ -173,6 +166,7 @@ base_url = ""
generate_feed = false
feed_file = "rss.xml"
posts_dir = ""
custom_admonitions = ""
CONFEOF
# Update site.conf
@@ -307,7 +301,7 @@ done
[ "$new_mode" = "true" ] && create_new_site "$new_title"
ensure_root_defaults
if [ -z "$src" ]; then
if [ "$post_mode" = "true" ] && [ -f "./site.conf" ]; then
@@ -454,6 +448,7 @@ base_url=""
generate_feed="false"
feed_file="rss.xml"
posts_dir=""
custom_admonitions=""
load_config() {
[ -f "$1" ] || return
@@ -505,6 +500,7 @@ load_config() {
generate_feed) generate_feed="$val" ;;
feed_file) feed_file="${val#/}" ;;
posts_dir) posts_dir="${val#/}" ;;
custom_admonitions) custom_admonitions="$val" ;;
esac
done < "$1"
}
@@ -584,7 +580,33 @@ nav_links_html() {
template="$src/template.html"
[ -f "$template" ] || template="./template.html"
[ -f "$template" ] || die "Template '$template' not found."
if [ ! -f "$template" ]; then
template="$KEWT_TMPDIR/default_template.html"
cat > "$template" <<'EOF'
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{TITLE}}</title>
<link rel="stylesheet" href="{{CSS}}" type="text/css" />
{{HEAD_EXTRA}}
</head>
<body>
<header>
<h1>{{HEADER_BRAND}}</h1>
</header>
<nav id="side-bar">{{NAV}}</nav>
<article>{{CONTENT}}</article>
<footer>{{FOOTER}}</footer>
</body>
</html>
EOF
fi
[ -d "$out" ] && rm -rf "$out"
mkdir -p "$out"
@@ -715,7 +737,19 @@ render_markdown() {
fi
fi
ENABLE_HEADER_LINKS="$enable_header_links" MARKDOWN_SITE_ROOT="$src" MARKDOWN_FALLBACK_FILE="$script_dir/styles/$style.css" sh "$script_dir/markdown.sh" "$content_file" | AWK_CURRENT_URL="$current_url" AWK_TITLE="$page_title" AWK_NAV="$nav" AWK_FOOTER="$footer" AWK_STYLE_PATH="${style_path}${asset_version}" AWK_HEADER_BRAND="$header_brand" AWK_HEAD_EXTRA="$head_extra" awk -f "$awk_dir/render_template.awk" "$local_template"
ENABLE_HEADER_LINKS="$enable_header_links" CUSTOM_ADMONITIONS="$custom_admonitions" MARKDOWN_SITE_ROOT="$src" MARKDOWN_FALLBACK_FILE="$script_dir/styles/$style.css" sh "$script_dir/markdown.sh" "$content_file" | AWK_CURRENT_URL="$current_url" AWK_TITLE="$page_title" AWK_NAV="$nav" AWK_FOOTER="$footer" AWK_STYLE_PATH="${style_path}${asset_version}" AWK_HEADER_BRAND="$header_brand" AWK_HEAD_EXTRA="$head_extra" awk -f "$awk_dir/render_template.awk" "$local_template"
}
needs_rebuild() {
src_file="$1"
out_file="$2"
[ ! -f "$out_file" ] && return 0
[ "$src_file" -nt "$out_file" ] && return 0
[ -f "./site.conf" ] && [ "./site.conf" -nt "$out_file" ] && return 0
[ -f "$src/site.conf" ] && [ "$src/site.conf" -nt "$out_file" ] && return 0
[ -f "$template" ] && [ "$template" -nt "$out_file" ] && return 0
[ -f "$script_dir/styles/$style.css" ] && [ "$script_dir/styles/$style.css" -nt "$out_file" ] && return 0
return 1
}
echo "Building site from '$src' to '$out'..."
@@ -728,9 +762,13 @@ eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type d -print" | sort | while
mkdir -p "$out_dir"
if [ -f "$dir/styles.css" ]; then
copy_style_with_resolved_vars "$dir/styles.css" "$out_dir/styles.css"
if needs_rebuild "$dir/styles.css" "$out_dir/styles.css"; then
copy_style_with_resolved_vars "$dir/styles.css" "$out_dir/styles.css"
fi
elif [ -f "$dir/style.css" ]; then
copy_style_with_resolved_vars "$dir/style.css" "$out_dir/styles.css"
if needs_rebuild "$dir/style.css" "$out_dir/styles.css"; then
copy_style_with_resolved_vars "$dir/style.css" "$out_dir/styles.css"
fi
fi
[ "$dir_indexes" != "true" ] && continue
@@ -747,7 +785,9 @@ eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type d -print" | sort | while
is_home="false"; [ "$dir" = "$src" ] && is_home="true"
target_url="/$rel_dir/index.html"
[ "$rel_dir" = "." ] && target_url="/index.html"
render_markdown "$md_file" "$is_home" "$target_url" > "$out_dir/index.html"
if needs_rebuild "$md_file" "$out_dir/index.html"; then
render_markdown "$md_file" "$is_home" "$target_url" > "$out_dir/index.html"
fi
continue
fi
fi
@@ -807,12 +847,14 @@ eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type d -print" | sort | while
is_home="false"; [ "$dir" = "$src" ] && is_home="true"
target_url="/$rel_dir/index.html"
[ "$rel_dir" = "." ] && target_url="/index.html"
render_markdown "$temp_index" "$is_home" "$target_url" > "$out_dir/index.html"
if needs_rebuild "$dir" "$out_dir/index.html"; then
render_markdown "$temp_index" "$is_home" "$target_url" > "$out_dir/index.html"
fi
rm "$temp_index"
fi
done
if [ ! -f "$out/styles.css" ] && [ -f "$script_dir/styles/$style.css" ]; then
if [ -f "$script_dir/styles/$style.css" ] && needs_rebuild "$script_dir/styles/$style.css" "$out/styles.css"; then
copy_style_with_resolved_vars "$script_dir/styles/$style.css" "$out/styles.css"
fi
@@ -844,9 +886,13 @@ eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type f -print" | sort | while
if [ "${file%.md}" != "$file" ] && [ "$is_preserved" -eq 0 ]; then
is_home="false"; [ "$file" = "$src/index.md" ] && is_home="true"
out_file="$out/${rel_path%.md}.html"
render_markdown "$file" "$is_home" > "$out_file"
if needs_rebuild "$file" "$out_file"; then
render_markdown "$file" "$is_home" > "$out_file"
fi
else
cp "$file" "$out/$rel_path"
if needs_rebuild "$file" "$out/$rel_path"; then
cp "$file" "$out/$rel_path"
fi
fi
done

View File

@@ -15,7 +15,7 @@ sed_inplace() {
fi
}
temp_file="/tmp/markdown.$$.md"
temp_file="${KEWT_TMPDIR:-/tmp}/markdown.$$.md"
cat "$@" > "$temp_file"
trap 'rm -f "$temp_file" "$temp_file.tmp"' EXIT INT TERM
@@ -40,14 +40,19 @@ done
sed_inplace "/^\[[^\]]*\]: */d" "$temp_file"
# Blocks
sed_inplace "s/^>!\[/> [!/g" "$temp_file"
sed_inplace "s/^>\[!/> [!/g" "$temp_file"
loop_count=0
max_iterations=100
while grep '^>' "$temp_file" >/dev/null; do
awk -f "$awk_dir/blockquote.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
loop_count=$((loop_count + 1))
if [ "$loop_count" -gt "$max_iterations" ]; then
echo "Warning: Blockquote processing exceeded $max_iterations iterations on $1. Breaking to prevent infinite loop." >&2
break
fi
done
awk -f "$awk_dir/blockquote_to_admonition.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
awk -v custom_admonitions="$CUSTOM_ADMONITIONS" -f "$awk_dir/blockquote_to_admonition.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
awk -f "$awk_dir/fenced_code.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
awk -f "$awk_dir/indented_code.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
awk -f "$awk_dir/pipe_tables.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"

View File

@@ -1,7 +1,7 @@
pkgbase = kewt-git
pkgdesc = A minimalist, 100% POSIX, static site generator inspired by werc and kew
pkgver = r0.0000000
pkgrel = 2
pkgrel = 3
url = https://kewt.krzak.org
arch = any
license = ISC

View File

@@ -1,7 +1,7 @@
pkgbase = kewt-bin
pkgdesc = A minimalist, 100% POSIX, static site generator inspired by werc and kew
pkgver = VERSION_PLACEHOLDER
pkgrel = 1
pkgrel = 2
url = https://kewt.krzak.org
arch = any
license = ISC

View File

@@ -10,7 +10,7 @@ makedepends=('git')
depends=('sh')
provides=('kewt')
conflicts=('kewt' 'kewt-bin')
source=("${pkgname}::git+${url}.git")
source=("${pkgname}::git+https://git.krzak.org/N0VA/kewt.git")
sha256sums=('SKIP')
pkgver() {

View File

@@ -9,7 +9,7 @@ license=('ISC')
depends=('sh')
provides=('kewt')
conflicts=('kewt' 'kewt-git')
source=("${pkgname}-${pkgver}.sh::${url}/releases/download/v${pkgver}/kewt")
source=("${pkgname}-${pkgver}.sh::https://git.krzak.org/N0VA/kewt/releases/download/v${pkgver}/kewt")
sha256sums=('SHA256SUM_PLACEHOLDER')
build() {

View File

@@ -19,7 +19,8 @@ It's meant to be a static site generator, like _[kew](https://github.com/uint23/
- Automatic inlining and embedding of many filetypes with `\![link]` or `\![alt](link)`
- Inline html support
- MFM `$font` and `\<plain>` tags
- Admonition support (that's what the blocks like the warning block below are called)
- GFM Admonition support (that's what the blocks like the warning block below are called)
- Task list support (`- [ ]`, `- [x]`)
- RSS/Feed generation and Sitemap support
- Post creation via `--post`
- Automatic 404 page generation
@@ -86,6 +87,7 @@ generate_feed = false
feed_file = "rss.xml"
posts_dir = ""
enable_header_links = true
custom_admonitions = ""
```
- `title` site title
@@ -112,6 +114,7 @@ enable_header_links = true
- `feed_file` filename for the generated RSS feed (default: "rss.xml")
- `posts_dir` directory name containing posts (e.g., "posts"). Enables reverse-chronological sorting, title headings in indexes, and automatic backlinks.
- `enable_header_links` turns markdown section headings into clickable anchor links (default: true)
- `custom_admonitions` comma separated list of custom admonitions
## Ignores

View File

@@ -19,3 +19,4 @@ error_page = "not_found.html"
versioning = true
enable_header_links = true
base_url = "https://kewt.krzak.org"
custom_admonitions = ""

View File

@@ -273,4 +273,13 @@ hr {
margin-right: 20px;
padding-top: 30px;
}
}
.task-list-item {
list-style-type: none;
}
.task-list-item-checkbox {
margin: 0 0.2em 0.25em -1.6em;
vertical-align: middle;
}