19 Commits

Author SHA1 Message Date
f89661c1a5 standalone version tmp spam fix
All checks were successful
Lint / shellcheck (push) Successful in 19s
Release Standalone Builder / build (release) Successful in 31s
Release Standalone Builder / publish-aur (release) Successful in 33s
2026-03-11 13:57:10 +01:00
e0a3b66fa9 docs: AUR
All checks were successful
Lint / shellcheck (push) Successful in 16s
2026-03-11 08:44:37 +01:00
4e6c9dbeb5 kewt-git AUR 2026-03-11 08:43:19 +01:00
af453ca2ec aur now workie pretty please
All checks were successful
Lint / shellcheck (push) Successful in 16s
Release Standalone Builder / build (release) Successful in 33s
Release Standalone Builder / publish-aur (release) Successful in 1m7s
2026-03-11 08:36:22 +01:00
cea84de242 Hopefully AUR fixed
Some checks failed
Lint / shellcheck (push) Successful in 17s
Release Standalone Builder / build (release) Successful in 31s
Release Standalone Builder / publish-aur (release) Failing after 23s
2026-03-11 08:31:00 +01:00
ad1ec9c2e3 Auto GitHub
Some checks failed
Lint / shellcheck (push) Successful in 18s
Release Standalone Builder / build (release) Successful in 31s
Release Standalone Builder / publish-aur (release) Failing after 43s
2026-03-11 08:26:36 +01:00
fa3b5592da AUR
All checks were successful
Lint / shellcheck (push) Successful in 17s
2026-03-11 08:21:12 +01:00
722d687afe docs: add standalone download instructions
All checks were successful
Lint / shellcheck (push) Successful in 15s
2026-03-11 08:07:17 +01:00
77c0b29b4c fix gitea actions missing go
All checks were successful
Lint / shellcheck (push) Successful in 16s
Release Standalone Builder / build (release) Successful in 1m8s
2026-03-11 08:01:26 +01:00
9ae965662c Actions! Work!
Some checks failed
Lint / shellcheck (push) Successful in 48s
Release Standalone Builder / build (release) Failing after 6s
2026-03-11 07:51:31 +01:00
9ced2af562 Actions
Some checks failed
Lint / shellcheck (push) Has been cancelled
2026-03-11 07:42:45 +01:00
f407b1c4af extra navbar links 2026-03-09 20:05:42 +01:00
bee12ce8c1 Update markdown_inline.awk 2026-03-09 10:40:30 +01:00
696dc1c142 Fix building from ./ 2026-03-08 19:28:06 +01:00
c2416e731b Turn off antialiasing in the default style 2026-03-08 17:54:10 +01:00
01fd55001a html tag fix 2026-03-08 17:44:28 +01:00
b1f69673d1 Recursive ignore files 2026-03-08 14:38:15 +01:00
6418b64672 Better logo style 2026-03-08 14:26:05 +01:00
e3cc1c1688 Some fixes, .kewtignore, .kewtpreserve and .kewthide 2026-03-08 14:18:45 +01:00
17 changed files with 592 additions and 68 deletions

16
.gitea/workflows/lint.yml Normal file
View File

@@ -0,0 +1,16 @@
name: Lint
on:
push:
branches: [main, master]
pull_request:
jobs:
shellcheck:
runs-on: local
steps:
- uses: actions/checkout@v4
- name: Install Shellcheck
run: sudo apt-get update && sudo apt-get install -y shellcheck
- name: Run Shellcheck
run: shellcheck -s sh kewt.sh markdown.sh tools/build-standalone.sh || true

View File

@@ -0,0 +1,109 @@
name: Release Standalone Builder
on:
release:
types: [published]
jobs:
build:
runs-on: local
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build standalone executable
run: |
chmod +x tools/build-standalone.sh
./tools/build-standalone.sh
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
- name: Upload Release Asset
uses: https://gitea.com/actions/release-action@main
with:
files: |-
kewt
api_key: '${{secrets.GITEA_TOKEN}}'
- name: Push to GitHub Release
run: |
TAG="${GITHUB_REF#refs/tags/}"
# Create the release on GitHub
curl -sL -X POST \
-H "Authorization: token ${{ secrets.GH_RELEASE_TOKEN }}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/n0va-bot/kewt/releases" \
-d "{\"tag_name\":\"${TAG}\",\"name\":\"${TAG}\",\"draft\":false,\"prerelease\":false}" || true
# Get the release ID
RELEASE_ID=$(curl -sL \
-H "Authorization: token ${{ secrets.GH_RELEASE_TOKEN }}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/n0va-bot/kewt/releases/tags/${TAG}" | jq -r '.id')
# Upload the asset
curl -sL -X POST \
-H "Authorization: token ${{ secrets.GH_RELEASE_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
"https://uploads.github.com/repos/n0va-bot/kewt/releases/${RELEASE_ID}/assets?name=kewt" \
--data-binary @kewt
publish-aur:
runs-on: local
needs: build
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Arch Linux environment
run: |
sudo apt-get update
sudo apt-get install -y pacman-package-manager curl jq || true
- name: Render PKGBUILD and SRCINFO
run: |
VERSION=${GITHUB_REF#refs/tags/v}
VERSION=${VERSION#refs/tags/}
curl -sL -o kewt https://git.krzak.org/N0VA/kewt/releases/download/v${VERSION}/kewt
CHECKSUM=$(sha256sum kewt | awk '{print $1}')
mkdir -p aur-work
sed -e "s/VERSION_PLACEHOLDER/${VERSION}/g" \
-e "s/SHA256SUM_PLACEHOLDER/${CHECKSUM}/g" \
packaging/AUR/PKGBUILD.template > aur-work/PKGBUILD
cat > aur-work/.SRCINFO << SRCEOF
pkgbase = kewt-bin
pkgdesc = A minimalist, 100% POSIX, static site generator inspired by werc and kew
pkgver = ${VERSION}
pkgrel = 1
url = https://git.krzak.org/N0VA/kewt
arch = any
license = MIT
depends = sh
provides = kewt
conflicts = kewt
conflicts = kewt-git
source = kewt-bin-${VERSION}.sh::https://git.krzak.org/N0VA/kewt/releases/download/v${VERSION}/kewt
sha256sums = ${CHECKSUM}
pkgname = kewt-bin
SRCEOF
# Remove leading whitespace from heredoc
sed -i 's/^ //' aur-work/.SRCINFO
- name: Publish to AUR
uses: KSXGitHub/github-actions-deploy-aur@v3.0.1
with:
pkgname: kewt-bin
pkgbuild: ./aur-work/PKGBUILD
commit_username: ${{ github.actor }}
commit_email: ${{ github.actor }}@users.noreply.github.com
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
commit_message: "Update kewt-bin to ${{ github.ref_name }}"

1
.gitignore vendored
View File

@@ -1 +1,2 @@
out/
kewt

View File

@@ -19,6 +19,20 @@ It's meant to be a static site generator, like _[kew](https://github.com/uint23/
If you want to **force** a file to be inlined, use `\!![]` instead of `\![]`
## Installation
You can clone the repository to use `kewt.sh` directly, or you can download the standalone executable, which bundles all dependencies into a single file:
```sh
curl -L -o kewt https://git.krzak.org/N0VA/kewt/releases/latest/download/kewt
chmod +x kewt
```
On Arch Linux, _kewt_ is available on the AUR:
- [kewt-bin](https://aur.archlinux.org/packages/kewt-bin) — prebuilt standalone binary from the latest release
- [kewt-git](https://aur.archlinux.org/packages/kewt-git) — built from the latest git source
## Usage
```sh
@@ -39,6 +53,10 @@ dir_indexes = true
single_file_index = true
flatten = false
order = ""
home_name = "Home"
show_home_in_nav = true
nav_links = ""
nav_extra = ""
footer = "made with <a href="https://kewt.krzak.org">kewt</a>"
logo = ""
display_logo = false
@@ -53,6 +71,10 @@ favicon = ""
- `single_file_index` if a directory has one markdown file and no `index.md`, use that file as `index.html`
- `flatten` flatten sidebar directory levels
- `order` comma separated file/directory name list to order the sidebar (alphabetical by default)
- `home_name` text for the home link in navigation (default: "Home")
- `show_home_in_nav` show home link in navigation (default: true)
- `nav_links` comma separated extra nav links, as bare URLs or Markdown links like `[Label](https://example.com)`
- `nav_extra` raw HTML appended inside the `<nav>` after the generated link list
- `footer` footer html/text shown at the bottom of pages
- `logo` logo image path (used in header if enabled)
- `display_logo` show logo in header
@@ -60,6 +82,12 @@ favicon = ""
- `logo_as_favicon` use `logo` as favicon
- `favicon` explicit favicon path (used when `logo_as_favicon` is false or no logo is set)
## Ignores
- `.kewtignore`: Files/directories to ignore. If empty, the whole directory gets ignored
- `.kewthide`: Files/directories to hide from navigation but still process. Same empty rules as with ignore
- `.kewtpreserve`: Files/directories to copy but not convert markdown to html. Same empty rules again
## Embeds
- `\![link]`:

View File

@@ -80,8 +80,9 @@ END {
}
print "<ul>"
if ("index.md" in all_paths) {
print "<li><a href=\"/index.html\">Home</a></li>"
if (show_home_in_nav == "true" && "index.md" in all_paths) {
if (home_name == "") home_name = "Home"
print "<li><a href=\"/index.html\">" home_name "</a></li>"
}
depth = 0

View File

@@ -3,10 +3,27 @@ function strip_markdown(s) {
gsub(/[*_`~]/, "", s)
gsub(/[\[\]]/, "", s)
gsub(/\([^\)]*\)/, "", s)
sub(/^[[:space:]]*/, "", s)
sub(/[[:space:]]*$/, "", s)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", s)
gsub(/[[:space:]]+/, "-", s)
return s
}
function print_header(line) {
if (line ~ /^# /) {
sub(/^# /, "", line); print "<h1 id=\"" strip_markdown(line) "\">" line "</h1>"
} else if (line ~ /^## /) {
sub(/^## /, "", line); print "<h2 id=\"" strip_markdown(line) "\">" line "</h2>"
} else if (line ~ /^### /) {
sub(/^### /, "", line); print "<h3 id=\"" strip_markdown(line) "\">" line "</h3>"
} else if (line ~ /^#### /) {
sub(/^#### /, "", line); print "<h4 id=\"" strip_markdown(line) "\">" line "</h4>"
} else if (line ~ /^##### /) {
sub(/^##### /, "", line); print "<h5 id=\"" strip_markdown(line) "\">" line "</h5>"
} else if (line ~ /^###### /) {
sub(/^###### /, "", line); print "<h6 id=\"" strip_markdown(line) "\">" line "</h6>"
} else {
print line
}
}
BEGIN {
has_prev = 0
in_pre = 0
@@ -14,7 +31,7 @@ BEGIN {
{
if ($0 ~ /^<pre><code>/) {
in_pre = 1
if (has_prev && prev != "") { print prev; has_prev = 0 }
if (has_prev && prev != "") { print_header(prev); has_prev = 0 }
print
next
}
@@ -24,30 +41,32 @@ BEGIN {
next
}
if ($0 ~ /^=+$/ && has_prev && prev != "" && prev !~ /^<[a-z]/) {
print "<h1 id=\"" strip_markdown(prev) "\">" prev "</h1>"
has_prev = 0
} else if ($0 ~ /^-+$/ && has_prev && prev != "" && prev !~ /^<[a-z]/) {
print "<h2 id=\"" strip_markdown(prev) "\">" prev "</h2>"
if ($0 ~ /^=+$/) {
if (has_prev && prev != "" && prev !~ /^<[a-z]/) {
print "<h1 id=\"" strip_markdown(prev) "\">" prev "</h1>"
has_prev = 0
} else {
if (has_prev) print_header(prev)
print $0
has_prev = 0
}
} else if ($0 ~ /^-+$/) {
if (has_prev && prev != "" && prev !~ /^<[a-z]/) {
print "<h2 id=\"" strip_markdown(prev) "\">" prev "</h2>"
has_prev = 0
} else {
if (has_prev) print_header(prev)
if (length($0) >= 3) print "<hr />"
else print $0
has_prev = 0
}
} else if ($0 ~ /^[*_]+$/ && length($0) >= 3) {
if (has_prev) print_header(prev)
print "<hr />"
has_prev = 0
} else {
if (has_prev) {
line = prev
if (line ~ /^# /) {
sub(/^# /, "", line); print "<h1 id=\"" strip_markdown(line) "\">" line "</h1>"
} else if (line ~ /^## /) {
sub(/^## /, "", line); print "<h2 id=\"" strip_markdown(line) "\">" line "</h2>"
} else if (line ~ /^### /) {
sub(/^### /, "", line); print "<h3 id=\"" strip_markdown(line) "\">" line "</h3>"
} else if (line ~ /^#### /) {
sub(/^#### /, "", line); print "<h4 id=\"" strip_markdown(line) "\">" line "</h4>"
} else if (line ~ /^##### /) {
sub(/^##### /, "", line); print "<h5 id=\"" strip_markdown(line) "\">" line "</h5>"
} else if (line ~ /^###### /) {
sub(/^###### /, "", line); print "<h6 id=\"" strip_markdown(line) "\">" line "</h6>"
} else {
print prev
}
print_header(prev)
}
prev = $0
has_prev = 1
@@ -55,11 +74,6 @@ BEGIN {
}
END {
if (has_prev) {
line = prev
if (line ~ /^# /) {
sub(/^# /, "", line); print "<h1 id=\"" strip_markdown(line) "\">" line "</h1>"
} else {
print prev
}
print_header(prev)
}
}

View File

@@ -2,11 +2,36 @@ BEGIN {
in_pre = 0
}
function mask_html_tags(s, out, rest, start, len, tag, token) {
out = ""
rest = s
html_tag_count = 0
while (match(rest, /<[^>]+>/)) {
out = out substr(rest, 1, RSTART - 1)
start = RSTART
len = RLENGTH
tag = substr(rest, start, len)
html_tag_count++
html_tag_token[html_tag_count] = "\034HT" html_tag_count "\034"
html_tag_value[html_tag_count] = tag
out = out html_tag_token[html_tag_count]
rest = substr(rest, start + len)
}
return out rest
}
function restore_html_tags(s, i) {
for (i = 1; i <= html_tag_count; i++) {
gsub(html_tag_token[i], html_tag_value[i], s)
}
return s
}
{
if ($0 ~ /<pre>/) {
in_pre = 1
}
if (in_pre) {
print
if ($0 ~ /<\/pre>/) {
@@ -114,6 +139,8 @@ BEGIN {
line = substr(line, 1, start - 1) "<span style=\"font-family: sans-serif;\">" content "</span>" substr(line, start + len)
}
line = mask_html_tags(line)
# Bold, Italic, Strikethrough (BRE-like logic in AWK)
# Strong Bold **
while (match(line, /\*\*[^*]+\*\*/)) {
@@ -152,11 +179,13 @@ BEGIN {
line = substr(line, 1, start - 1) repl substr(line, start + len)
}
line = restore_html_tags(line)
# special characters
if (line !~ /&[A-Za-z0-9#]+;/) {
gsub(/&/, "&amp;", line)
}
p = 1
while (match(substr(line, p), /</)) {
start = p + RSTART - 1

View File

@@ -13,7 +13,7 @@ BEGIN {
next
}
if ($0 ~ /^<\/?(div|table|p|[ou]l|h[1-6]|[bh]r|blockquote|li)/) {
if ($0 ~ /^<\/?(div|table|p|[ou]l|h[1-6]|[bh]r|blockquote|li|hr)/) {
if (in_p) {
print "</p>"
in_p = 0

234
kewt.sh
View File

@@ -6,11 +6,12 @@ die() {
}
usage() {
invoked_as="${KEWT_INVOKED_AS:-$0}"
cat <<EOF
Usage: $0 [--from <src>] [--to <out>]
$0 [src] [out]
$0 --new [title]
$0 --help
Usage: $invoked_as [--from <src>] [--to <out>]
$invoked_as [src] [out]
$invoked_as --new [title]
$invoked_as --help
Options:
--help Show this help message.
@@ -20,9 +21,12 @@ Options:
EOF
}
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
script_dir=$(CDPATH="" cd -- "$(dirname -- "$0")" && pwd)
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'
@@ -32,6 +36,10 @@ dir_indexes = true
single_file_index = true
flatten = false
order = ""
home_name = "Home"
show_home_in_nav = true
nav_links = ""
nav_extra = ""
footer = "made with <a href="https://kewt.krzak.org">kewt</a>"
logo = ""
display_logo = false
@@ -89,10 +97,7 @@ create_new_site() {
exit 0
}
generate_nav() {
dinfo=$(find "$1" \( -name ".*" ! -name "." ! -name ".." -prune \) -o -print | sort | awk -v src="$1" -f "$awk_dir/collect_dir_info.awk")
find "$1" \( -name ".*" ! -name "." ! -name ".." -prune \) -o -name "*.md" -print | sort | awk -v src="$1" -v single_file_index="$single_file_index" -v flatten="$flatten" -v order="$order" -v dinfo="$dinfo" -f "$awk_dir/generate_sidebar.awk"
}
src=""
out=""
@@ -129,9 +134,9 @@ while [ $# -gt 0 ]; do
*)
positional_count=$((positional_count + 1))
if [ "$positional_count" -eq 1 ]; then
[ -z "$src" ] && src="$1" || die "Source already set (use either positional or --from)."
if [ -z "$src" ]; then src="$1"; else die "Source already set (use either positional or --from)."; fi
elif [ "$positional_count" -eq 2 ]; then
[ -z "$out" ] && out="$1" || die "Output already set (use either positional or --to)."
if [ -z "$out" ]; then out="$1"; else die "Output already set (use either positional or --to)."; fi
else
die "Too many positional arguments."
fi
@@ -147,8 +152,106 @@ ensure_root_defaults
[ -z "$src" ] && src="site"
[ -z "$out" ] && out="out"
src="${src%/}"
out="${out%/}"
[ -d "$src" ] || die "Source directory '$src' does not exist."
IGNORE_ARGS="-name '.kewtignore' -o -path '$src/.*'"
if [ -f "$src/.kewtignore" ]; then
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
''|'#'*) continue ;;
esac
pattern=$(echo "$line" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
[ -z "$pattern" ] && continue
pattern_clean="${pattern#/}"
pattern_clean="${pattern_clean%/}"
if echo "$pattern" | grep -q "/"; then
IGNORE_ARGS="$IGNORE_ARGS -o -path '$src/$pattern_clean' -o -path '$src/$pattern_clean/*'"
else
IGNORE_ARGS="$IGNORE_ARGS -o -name '$pattern_clean'"
fi
done < "$src/.kewtignore"
fi
find "$src" -name .kewtignore > "$KEWT_TMPDIR/kewt_ignore"
while read -r ki; do
d="${ki%/.kewtignore}"
if [ "$d" != "$src" ] && [ "$d" != "." ]; then
IGNORE_ARGS="$IGNORE_ARGS -o -path '$d' -o -path '$d/*'"
fi
done < "$KEWT_TMPDIR/kewt_ignore"
rm -f "$KEWT_TMPDIR/kewt_ignore"
HIDE_ARGS="-name '.kewtignore' -o -name '.kewthide' -o -name '.kewtpreserve' -o -path '$src/.*'"
if [ -f "$src/.kewthide" ]; then
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
''|'#'*) continue ;;
esac
pattern=$(echo "$line" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
[ -z "$pattern" ] && continue
pattern_clean="${pattern#/}"
pattern_clean="${pattern_clean%/}"
if echo "$pattern" | grep -q "/"; then
HIDE_ARGS="$HIDE_ARGS -o -path '$src/$pattern_clean' -o -path '$src/$pattern_clean/*'"
else
HIDE_ARGS="$HIDE_ARGS -o -name '$pattern_clean'"
fi
done < "$src/.kewthide"
fi
find "$src" -name .kewthide > "$KEWT_TMPDIR/kewt_hide"
while read -r kh; do
d="${kh%/.kewthide}"
if [ "$d" != "$src" ] && [ "$d" != "." ]; then
HIDE_ARGS="$HIDE_ARGS -o -path '$d' -o -path '$d/*'"
fi
done < "$KEWT_TMPDIR/kewt_hide"
rm -f "$KEWT_TMPDIR/kewt_hide"
PRESERVE_ARGS="-false"
if [ -f "$src/.kewtpreserve" ]; then
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
''|'#'*) continue ;;
esac
pattern=$(echo "$line" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
[ -z "$pattern" ] && continue
pattern_clean="${pattern#/}"
pattern_clean="${pattern_clean%/}"
if echo "$pattern" | grep -q "/"; then
PRESERVE_ARGS="$PRESERVE_ARGS -o -path '$src/$pattern_clean' -o -path '$src/$pattern_clean/*'"
else
PRESERVE_ARGS="$PRESERVE_ARGS -o -name '$pattern_clean'"
fi
done < "$src/.kewtpreserve"
fi
find "$src" -name .kewtpreserve > "$KEWT_TMPDIR/kewt_preserve"
while read -r kp; do
d="${kp%/.kewtpreserve}"
if [ "$d" != "$src" ] && [ "$d" != "." ]; then
PRESERVE_ARGS="$PRESERVE_ARGS -o -path '$d' -o -path '$d/*'"
fi
done < "$KEWT_TMPDIR/kewt_preserve"
rm -f "$KEWT_TMPDIR/kewt_preserve"
generate_nav() {
dinfo=$(eval "find \"$1\" \( $IGNORE_ARGS -o $HIDE_ARGS -o $PRESERVE_ARGS \) -prune -o -print" | sort | awk -v src="$1" -f "$awk_dir/collect_dir_info.awk")
eval "find \"$1\" \( $IGNORE_ARGS -o $HIDE_ARGS -o $PRESERVE_ARGS \) -prune -o -name \"*.md\" -print" | sort | awk -v src="$1" -v single_file_index="$single_file_index" -v flatten="$flatten" -v order="$order" -v home_name="$home_name" -v show_home_in_nav="$show_home_in_nav" -v dinfo="$dinfo" -f "$awk_dir/generate_sidebar.awk"
}
title="kewt"
style="kewt"
footer="made with <a href=\"https://kewt.krzak.org\">kewt</a>"
@@ -156,6 +259,11 @@ dir_indexes="true"
single_file_index="true"
flatten="false"
order=""
home_name="Home"
show_home_in_nav="true"
nav_links=""
nav_extra=""
footer="made with <a href=\"https://kewt.krzak.org\">kewt</a>"
logo=""
display_logo="false"
display_title="true"
@@ -188,6 +296,10 @@ load_config() {
single_file_index) single_file_index="$val" ;;
flatten) flatten="$val" ;;
order) order="$val" ;;
home_name) home_name="$val" ;;
show_home_in_nav) show_home_in_nav="$val" ;;
nav_links) nav_links="$val" ;;
nav_extra) nav_extra="$val" ;;
footer) footer="$val" ;;
logo) logo="$val" ;;
display_logo) display_logo="$val" ;;
@@ -201,6 +313,65 @@ load_config() {
load_config "./site.conf"
load_config "$src/site.conf"
escape_html_text() {
printf '%s' "$1" | sed \
-e 's/&/\&amp;/g' \
-e 's/</\&lt;/g' \
-e 's/>/\&gt;/g'
}
escape_html_attr() {
printf '%s' "$1" | sed \
-e 's/&/\&amp;/g' \
-e 's/"/\&quot;/g' \
-e 's/</\&lt;/g' \
-e 's/>/\&gt;/g'
}
nav_links_html() {
[ -n "$nav_links" ] || return
old_ifs=$IFS
set -f
IFS=','
# shellcheck disable=SC2086
set -- $nav_links
IFS=$old_ifs
set +f
[ $# -gt 0 ] || return
printf '<ul class="nav-extra-links">\n'
for raw_link in "$@"; do
link=$(printf '%s' "$raw_link" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
[ -n "$link" ] || continue
case "$link" in
\[*\]\(*\))
label=${link#\[}
label=${label%%\]*}
link_url=${link#*](}
link_url=${link_url%)}
;;
*)
link_url=$link
label=$(printf '%s' "$link" | sed \
-e 's|^[A-Za-z][A-Za-z0-9+.-]*://||' \
-e 's|/$||')
[ -n "$label" ] || label="$link"
;;
esac
[ -n "$link_url" ] || continue
[ -n "$label" ] || label="$link_url"
link_attr=$(escape_html_attr "$link_url")
label_text=$(escape_html_text "$label")
printf '<li><a href="%s">%s</a></li>\n' "$link_attr" "$label_text"
done
printf '</ul>'
}
template="$src/template.html"
[ -f "$template" ] || template="./template.html"
[ -f "$template" ] || die "Template '$template' not found."
@@ -209,6 +380,15 @@ template="$src/template.html"
mkdir -p "$out"
nav=$(generate_nav "$src")
extra_links=$(nav_links_html)
if [ -n "$extra_links" ]; then
nav="$nav
$extra_links"
fi
if [ -n "$nav_extra" ]; then
nav="$nav
$nav_extra"
fi
find_closest() {
target="$1"
@@ -240,7 +420,7 @@ render_markdown() {
closest_style_src=$(find_closest "styles.css" "$(dirname "$file")")
[ -z "$closest_style_src" ] && closest_style_src=$(find_closest "style.css" "$(dirname "$file")")
if [ -n "$closest_style_src" ]; then
style_rel_to_src="${closest_style_src#$src/}"
style_rel_to_src="${closest_style_src#"$src"/}"
case "$closest_style_src" in
"$src/styles.css") style_rel_to_src="styles.css" ;;
"$src/style.css") style_rel_to_src="style.css" ;;
@@ -282,14 +462,15 @@ render_markdown() {
head_extra="<link rel=\"icon\" href=\"$favicon_src\" />"
fi
MARKDOWN_SITE_ROOT="$src" MARKDOWN_FALLBACK_FILE="styles/$style.css" sh "$script_dir/markdown.sh" "$file" | awk -v title="$title" -v nav="$nav" -v footer="$footer" -v style_path="$style_path" -v header_brand="$header_brand" -v head_extra="$head_extra" -f "$awk_dir/render_template.awk" "$local_template"
MARKDOWN_SITE_ROOT="$src" MARKDOWN_FALLBACK_FILE="$script_dir/styles/$style.css" sh "$script_dir/markdown.sh" "$file" | awk -v title="$title" -v nav="$nav" -v footer="$footer" -v style_path="$style_path" -v header_brand="$header_brand" -v head_extra="$head_extra" -f "$awk_dir/render_template.awk" "$local_template"
}
echo "Building site from '$src' to '$out'..."
find "$src" \( -name ".*" ! -name "." ! -name ".." -prune \) -o -type d -print | sort | while read -r dir; do
rel_dir="${dir#$src/}"
[ "$dir" = "$src" ] && rel_dir="."
eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type d -print" | sort | while read -r dir; do
rel_dir="${dir#"$src"}"
rel_dir="${rel_dir#/}"
[ -z "$rel_dir" ] && rel_dir="."
out_dir="$out/$rel_dir"
mkdir -p "$out_dir"
@@ -311,7 +492,7 @@ find "$src" \( -name ".*" ! -name "." ! -name ".." -prune \) -o -type d -print |
fi
fi
temp_index="/tmp/kewt_index_$$.md"
temp_index="$KEWT_TMPDIR/index.md"
display_dir="${rel_dir#.}"
[ -z "$display_dir" ] && display_dir="/"
echo "# Index of $display_dir" > "$temp_index"
@@ -334,12 +515,13 @@ find "$src" \( -name ".*" ! -name "." ! -name ".." -prune \) -o -type d -print |
fi
done
if [ ! -f "$out/styles.css" ] && [ -f "styles/$style.css" ]; then
copy_style_with_resolved_vars "styles/$style.css" "$out/styles.css"
if [ ! -f "$out/styles.css" ] && [ -f "$script_dir/styles/$style.css" ]; then
copy_style_with_resolved_vars "$script_dir/styles/$style.css" "$out/styles.css"
fi
find "$src" \( -name ".*" ! -name "." ! -name ".." -prune \) -o -type f -print | sort | while IFS= read -r file; do
rel_path="${file#$src/}"
eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type f -print" | sort | while IFS= read -r file; do
rel_path="${file#"$src"}"
rel_path="${rel_path#/}"
dir_rel=$(dirname "$rel_path")
out_dir="$out/$dir_rel"
@@ -347,12 +529,17 @@ find "$src" \( -name ".*" ! -name "." ! -name ".." -prune \) -o -type f -print |
template.html|site.conf|style.css|styles.css) continue ;;
esac
if [ "$single_file_index" = "true" ] && [ "${file%.md}" != "$file" ] && [ ! -f "$(dirname "$file")/index.md" ]; then
is_preserved=0
if [ -n "$(eval "find \"$file\" \( $PRESERVE_ARGS \) -print")" ]; then
is_preserved=1
fi
if [ "$single_file_index" = "true" ] && [ "${file%.md}" != "$file" ] && [ "$is_preserved" -eq 0 ] && [ ! -f "$(dirname "$file")/index.md" ]; then
md_count=$(find "$(dirname "$file")" ! -name "$(basename "$(dirname "$file")")" -prune -name "*.md" | wc -l)
[ "$md_count" -eq 1 ] && continue
fi
if [ "${file%.md}" != "$file" ]; then
if [ "${file%.md}" != "$file" ] && [ "$is_preserved" -eq 0 ]; then
out_file="$out/${rel_path%.md}.html"
render_markdown "$file" > "$out_file"
else
@@ -360,4 +547,5 @@ find "$src" \( -name ".*" ! -name "." ! -name ".." -prune \) -o -type f -print |
fi
done
echo "Build complete."

View File

@@ -1,27 +1,31 @@
#!/bin/sh
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
script_dir=$(CDPATH="" cd -- "$(dirname -- "$0")" && pwd)
awk_dir="$script_dir/awk"
sed_inplace() {
script="$1"
file="$2"
tmp="${file}.tmp.$$"
sed "$script" "$file" > "$tmp" && mv "$tmp" "$file" || {
if sed "$script" "$file" > "$tmp" && mv "$tmp" "$file"; then
return 0
else
rm -f "$tmp"
return 1
}
fi
}
temp_file="/tmp/markdown.$$"
temp_file="/tmp/markdown.$$.md"
cat "$@" > "$temp_file"
trap 'rm -f "$temp_file" "$temp_file.tmp"' EXIT INT TERM
# Mask
awk -f "$awk_dir/mask_inline_code.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
awk -f "$awk_dir/mask_plain.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
# Reference links
refs=$(cat "$@" | awk '/^\[[^\]]+\]: +/')
refs=$(cat "$@" | awk '/^\[[^\]]+\]: */')
IFS='
'
for ref in $refs; do
@@ -33,7 +37,7 @@ for ref in $refs; do
sed_inplace "s|!\[$ref_id\]\[\]|<img src=\"$ref_url\" title=\"$ref_title\" alt=\"$ref_id\" />|g" "$temp_file"
sed_inplace "s|\[$ref_id\]\[\]|<a href=\"$ref_url\" title=\"$ref_title\">$ref_id</a>|g" "$temp_file"
done
sed_inplace "/^\[[^\]]*\]: +/d" "$temp_file"
sed_inplace "/^\[[^\]]*\]: */d" "$temp_file"
# Blocks
sed_inplace "s/^>!\[/> [!/g" "$temp_file"
@@ -50,10 +54,6 @@ awk -f "$awk_dir/pipe_tables.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_f
awk -f "$awk_dir/headers.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
awk -f "$awk_dir/lists.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
sed_inplace "s/^\*\*\*+$/<hr \/>/g" "$temp_file"
sed_inplace "s/^---+$/<hr \/>/g" "$temp_file"
sed_inplace "s/^___+$/<hr \/>/g" "$temp_file"
# Spacing
awk -f "$awk_dir/breaks.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
awk -f "$awk_dir/paragraphs.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"

View File

@@ -0,0 +1,29 @@
# Maintainer: n0va <n0va@krzak.org>
pkgname=kewt-git
pkgver=r0.0000000
pkgrel=1
pkgdesc="A minimalist, 100% POSIX, static site generator inspired by werc and kew"
arch=('any')
url="https://git.krzak.org/N0VA/kewt"
license=('MIT')
makedepends=('git')
depends=('sh')
provides=('kewt')
conflicts=('kewt' 'kewt-bin')
source=("${pkgname}::git+${url}.git")
sha256sums=('SKIP')
pkgver() {
cd "${pkgname}"
printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
}
build() {
cd "${pkgname}"
sh tools/build-standalone.sh
}
package() {
cd "${pkgname}"
install -Dm755 kewt "${pkgdir}/usr/bin/kewt"
}

View File

@@ -0,0 +1,21 @@
# Maintainer: n0va <n0va@krzak.org>
pkgname=kewt-bin
pkgver=VERSION_PLACEHOLDER
pkgrel=1
pkgdesc="A minimalist, 100% POSIX, static site generator inspired by werc and kew"
arch=('any')
url="https://git.krzak.org/N0VA/kewt"
license=('MIT')
depends=('sh')
provides=('kewt')
conflicts=('kewt' 'kewt-git')
source=("${pkgname}-${pkgver}.sh::${url}/releases/download/v${pkgver}/kewt")
sha256sums=('SHA256SUM_PLACEHOLDER')
build() {
chmod +x "${srcdir}/${pkgname}-${pkgver}.sh"
}
package() {
install -Dm755 "${srcdir}/${pkgname}-${pkgver}.sh" "${pkgdir}/usr/bin/kewt"
}

View File

@@ -10,3 +10,7 @@ display_title = true
logo_as_favicon = true
favicon = ""
order = ""
home_name = "Home"
show_home_in_nav = true
nav_links = ""
nav_extra = ""

View File

@@ -15,3 +15,5 @@ Sed dictum tortor at interdum dignissim. Nunc hendrerit sollicitudin elementum.
Cras vitae sapien egestas, blandit libero et, volutpat augue. Ut augue quam, sollicitudin quis libero laoreet, bibendum imperdiet massa. Duis sed venenatis risus. Praesent a est mollis, viverra erat quis, faucibus elit. Donec at sagittis est, non posuere nisi. Integer posuere pharetra dui in aliquam. Morbi vehicula eros in hendrerit aliquam. Duis in turpis vel mauris mattis convallis in id tortor. Cras et aliquam augue.
Cras quis consectetur dolor, a sodales tortor. Vestibulum aliquam lacinia metus, sed viverra erat egestas in. Morbi interdum sapien sed bibendum maximus. Aenean accumsan pharetra libero dapibus aliquam. Etiam sodales purus posuere gravida ullamcorper. Vestibulum tincidunt, nibh a pulvinar aliquet, leo tortor pulvinar diam, ut viverra nunc elit bibendum nulla. Praesent vel pulvinar erat, eu efficitur magna. Mauris at consequat purus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce in dui quis nisi elementum aliquam. Proin eget justo sed est commodo accumsan. Suspendisse a feugiat tellus, eget gravida tellus.
![https://www.youtube.com/embed/NvQD9E5Cq8A]

View File

@@ -19,6 +19,20 @@ It's meant to be a static site generator, like _[kew](https://github.com/uint23/
If you want to **force** a file to be inlined, use `\!![]` instead of `\![]`
## Installation
You can clone the repository to use `kewt.sh` directly, or you can download the standalone executable, which bundles all dependencies into a single file:
```sh
curl -L -o kewt https://git.krzak.org/N0VA/kewt/releases/latest/download/kewt
chmod +x kewt
```
On Arch Linux, _kewt_ is available on the AUR:
- [kewt-bin](https://aur.archlinux.org/packages/kewt-bin) — prebuilt standalone binary from the latest release
- [kewt-git](https://aur.archlinux.org/packages/kewt-git) — built from the latest git source
## Usage
```sh
@@ -39,6 +53,10 @@ dir_indexes = true
single_file_index = true
flatten = false
order = ""
home_name = "Home"
show_home_in_nav = true
nav_links = ""
nav_extra = ""
footer = "made with <a href="https://kewt.krzak.org">kewt</a>"
logo = ""
display_logo = false
@@ -53,6 +71,10 @@ favicon = ""
- `single_file_index` if a directory has one markdown file and no `index.md`, use that file as `index.html`
- `flatten` flatten sidebar directory levels
- `order` comma separated file/directory name list to order the sidebar (alphabetical by default)
- `home_name` text for the home link in navigation (default: "Home")
- `show_home_in_nav` show home link in navigation (default: true)
- `nav_links` comma separated extra nav links, as bare URLs or Markdown links like `[Label](https://example.com)`
- `nav_extra` raw HTML appended inside the `<nav>` after the generated link list
- `footer` footer html/text shown at the bottom of pages
- `logo` logo image path (used in header if enabled)
- `display_logo` show logo in header
@@ -60,6 +82,12 @@ favicon = ""
- `logo_as_favicon` use `logo` as favicon
- `favicon` explicit favicon path (used when `logo_as_favicon` is false or no logo is set)
## Ignores
- `.kewtignore`: Files/directories to ignore. If empty, the whole directory gets ignored
- `.kewthide`: Files/directories to hide from navigation but still process. Same empty rules as with ignore
- `.kewtpreserve`: Files/directories to copy but not convert markdown to html. Same empty rules again
## Embeds
- `\![link]`:

View File

@@ -43,6 +43,15 @@ header h1 {
font-style: italic;
}
.site-logo {
vertical-align: middle;
height: 1.1em;
width: auto;
border: none;
margin-right: 0.2em;
margin-top: -0.1em;
}
header a {
color: var(--fg);
text-decoration: none;
@@ -199,3 +208,15 @@ footer {
margin-left: 240px;
margin-top: 0px;
}
img {
image-rendering: auto;
image-rendering: crisp-edges;
image-rendering: pixelated;
image-rendering: -webkit-optimize-contrast;
}
footer img {
display: inline-block;
vertical-align: top;
}

33
tools/build-standalone.sh Executable file
View File

@@ -0,0 +1,33 @@
#!/bin/sh
set -e
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
if [ ! -f "$REPO_ROOT/kewt.sh" ]; then
echo "kewt.sh not found. Run from the repository root or tools/."
exit 1
fi
OUT_FILE="$REPO_ROOT/kewt"
cat << 'EOF' > "$OUT_FILE"
#!/bin/sh
tmpdir=$(mktemp -d "/tmp/kewt.XXXXXX")
trap 'rm -rf "$tmpdir"' EXIT HUP INT TERM
# Extract payload
sed '1,/^#==PAYLOAD==$/d' "$0" | tar -xz -C "$tmpdir"
# Pass control to the extracted script
KEWT_INVOKED_AS="$0" "$tmpdir/kewt.sh" "$@"
exit $?
#==PAYLOAD==
EOF
tar -cz -C "$REPO_ROOT" kewt.sh markdown.sh awk styles >> "$OUT_FILE"
chmod +x "$OUT_FILE"
echo "Generated standalone executable at $OUT_FILE"