32 Commits

Author SHA1 Message Date
4069bafd52 fix: typo in codeblock handling that made it so headers appear
All checks were successful
Lint / shellcheck (push) Successful in 19s
Release Standalone Builder / build (release) Successful in 33s
Release Standalone Builder / publish-aur (release) Successful in 36s
Release Standalone Builder / publish-homebrew (release) Successful in 7s
2026-03-24 08:18:11 +01:00
9dbd41392e docs: update installation instructions link in readme
All checks were successful
Lint / shellcheck (push) Successful in 22s
2026-03-24 08:12:48 +01:00
35eac48dcd fix: v1.4.0 hotfix
All checks were successful
Lint / shellcheck (push) Successful in 22s
Release Standalone Builder / build (release) Successful in 32s
Release Standalone Builder / publish-aur (release) Successful in 35s
Release Standalone Builder / publish-homebrew (release) Successful in 6s
2026-03-23 12:09:54 +01:00
ef16ed4c88 feat: frontmatter
Some checks failed
Lint / shellcheck (push) Successful in 53s
Release Standalone Builder / publish-aur (release) Successful in 36s
Release Standalone Builder / publish-homebrew (release) Failing after 6s
Release Standalone Builder / build (release) Successful in 34s
2026-03-23 11:39:05 +01:00
30b7681234 Update index.md
All checks were successful
Lint / shellcheck (push) Successful in 19s
2026-03-22 07:24:36 +01:00
13b6106efd docs: bpkg
All checks were successful
Lint / shellcheck (push) Successful in 20s
2026-03-22 07:23:24 +01:00
831b081fc7 docs: new contribution instructions
All checks were successful
Lint / shellcheck (push) Successful in 19s
2026-03-21 16:10:33 +01:00
fde423a32b docs: brew
All checks were successful
Lint / shellcheck (push) Successful in 18s
2026-03-20 09:36:53 +01:00
55a82f75a9 fix: link in homebrew
All checks were successful
Lint / shellcheck (push) Successful in 18s
2026-03-20 09:33:45 +01:00
f85abd43c4 fix: brew
Some checks failed
Lint / shellcheck (push) Has been cancelled
Release Standalone Builder / build (release) Successful in 30s
Release Standalone Builder / publish-aur (release) Successful in 32s
Release Standalone Builder / publish-homebrew (release) Successful in 6s
2026-03-20 09:26:20 +01:00
0f66ebf52a dist: brew
All checks were successful
Lint / shellcheck (push) Successful in 18s
2026-03-20 09:23:43 +01:00
55a515ccd5 dist: bpkg preparation
All checks were successful
Lint / shellcheck (push) Successful in 18s
Release Standalone Builder / build (release) Successful in 31s
Release Standalone Builder / publish-aur (release) Successful in 34s
2026-03-20 08:46:13 +01:00
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
c5a9355871 fix: config slashes
All checks were successful
Lint / shellcheck (push) Successful in 18s
Publish kewt-git to AUR / publish-aur-git (push) Successful in 30s
Release Standalone Builder / build (release) Successful in 31s
Release Standalone Builder / publish-aur (release) Successful in 34s
2026-03-19 21:45:38 +01:00
b8cd129c47 fix: rename to later in the alphabet, just in case
All checks were successful
Lint / shellcheck (push) Successful in 17s
Publish kewt-git to AUR / publish-aur-git (push) Successful in 33s
2026-03-19 21:29:22 +01:00
5e033a65e7 dist: AUR updates and upgrades
All checks were successful
Publish kewt-git to AUR / publish-aur-git (push) Successful in 34s
Lint / shellcheck (push) Successful in 20s
2026-03-19 21:26:17 +01:00
5bf2e2abe5 Update LICENSE
All checks were successful
Lint / shellcheck (push) Successful in 20s
2026-03-19 16:27:56 +01:00
3a2056ff8f Update site/site.conf
All checks were successful
Lint / shellcheck (push) Successful in 20s
2026-03-19 16:20:52 +01:00
fd829a3f22 branding: icon
All checks were successful
Lint / shellcheck (push) Successful in 19s
2026-03-19 16:12:21 +01:00
bad02decba docs: add contributing instructions
All checks were successful
Lint / shellcheck (push) Successful in 18s
2026-03-19 15:58:41 +01:00
2aef6ec4a1 Update site/site.conf
All checks were successful
Lint / shellcheck (push) Successful in 19s
2026-03-19 15:40:39 +01:00
78eac182dc dist: github release descriptions
All checks were successful
Lint / shellcheck (push) Successful in 19s
Release Standalone Builder / build (release) Successful in 30s
Release Standalone Builder / publish-aur (release) Successful in 33s
2026-03-19 15:37:55 +01:00
b7382a20ab feat: new default style and more
All checks were successful
Lint / shellcheck (push) Successful in 20s
2026-03-19 15:35:14 +01:00
d8cf07ee2a docs: move to only be on the website
All checks were successful
Lint / shellcheck (push) Successful in 18s
2026-03-19 15:25:01 +01:00
76e2ae0117 make automatic filesnames not use : for better windows support ig
All checks were successful
Lint / shellcheck (push) Successful in 20s
2026-03-19 09:35:14 +01:00
2e331b5d9a fix: to the fix, clap your hands together, caramelldanse o-o-or whatever
All checks were successful
Lint / shellcheck (push) Successful in 17s
Release Standalone Builder / build (release) Successful in 31s
Release Standalone Builder / publish-aur (release) Successful in 35s
2026-03-18 22:26:32 +01:00
9f5d1089a2 feat: --version, fix: --post fixes
All checks were successful
Lint / shellcheck (push) Successful in 19s
2026-03-18 22:18:13 +01:00
32 changed files with 872 additions and 289 deletions

View File

@@ -0,0 +1,38 @@
name: Publish kewt-git to AUR
on:
push:
branches:
- main
paths:
- 'packaging/AUR/PKGBUILD.git'
- 'packaging/AUR/.SRCINFO.git'
workflow_dispatch:
jobs:
publish-aur-git:
runs-on: local
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: Prepare AUR files
run: |
mkdir -p aur-work
cp packaging/AUR/PKGBUILD.git aur-work/PKGBUILD
cp packaging/AUR/.SRCINFO.git aur-work/.SRCINFO
- name: Publish to AUR
uses: KSXGitHub/github-actions-deploy-aur@v3.0.1
with:
pkgname: kewt-git
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-git to ${{ github.sha }}"

View File

@@ -3,6 +3,7 @@ name: Release Standalone Builder
on:
release:
types: [published]
workflow_dispatch:
jobs:
build:
@@ -32,12 +33,24 @@ jobs:
run: |
TAG="${GITHUB_REF#refs/tags/}"
# Fetch release body from Gitea
RELEASE_BODY=$(curl -sL \
"https://git.krzak.org/api/v1/repos/N0VA/kewt/releases/tags/${TAG}" \
| jq -r '.body // ""')
# Build JSON payload
PAYLOAD=$(jq -n \
--arg tag "$TAG" \
--arg name "Release $TAG" \
--arg body "$RELEASE_BODY" \
'{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')
# 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
-d "$PAYLOAD" || true
# Get the release ID
RELEASE_ID=$(curl -sL \
@@ -78,25 +91,9 @@ jobs:
-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
sed -e "s/VERSION_PLACEHOLDER/${VERSION}/g" \
-e "s/SHA256SUM_PLACEHOLDER/${CHECKSUM}/g" \
packaging/AUR/.SRCINFO.template > aur-work/.SRCINFO
- name: Publish to AUR
uses: KSXGitHub/github-actions-deploy-aur@v3.0.1
@@ -107,3 +104,34 @@ jobs:
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 }}"
publish-homebrew:
runs-on: local
needs: build
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Render Formula and push to GitHub
run: |
TAG="${GITHUB_REF#refs/tags/}"
VERSION="${TAG#v}"
curl -sL -o kewt-binary \
"https://git.krzak.org/N0VA/kewt/releases/download/${TAG}/kewt"
CHECKSUM=$(sha256sum kewt-binary | awk '{print $1}')
rm -f kewt-binary
git clone https://x-access-token:${{ secrets.GH_RELEASE_TOKEN }}@github.com/n0va-bot/homebrew-tap.git brew-work || true
mkdir -p brew-work/Formula
sed -e "s/VERSION_PLACEHOLDER/${VERSION}/g" \
-e "s/SHA256SUM_PLACEHOLDER/${CHECKSUM}/g" \
packaging/homebrew/kewt.rb.template > brew-work/Formula/kewt.rb
cd brew-work
[ -d .git ] || { git init && git checkout --orphan main && git remote add origin https://x-access-token:${{ secrets.GH_RELEASE_TOKEN }}@github.com/n0va-bot/homebrew-tap.git; }
git add Formula/kewt.rb
git config user.name "${{ github.actor }}"
git config user.email "${{ github.actor }}@users.noreply.github.com"
git commit -m "Update kewt to ${TAG}" || echo "No changes to commit"
git push -u origin main

View File

@@ -18,9 +18,8 @@ PERFORMANCE OF THIS SOFTWARE.
---
This project incorporates code (CSS style) from the 'kew' project, which is also licensed under the ISC License:
Copyright (c) 2023 uint23
This project incorporates code (CSS style) from the 'kew' project,
which is also licensed under the ISC License: Copyright (c) 2026 uint23
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above

19
Makefile Normal file
View File

@@ -0,0 +1,19 @@
PREFIX ?= /usr/local
BINDIR = $(PREFIX)/bin
all: kewt
kewt:
./tools/build-standalone.sh
install: kewt
install -d $(DESTDIR)$(BINDIR)
install -m 755 kewt $(DESTDIR)$(BINDIR)/kewt
uninstall:
rm -f $(DESTDIR)$(BINDIR)/kewt
clean:
rm -f kewt
.PHONY: all install uninstall clean

124
README.md
View File

@@ -1,125 +1,21 @@
# _kewt_
# ![kewt](/icon.svg)
### Pronounced "cute"
***
# [Go to the website](https://kewt.krzak.org)
***
_kewt_ is a minimalist ssg inspired by _[werc](http://werc.cat-v.org/)_ and _[kew](https://github.com/uint23/kew)_
It's meant to be a static site generator, like _[kew](https://github.com/uint23/kew)_ but use only default (POSIX) tooling, like _[werc](http://werc.cat-v.org/)_ (and definitely unlike _[kew](https://github.com/uint23/kew)_)
## Features
## [Installation](https://kewt.krzak.org/#installation)
- No dependencies
- Supports many embed types
- Automatic css variable replacement for older browsers
- 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)
- RSS/Feed generation and Sitemap support
- Post creation via `--post`
## Contributing
If you want to **force** a file to be inlined, use `\!![]` instead of `\![]`
Either open an issue or pull request on the **home** repository ([N0VA/kewt](https://git.krzak.org/N0VA/kewt)) or message me on my email address ([n0va@krzak.org](mailto:n0va@krzak.org?subject=%5Bkewt%5D%20something)) with the subjectline being `[kewt] something`.
## Installation
## License
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/download/latest/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
./kewt.sh --help
./kewt.sh --new [title]
./kewt.sh --post
./kewt.sh --from <src> --to <out>
./kewt.sh [src] [out]
```
`--new [title]` creates a new site directory with a copied `site.conf` and a default `index.md`.
`--post` creates a new empty markdown file in the configured `posts_dir` with the current date and time as the name.
## site.conf
```conf
title = "kewt"
style = "kewt"
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
logo_as_favicon = true
favicon = ""
generate_page_title = true
error_page = "not_found.html"
versioning = false
base_url = ""
generate_feed = false
feed_file = "rss.xml"
posts_dir = ""
```
- `title` site title
- `style` style file name from `./styles` (without `.css`)
- `dir_indexes` generate directory index pages when missing `index.md`
- `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
- `display_title` show title text in header
- `logo_as_favicon` use `logo` as favicon
- `favicon` explicit favicon path (used when `logo_as_favicon` is false or no logo is set)
- `generate_page_title` automatically generate title text from the first markdown heading or filename (default: true)
- `error_page` filename for the generated 404 error page (default: "not_found.html", empty to disable)
- `versioning` append a version query parameter (`?v=timestamp`) to css asset urls to bypass cache (default: false)
- `base_url` absolute URL of the site, used for sitemap and RSS feed generation
- `generate_feed` enable RSS feed generation (requires `base_url`)
- `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.
## 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]`:
- local image/audio/video files are embedded as media tags
- local text/code files are inlined directly
- global image/audio/video links are embedded as media tags
- other global links are embedded as `<iframe>`
- `\![alt](link)` works the same, with `alt` used for images
- `\!![]` and `\!![alt](link)` force inline local file contents
## Credits
- Default css style and html template based on _[kew](https://github.com/uint23/kew)_ by [uint23](https://github.com/uint23)
>[!WARNING]
>Most of this was coded at night, while sleepy and a bit sick, and after walking for about 4 hours around a forest, so...
ISC

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

@@ -1,4 +1,5 @@
BEGIN {
src = ENVIRON["AWK_SRC"]
slen = length(src)
}

View File

@@ -1,11 +1,20 @@
BEGIN { in_fence = 0; first_line = 0 }
BEGIN { in_fence = 0; first_line = 0; code_tag = "<code>" }
{
if (!in_fence && $0 ~ /^```/) {
in_fence = 1
first_line = 1
lang = $0
sub(/^```[[:space:]]*/, "", lang)
sub(/[[:space:]]*$/, "", lang)
if (lang != "") {
code_tag = "<code class=\"language-" lang "\">"
} else {
code_tag = "<code>"
}
next
}
if (in_fence && $0 ~ /^```[[:space:]]*$/) {
if (first_line) printf "%s", "<pre>" code_tag
print "</code></pre>"
in_fence = 0
next
@@ -14,8 +23,12 @@ BEGIN { in_fence = 0; first_line = 0 }
gsub(/&/, "\\&amp;"); gsub(/</, "\\&lt;"); gsub(/>/, "\\&gt;")
if (first_line) {
first_line = 0
if ($0 == "") next
print "<pre><code>" $0
printf "%s", "<pre>" code_tag
if ($0 == "") {
print ""
next
}
print $0
} else {
print
}
@@ -24,5 +37,8 @@ BEGIN { in_fence = 0; first_line = 0 }
}
}
END {
if (in_fence) print "</code></pre>"
if (in_fence) {
if (first_line) printf "%s", "<pre>" code_tag
print "</code></pre>"
}
}

46
awk/frontmatter.awk Normal file
View File

@@ -0,0 +1,46 @@
BEGIN {
state = "start"
}
{
if (state == "start") {
if ($0 == "---") {
state = "in_fm"
next
} else {
state = "body"
print
next
}
}
if (state == "in_fm") {
if ($0 == "---") {
state = "body"
next
}
line = $0
if (line ~ /^[[:space:]]*$/ || line ~ /^[[:space:]]*#/) next
if (line !~ /=/) next
key = line
val = line
sub(/=.*/, "", key)
sub(/[^=]*=/, "", val)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", key)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", val)
if (val ~ /^".*"$/) {
val = substr(val, 2, length(val) - 2)
gsub(/\\"/, "\"", val)
} else if (val ~ /^'.*'$/) {
val = substr(val, 2, length(val) - 2)
gsub(/\\'/, "'", val)
}
if (fm_out != "") {
print key "=" val >> fm_out
}
next
}
print
}

View File

@@ -33,6 +33,13 @@ function compare_paths(p1, p2, parts1, parts2, n1, n2, i, name1, name2, lname
}
BEGIN {
src = ENVIRON["AWK_SRC"]
single_file_index = ENVIRON["AWK_SINGLE_FILE_INDEX"]
flatten = ENVIRON["AWK_FLATTEN"]
order = ENVIRON["AWK_ORDER"]
home_name = ENVIRON["AWK_HOME_NAME"]
show_home_in_nav = ENVIRON["AWK_SHOW_HOME_IN_NAV"]
dinfo = ENVIRON["AWK_DINFO"]
n_dlines = split(dinfo, dlines, "\n")
for (i = 1; i <= n_dlines; i++) {
if (split(dlines[i], dparts, "|") == 3) {

View File

@@ -3,8 +3,14 @@ function strip_markdown(s) {
gsub(/[*_`~]/, "", s)
gsub(/[\[\]]/, "", s)
gsub(/\([^\)]*\)/, "", s)
s = tolower(s)
gsub(/[^a-z0-9 -]/, "", s)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", s)
gsub(/[[:space:]]+/, "-", s)
gsub(/-{2,}/, "-", s)
gsub(/^-+|-+$/, "", s)
if (length(s) > 80) s = substr(s, 1, 80)
gsub(/-+$/, "", s)
return s
}
function print_header(line) {
@@ -32,7 +38,7 @@ BEGIN {
in_pre = 0
}
{
if ($0 ~ /^<pre><code>/) {
if ($0 ~ /^<pre><code/) {
in_pre = 1
if (has_prev && prev != "") { print_header(prev); has_prev = 0 }
print

View File

@@ -1,9 +1,20 @@
BEGIN { in_code = 0 }
/^ | / {
if (!in_code) { print "<pre><code>"; in_code = 1 }
sub(/^ | /, "", $0)
BEGIN { in_code = 0; in_html_pre = 0 }
{
if ($0 ~ /<pre>/) in_html_pre = 1
if ($0 ~ /<\/pre>/) { in_html_pre = 0; if (in_code) { print "</code></pre>"; in_code = 0 }; print; next }
if (!in_html_pre && $0 ~ /^(\t| )/) {
if (!in_code) { printf "%s", "<pre><code>"; in_code = 1 }
sub(/^(\t| )/, "", $0)
gsub(/&/, "\\&amp;"); gsub(/</, "\\&lt;"); gsub(/>/, "\\&gt;")
print; next
print
next
}
if (in_code) {
print "</code></pre>"
in_code = 0
}
print
}
{ if (in_code) { print "</code></pre>"; in_code = 0 } print }
END { if (in_code) print "</code></pre>" }

View File

@@ -48,7 +48,26 @@ BEGIN {
}
}
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] ">"

View File

@@ -204,7 +204,7 @@ function render_embed(src, alt, has_alt, force_inline, ext, local_path, conte
}
if (is_audio_ext(ext)) return "<audio controls src=\"" src "\"></audio>"
if (is_video_ext(ext)) return "<video controls src=\"" src "\"></video>"
return "<iframe src=\"" src "\"></iframe>"
return "<iframe src=\"" src "\" allowfullscreen></iframe>"
}
if (is_image_ext(ext)) {
@@ -223,7 +223,29 @@ function render_embed(src, alt, has_alt, force_inline, ext, local_path, conte
}
}
return "<iframe src=\"" src "\"></iframe>"
return "<iframe src=\"" src "\" allowfullscreen></iframe>"
}
function render_typed_embed(etype, src, alt, has_alt, local_path, content) {
if (etype == "i") {
if (has_alt) return "<img alt=\"" alt "\" src=\"" src "\" />"
return "<img src=\"" src "\" />"
}
if (etype == "v") return "<video controls src=\"" src "\"></video>"
if (etype == "a") return "<audio controls src=\"" src "\"></audio>"
if (etype == "f") return "<iframe src=\"" src "\" allowfullscreen></iframe>"
if (etype == "e") {
if (!is_global_url(src)) {
local_path = resolve_local_path(src)
if (local_path != "") {
content = read_file(local_path)
if (content ~ /\n$/) sub(/\n$/, "", content)
return content
}
}
return render_embed(src, alt, has_alt, 1)
}
return render_embed(src, alt, has_alt, 0)
}
function extract_attr(tag, attr, pat, m, token) {
@@ -319,7 +341,7 @@ function apply_td_vertical_align(line, out, rest, seg, td_tag, img_tag, after
return out rest
}
function rewrite_img_tags(line, out, rest, tag, src, alt, force_inline_tag, pre, post, repl) {
function rewrite_img_tags(line, out, rest, tag, src, alt, force_inline_tag, embed_type, pre, post, repl) {
out = ""
rest = line
while (match(rest, /<img[^>]*\/?>/)) {
@@ -329,7 +351,10 @@ function rewrite_img_tags(line, out, rest, tag, src, alt, force_inline_tag, p
src = extract_attr(tag, "src")
alt = extract_attr(tag, "alt")
force_inline_tag = extract_attr(tag, "data-force-inline")
if (is_image_ext(ext_of(src)) && force_inline_tag == "") {
embed_type = extract_attr(tag, "data-embed-type")
if (embed_type != "") {
repl = render_typed_embed(embed_type, src, alt, (alt != ""))
} else if (is_image_ext(ext_of(src)) && force_inline_tag == "") {
# Preserve hand-written <img> attributes (style/class/etc) for normal images.
repl = tag
} else {

View File

@@ -20,9 +20,11 @@ function mask_html_tags(s, out, rest, start, len, tag, token) {
return out rest
}
function restore_html_tags(s, i) {
function restore_html_tags(s, i, val) {
for (i = 1; i <= html_tag_count; i++) {
gsub(html_tag_token[i], html_tag_value[i], s)
val = html_tag_value[i]
gsub(/&/, "\\\\&", val)
gsub(html_tag_token[i], val, s)
}
return s
}
@@ -58,6 +60,36 @@ function restore_html_tags(s, i) {
line = substr(line, 1, start - 1) repl substr(line, start + len)
}
# typed embeds: !i, !v, !a, !f, !e
while (match(line, /![ivafe]\[[^\]]*\]\([^\)]+ "[^"]*"\)/)) {
start = RSTART; len = RLENGTH
token = substr(line, start, len)
etype = substr(token, 2, 1)
match(token, /\[[^\]]*\]/); alt = substr(token, RSTART + 1, RLENGTH - 2)
match(token, /"[^"]*"/); etitle = substr(token, RSTART + 1, RLENGTH - 2)
match(token, /\([^\)]+/); inner = substr(token, RSTART + 1, RLENGTH - 1)
sub(/[[:space:]]*"[^"]*"/, "", inner); src = inner
repl = "<img data-embed-type=\"" etype "\" alt=\"" alt "\" src=\"" src "\" title=\"" etitle "\" />"
line = substr(line, 1, start - 1) repl substr(line, start + len)
}
while (match(line, /![ivafe]\[[^\]]*\]\([^\)]+\)/)) {
start = RSTART; len = RLENGTH
token = substr(line, start, len)
etype = substr(token, 2, 1)
match(token, /\[[^\]]*\]/); alt = substr(token, RSTART + 1, RLENGTH - 2)
match(token, /\([^\)]+/); src = substr(token, RSTART + 1, RLENGTH - 1)
repl = "<img data-embed-type=\"" etype "\" alt=\"" alt "\" src=\"" src "\" />"
line = substr(line, 1, start - 1) repl substr(line, start + len)
}
while (match(line, /![ivafe]\[[^\]]+\]/)) {
start = RSTART; len = RLENGTH
token = substr(line, start, len)
etype = substr(token, 2, 1)
src = substr(token, 4, len - 4)
repl = "<img data-embed-type=\"" etype "\" src=\"" src "\" />"
line = substr(line, 1, start - 1) repl substr(line, start + len)
}
# force-inline image syntax (double bang)
while (match(line, /!!\[[^\]]*\]\([^\)]+ "[^"]*"\)/)) {
start = RSTART; len = RLENGTH

View File

@@ -1,9 +1,24 @@
function replace_all(text, token, value, pos, token_len) {
function replace_all(text, token, value, pos, token_len, res) {
token_len = length(token)
res = ""
while ((pos = index(text, token)) > 0) {
text = substr(text, 1, pos - 1) value substr(text, pos + token_len)
res = res substr(text, 1, pos - 1) value
text = substr(text, pos + token_len)
}
return res text
}
BEGIN {
current_url = ENVIRON["AWK_CURRENT_URL"]
nav = ENVIRON["AWK_NAV"]
title = ENVIRON["AWK_TITLE"]
footer = ENVIRON["AWK_FOOTER"]
style_path = ENVIRON["AWK_STYLE_PATH"]
head_extra = ENVIRON["AWK_HEAD_EXTRA"]
header_brand = ENVIRON["AWK_HEADER_BRAND"]
if (current_url != "") {
nav = replace_all(nav, "href=\"" current_url "\"", "href=\"" current_url "\" class=\"current-page\"")
}
return text
}
{

View File

@@ -1,4 +1,7 @@
BEGIN { done = 0 }
BEGIN {
new_title = ENVIRON["AWK_NEW_TITLE"]
done = 0
}
/^title[[:space:]]*=/ {
print "title = \"" new_title "\""
done = 1

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

21
icon.svg Normal file
View File

@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
<defs>
<linearGradient id="bg-grad" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#4a3b69"/>
<stop offset="100%" stop-color="#352654"/>
</linearGradient>
</defs>
<rect width="512" height="512" rx="80" ry="80" fill="url(#bg-grad)"/>
<text
x="256"
y="296"
text-anchor="middle"
dominant-baseline="central"
font-family="Georgia, 'Times New Roman', Times, serif"
font-size="220"
font-weight="bold"
font-style="italic"
fill="#debfff"
letter-spacing="-8"
>kewt</text>
</svg>

After

Width:  |  Height:  |  Size: 639 B

286
kewt.sh
View File

@@ -13,6 +13,7 @@ Usage: $invoked_as [--from <src>] [--to <out>]
$invoked_as --new [title]
$invoked_as --update [dir]
$invoked_as --post
$invoked_as --version
$invoked_as --help
Options:
@@ -20,6 +21,7 @@ Options:
--new [title] Create a new site directory (default: site)
--update [dir] Update site.conf and template.html with latest defaults (defaults to current directory)
--post Create a new empty post file in the configured posts_dir with current date and time as name
--version Show version information.
--from <src> Source directory (default: site)
--to <out> Output directory (default: out)
EOF
@@ -31,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
@@ -58,15 +68,15 @@ 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>
<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" />
@@ -85,24 +95,10 @@ 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
awk -v new_title="$new_title" -f "$awk_dir/update_site_conf.awk" "$new_dir/site.conf" > "$new_dir/site.conf.tmp" && mv "$new_dir/site.conf.tmp" "$new_dir/site.conf"
AWK_NEW_TITLE="$new_title" awk -f "$awk_dir/update_site_conf.awk" "$new_dir/site.conf" > "$new_dir/site.conf.tmp" && mv "$new_dir/site.conf.tmp" "$new_dir/site.conf"
fi
echo "Created new site at '$new_dir'."
@@ -111,6 +107,7 @@ create_new_site() {
create_new_post() {
post_src_dir="$1"
post_user_title="$2"
target_dir="$post_src_dir"
if [ -n "$posts_dir" ]; then
@@ -119,7 +116,7 @@ create_new_post() {
mkdir -p "$target_dir"
base_filename="$(date +%Y-%m-%d-%H:%M)"
base_filename="$(date +%Y-%m-%d-%H-%M)"
filename="${base_filename}.md"
file_path="$target_dir/$filename"
@@ -130,7 +127,12 @@ create_new_post() {
counter=$((counter + 1))
done
touch "$file_path"
post_date_val="$(date "+%Y-%m-%d %H:%M")"
if [ -n "$post_user_title" ]; then
printf -- '---\ntitle = "%s"\ndate = "%s"\ndraft = false\n---\n# %s\n' "$post_user_title" "$post_date_val" "$post_user_title" > "$file_path"
else
printf -- '---\ndate = "%s"\ndraft = false\n---\n' "$post_date_val" > "$file_path"
fi
echo "Created new post at '$file_path'."
exit 0
@@ -170,6 +172,7 @@ base_url = ""
generate_feed = false
feed_file = "rss.xml"
posts_dir = ""
custom_admonitions = ""
CONFEOF
# Update site.conf
@@ -205,6 +208,7 @@ CONFEOF
<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" />
@@ -259,8 +263,16 @@ while [ $# -gt 0 ]; do
shift
fi
;;
--version|-v)
echo "kewt version git"
exit 0
;;
--post)
post_mode="true"
if [ $# -gt 1 ] && [ "${2#-}" = "$2" ]; then
post_title="$2"
shift
fi
;;
--update)
update_dir="."
@@ -299,9 +311,15 @@ done
[ "$new_mode" = "true" ] && create_new_site "$new_title"
ensure_root_defaults
[ -z "$src" ] && src="site"
if [ -z "$src" ]; then
if [ "$post_mode" = "true" ] && [ -f "./site.conf" ]; then
src="."
else
src="site"
fi
fi
[ -z "$out" ] && out="out"
src="${src%/}"
@@ -407,12 +425,12 @@ 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")
dinfo=$(eval "find \"$1\" \( $IGNORE_ARGS -o $HIDE_ARGS -o $PRESERVE_ARGS \) -prune -o -print" | sort | AWK_SRC="$1" awk -f "$awk_dir/collect_dir_info.awk")
find_cmd="find \"$1\" \( $IGNORE_ARGS -o $HIDE_ARGS -o $PRESERVE_ARGS \) -prune -o -name \"*.md\" -print"
if [ -n "$posts_dir" ] && [ -d "$1/$posts_dir" ]; then
find_cmd="$find_cmd && echo \"$1/$posts_dir/index.md\""
fi
eval "$find_cmd" | sort -u | 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"
eval "$find_cmd" | sort -u | AWK_SRC="$1" AWK_SINGLE_FILE_INDEX="$single_file_index" AWK_FLATTEN="$flatten" AWK_ORDER="$order" AWK_HOME_NAME="$home_name" AWK_SHOW_HOME_IN_NAV="$show_home_in_nav" AWK_DINFO="$dinfo" awk -f "$awk_dir/generate_sidebar.awk"
}
title="kewt"
@@ -440,6 +458,7 @@ base_url=""
generate_feed="false"
feed_file="rss.xml"
posts_dir=""
custom_admonitions=""
load_config() {
[ -f "$1" ] || return
@@ -468,7 +487,7 @@ load_config() {
case "$key" in
title) title="$val" ;;
style) style="$val" ;;
style) style="${val#/}" ;;
dir_indexes) dir_indexes="$val" ;;
single_file_index) single_file_index="$val" ;;
flatten) flatten="$val" ;;
@@ -478,19 +497,20 @@ load_config() {
nav_links) nav_links="$val" ;;
nav_extra) nav_extra="$val" ;;
footer) footer="$val" ;;
logo) logo="$val" ;;
logo) logo="${val#/}" ;;
display_logo) display_logo="$val" ;;
display_title) display_title="$val" ;;
logo_as_favicon) logo_as_favicon="$val" ;;
favicon) favicon="$val" ;;
favicon) favicon="${val#/}" ;;
generate_page_title) generate_page_title="$val" ;;
error_page) error_page="$val" ;;
error_page) error_page="${val#/}" ;;
versioning) versioning="$val" ;;
enable_header_links) enable_header_links="$val" ;;
base_url) base_url="$val" ;;
generate_feed) generate_feed="$val" ;;
feed_file) feed_file="$val" ;;
posts_dir) posts_dir="$val" ;;
feed_file) feed_file="${val#/}" ;;
posts_dir) posts_dir="${val#/}" ;;
custom_admonitions) custom_admonitions="$val" ;;
esac
done < "$1"
}
@@ -502,7 +522,7 @@ if [ -n "$posts_dir" ]; then
HIDE_ARGS="$HIDE_ARGS -o -path '$src/$posts_dir/*'"
fi
[ "$post_mode" = "true" ] && create_new_post "$src"
[ "$post_mode" = "true" ] && create_new_post "$src" "$post_title"
asset_version=""
if [ "$versioning" = "true" ]; then
@@ -524,6 +544,24 @@ escape_html_attr() {
-e 's/>/\&gt;/g'
}
parse_frontmatter() {
_fm_file="$1"
_fm_out="$KEWT_TMPDIR/fm_vals.txt"
: > "$_fm_out"
awk -v fm_out="$_fm_out" -f "$awk_dir/frontmatter.awk" "$_fm_file" > /dev/null
fm_title=""
fm_date=""
fm_draft=""
while IFS='=' read -r _fk _fv; do
case "$_fk" in
title) fm_title="$_fv" ;;
date) fm_date="$_fv" ;;
draft) fm_draft="$_fv" ;;
esac
done < "$_fm_out"
rm -f "$_fm_out"
}
nav_links_html() {
[ -n "$nav_links" ] || return
@@ -570,7 +608,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"
@@ -611,6 +675,15 @@ copy_style_with_resolved_vars() {
render_markdown() {
file="$1"
is_home="$2"
url_override="$3"
if [ -n "$url_override" ]; then
current_url="$url_override"
else
rel_path="${file#"$src"}"
rel_path="${rel_path#/}"
current_url="/${rel_path%.md}.html"
fi
content_file="$file"
if [ -n "$posts_dir" ] && [ "$file" != "$src/$posts_dir/index.md" ]; then
@@ -620,7 +693,7 @@ render_markdown() {
if [ "$rel_dir_of_file" = "$posts_dir" ]; then
temp_post_with_backlink="$KEWT_TMPDIR/post_with_backlink.md"
printf "[< Back](index.html)\n\n" > "$temp_post_with_backlink"
cat "$file" >> "$temp_post_with_backlink"
awk -f "$awk_dir/frontmatter.awk" "$file" >> "$temp_post_with_backlink"
content_file="$temp_post_with_backlink"
fi
fi
@@ -670,11 +743,21 @@ render_markdown() {
fi
head_extra=""
if [ -n "$favicon_src" ]; then
if echo "$favicon_src" | grep -q "^http"; then
head_extra="<link rel=\"icon\" href=\"$favicon_src\" />"
elif echo "$favicon_src" | grep -q "^/"; then
head_extra="<link rel=\"icon\" href=\"$favicon_src\" />"
else
head_extra="<link rel=\"icon\" href=\"/$favicon_src\" />"
fi
fi
parse_frontmatter "$file"
page_title="$title"
if [ "$generate_page_title" = "true" ] && [ -n "$file" ] && [ -f "$file" ]; then
if [ -n "$fm_title" ]; then
page_title="$fm_title - $title"
elif [ "$generate_page_title" = "true" ] && [ -n "$file" ] && [ -f "$file" ]; then
if [ "$is_home" = "true" ] && [ -n "$home_name" ]; then
page_title="$home_name - $title"
else
@@ -692,7 +775,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 -v title="$page_title" -v nav="$nav" -v footer="$footer" -v style_path="${style_path}${asset_version}" -v header_brand="$header_brand" -v head_extra="$head_extra" -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'..."
@@ -705,20 +800,32 @@ eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type d -print" | sort | while
mkdir -p "$out_dir"
if [ -f "$dir/styles.css" ]; then
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
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
if [ ! -f "$dir/index.md" ]; then
if [ "$single_file_index" = "true" ]; then
is_posts_dir="false"
if [ -n "$posts_dir" ] && { [ "$rel_dir" = "$posts_dir" ] || [ "./$rel_dir" = "$posts_dir" ]; }; then
is_posts_dir="true"
fi
if [ "$single_file_index" = "true" ] && [ "$is_posts_dir" = "false" ]; then
md_count=$(find "$dir" ! -name "$(basename "$dir")" -prune -name "*.md" | wc -l)
if [ "$md_count" -eq 1 ]; then
md_file=$(find "$dir" ! -name "$(basename "$dir")" -prune -name "*.md")
is_home="false"; [ "$dir" = "$src" ] && is_home="true"
render_markdown "$md_file" "$is_home" > "$out_dir/index.html"
target_url="/$rel_dir/index.html"
[ "$rel_dir" = "." ] && target_url="/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
@@ -745,24 +852,62 @@ eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type d -print" | sort | while
elif [ "${entry%.md}" != "$entry" ]; then
label="${name%.md}"
# Parse frontmatter for date/title/draft
parse_frontmatter "$entry"
[ "$fm_draft" = "true" ] && continue
# Try to get first heading
post_h="$fm_title"
if [ -z "$post_h" ]; then
post_h=$(grep -m 1 '^# ' "$entry" | sed 's/^# *//')
if [ -n "$post_h" ]; then
post_h=$(echo "$post_h" | sed -e 's/\[//g' -e 's/\]//g' -e 's/!//g' -e 's/\*//g' -e 's/_//g' -e 's/`//g' -e 's/([^)]*)//g' | sed 's/\\//g')
fi
fi
is_post_entry="false"
if [ "$rel_dir" = "$posts_dir" ] || [ "./$rel_dir" = "$posts_dir" ]; then
# For posts add date and time
is_post_entry="true"
fi
if [ -n "$post_h" ]; then
if [ "$is_post_entry" = "true" ]; then
# Use frontmatter date if available, else parse from filename
if [ -n "$fm_date" ]; then
p_date=$(echo "$fm_date" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/')
p_time=""
if echo "$fm_date" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?[0-9]\{2\}[:\-][0-9]\{2\}'; then
p_time=$(echo "$fm_date" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
fi
else
p_date=$(echo "${name%.md}" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/')
p_time="00:00"
if echo "${name%.md}" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then
p_time=$(echo "${name%.md}" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
fi
fi
if [ -n "$p_time" ]; then
label="$post_h - $p_date $p_time"
else
label="$post_h - $p_date"
fi
else
label="$post_h"
fi
elif [ "$rel_dir" = "$posts_dir" ] || [ "./$rel_dir" = "$posts_dir" ]; then
# No heading and date and time for posts
elif [ "$is_post_entry" = "true" ]; then
# No heading; use date
if [ -n "$fm_date" ]; then
p_date=$(echo "$fm_date" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/')
p_time=""
if echo "$fm_date" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?[0-9]\{2\}[:\-][0-9]\{2\}'; then
p_time=$(echo "$fm_date" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
fi
if [ -n "$p_time" ]; then
label="$p_date $p_time"
else
label="$p_date"
fi
else
p_date=$(echo "${name%.md}" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/')
p_time="00:00"
if echo "${name%.md}" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then
@@ -770,18 +915,23 @@ eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type d -print" | sort | while
fi
label="$p_date $p_time"
fi
fi
echo "- [$label](${name%.md}.html)" >> "$temp_index"
else
echo "- [$name]($name)" >> "$temp_index"
fi
done
is_home="false"; [ "$dir" = "$src" ] && is_home="true"
render_markdown "$temp_index" "$is_home" > "$out_dir/index.html"
target_url="/$rel_dir/index.html"
[ "$rel_dir" = "." ] && target_url="/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
@@ -800,18 +950,32 @@ eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type f -print" | sort | while
is_preserved=1
fi
if [ "$single_file_index" = "true" ] && [ "${file%.md}" != "$file" ] && [ "$is_preserved" -eq 0 ] && [ ! -f "$(dirname "$file")/index.md" ]; then
is_posts_dir_2="false"
if [ -n "$posts_dir" ] && { [ "$dir_rel" = "$posts_dir" ] || [ "./$dir_rel" = "$posts_dir" ]; }; then
is_posts_dir_2="true"
fi
if [ "$single_file_index" = "true" ] && [ "${file%.md}" != "$file" ] && [ "$is_preserved" -eq 0 ] && [ ! -f "$(dirname "$file")/index.md" ] && [ "$is_posts_dir_2" = "false" ]; 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" ] && [ "$is_preserved" -eq 0 ]; then
# Skip draft files
parse_frontmatter "$file"
if [ "$fm_draft" = "true" ]; then
continue
fi
is_home="false"; [ "$file" = "$src/index.md" ] && is_home="true"
out_file="$out/${rel_path%.md}.html"
if needs_rebuild "$file" "$out_file"; then
render_markdown "$file" "$is_home" > "$out_file"
fi
else
if needs_rebuild "$file" "$out/$rel_path"; then
cp "$file" "$out/$rel_path"
fi
fi
done
if [ -n "$error_page" ] && [ ! -f "$out/$error_page" ]; then
@@ -819,7 +983,7 @@ if [ -n "$error_page" ] && [ ! -f "$out/$error_page" ]; then
echo "# 404 - Not Found" > "$temp_404"
echo "" >> "$temp_404"
echo "The requested page could not be found." >> "$temp_404"
render_markdown "$temp_404" > "$out/$error_page"
render_markdown "$temp_404" "false" "/$error_page" > "$out/$error_page"
rm -f "$temp_404"
fi
@@ -859,20 +1023,34 @@ if [ "$generate_feed" = "true" ] && [ -n "$base_url" ]; then
printf ' <description>%s</description>\n' "$title" >> "$feed_path"
printf ' <lastBuildDate>%s</lastBuildDate>\n' "$build_date" >> "$feed_path"
find "$src" -type f -name '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]*.md' -print | LC_ALL=C sort -r | while IFS= read -r post_file; do
find "$src" -type f -name '*.md' -path "*${posts_dir:-__no_posts__}*" -print | LC_ALL=C sort -r | while IFS= read -r post_file; do
post_basename=$(basename "$post_file" .md)
# Extract YYYY-MM-DD
post_date=$(echo "$post_basename" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/')
# Extract HH:MM if present (e.g., 2026-03-17-10:30 or 2026-03-17-10:30_1)
# Parse frontmatter
parse_frontmatter "$post_file"
[ "$fm_draft" = "true" ] && continue
# Use frontmatter date, fallback to filename
if [ -n "$fm_date" ]; then
post_date=$(echo "$fm_date" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/')
post_time="00:00"
if echo "$fm_date" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?[0-9]\{2\}[:\-][0-9]\{2\}'; then
post_time=$(echo "$fm_date" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
fi
else
post_date=$(echo "$post_basename" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/')
post_time="00:00"
if echo "$post_basename" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then
post_time=$(echo "$post_basename" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
fi
fi
post_slug=$(echo "$post_basename" | sed -e 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}//' -e 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}//' -e 's/^[_\-]//')
post_heading="$fm_title"
if [ -z "$post_heading" ]; then
post_heading=$(grep -m 1 '^# ' "$post_file" | sed 's/^# *//')
fi
if [ -z "$post_heading" ]; then
if [ -n "$post_slug" ] && ! echo "$post_slug" | grep -q '^[0-9]\+$'; then
post_heading=$(echo "$post_slug" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)}1')
@@ -881,7 +1059,7 @@ if [ "$generate_feed" = "true" ] && [ -n "$base_url" ]; then
fi
fi
post_heading=$(echo "$post_heading" | sed -e 's/\[//g' -e 's/\]//g' -e 's/!//g' -e 's/\*//g' -e 's/_//g' -e 's/`//g' -e 's/([^)]*)//g' | sed 's/\\//g')
post_title="$post_heading - $post_date $post_time"
feed_post_title="$post_heading - $post_date $post_time"
rel_path="${post_file#"$src"}"
rel_path="${rel_path#/}"
@@ -890,6 +1068,8 @@ if [ "$generate_feed" = "true" ] && [ -n "$base_url" ]; then
pub_year=$(echo "$post_date" | cut -d- -f1)
pub_month=$(echo "$post_date" | cut -d- -f2)
pub_day=$(echo "$post_date" | cut -d- -f3)
# zero-padded
pub_day=$(printf '%02d' "${pub_day#0}")
case "$pub_month" in
01) pub_mon="Jan" ;; 02) pub_mon="Feb" ;; 03) pub_mon="Mar" ;;
04) pub_mon="Apr" ;; 05) pub_mon="May" ;; 06) pub_mon="Jun" ;;
@@ -899,7 +1079,7 @@ if [ "$generate_feed" = "true" ] && [ -n "$base_url" ]; then
pub_date="${pub_day} ${pub_mon} ${pub_year} ${post_time}:00 +0000"
printf ' <item>\n' >> "$feed_path"
printf ' <title>%s</title>\n' "$post_title" >> "$feed_path"
printf ' <title>%s</title>\n' "$feed_post_title" >> "$feed_path"
printf ' <link>%s</link>\n' "$post_url" >> "$feed_path"
printf ' <guid>%s</guid>\n' "$post_url" >> "$feed_path"
printf ' <pubDate>%s</pubDate>\n' "$pub_date" >> "$feed_path"

View File

@@ -15,10 +15,15 @@ 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
trap 'rm -f "$temp_file" "$temp_file.tmp" "$temp_file.fm"' EXIT INT TERM
# Frontmatter
fm_file="$temp_file.fm"
: > "$fm_file"
awk -v fm_out="$fm_file" -f "$awk_dir/frontmatter.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
# Mask
awk -f "$awk_dir/mask_inline_code.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
@@ -40,14 +45,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"

7
package.json Normal file
View File

@@ -0,0 +1,7 @@
{
"name": "kewt",
"description": "A minimalist static site generator inspired by werc",
"global": "true",
"install": "make install",
"scripts": ["kewt"]
}

View File

@@ -0,0 +1,16 @@
pkgbase = kewt-git
pkgdesc = A minimalist, 100% POSIX, static site generator inspired by werc and kew
pkgver = r0.0000000
pkgrel = 3
url = https://kewt.krzak.org
arch = any
license = ISC
makedepends = git
depends = sh
provides = kewt
conflicts = kewt
conflicts = kewt-bin
source = kewt-git::git+https://git.krzak.org/N0VA/kewt.git
sha256sums = SKIP
pkgname = kewt-git

View File

@@ -0,0 +1,15 @@
pkgbase = kewt-bin
pkgdesc = A minimalist, 100% POSIX, static site generator inspired by werc and kew
pkgver = VERSION_PLACEHOLDER
pkgrel = 2
url = https://kewt.krzak.org
arch = any
license = ISC
depends = sh
provides = kewt
conflicts = kewt
conflicts = kewt-git
source = kewt-bin-VERSION_PLACEHOLDER.sh::https://git.krzak.org/N0VA/kewt/releases/download/vVERSION_PLACEHOLDER/kewt
sha256sums = SHA256SUM_PLACEHOLDER
pkgname = kewt-bin

View File

@@ -1,16 +1,16 @@
# Maintainer: n0va <n0va@krzak.org>
pkgname=kewt-git
pkgver=r0.0000000
pkgrel=1
pkgrel=2
pkgdesc="A minimalist, 100% POSIX, static site generator inspired by werc and kew"
arch=('any')
url="https://git.krzak.org/N0VA/kewt"
license=('MIT')
url="https://kewt.krzak.org"
license=('ISC')
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

@@ -4,12 +4,12 @@ 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')
url="https://kewt.krzak.org"
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

@@ -0,0 +1,16 @@
class Kewt < Formula
desc "Minimalist static site generator inspired by werc"
homepage "https://kewt.krzak.org"
url "https://github.com/n0va-bot/kewt/releases/download/vVERSION_PLACEHOLDER/kewt"
sha256 "SHA256SUM_PLACEHOLDER"
license "ISC"
version "VERSION_PLACEHOLDER"
def install
bin.install "kewt"
end
test do
system "#{bin}/kewt", "--version"
end
end

BIN
site/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

View File

@@ -1,8 +1,12 @@
# _kewt_
### Pronounced "cute"
***
# [Go to the repo](https://git.krzak.org/N0VA/kewt)
***
_kewt_ is a minimalist ssg inspired by _[werc](http://werc.cat-v.org/)_ and _[kew](https://github.com/uint23/kew)_
It's meant to be a static site generator, like _[kew](https://github.com/uint23/kew)_ but use only default (POSIX) tooling, like _[werc](http://werc.cat-v.org/)_ (and definitely unlike _[kew](https://github.com/uint23/kew)_)
@@ -10,35 +14,82 @@ It's meant to be a static site generator, like _[kew](https://github.com/uint23/
## Features
- No dependencies
- Frontmatter support (title, date, draft)
- Supports many embed types
- Automatic css variable replacement for older browsers
- Automatic inlining and embedding of many filetypes with `\![link]` or `\![alt](link)`
- Typed embeds: `\!i`, `\!v`, `\!a`, `\!f`, `\!e`
- 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
- `?v=n` support for cache busting
- Code block classes for use with external libraries like highlight.js or prism.js (both tested)
- Clickable markdown header anchors
- Mobile responsive layout
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:
### Standalone
```sh
curl -L -o kewt https://git.krzak.org/N0VA/kewt/releases/download/latest/kewt
chmod +x kewt
```
On Arch Linux, _kewt_ is available on the AUR:
### From source
```sh
git clone https://git.krzak.org/N0VA/kewt.git
cd kewt
```
#### Building
```sh
make
```
#### Installing
```sh
sudo make install
```
### Package Managers
#### 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
#### Homebrew
```sh
brew tap n0va-bot/tap
brew install kewt
```
#### bpkg
```sh
bpkg install n0va-bot/kewt
```
***
## Usage
```sh
./kewt.sh --help
./kewt.sh --version
./kewt.sh --new [title]
./kewt.sh --post
./kewt.sh --from <src> --to <out>
@@ -47,9 +98,9 @@ On Arch Linux, _kewt_ is available on the AUR:
`--new [title]` creates a new site directory with a copied `site.conf` and a default `index.md`.
`--post` creates a new empty markdown file in the configured `posts_dir` with the current date and time as the name.
`--post [title]` creates a new markdown file in the configured `posts_dir` with the current date/time as the name and creates the default frontmatter.
## site.conf
### site.conf
```conf
title = "kewt"
@@ -75,6 +126,8 @@ base_url = ""
generate_feed = false
feed_file = "rss.xml"
posts_dir = ""
enable_header_links = true
custom_admonitions = ""
```
- `title` site title
@@ -100,14 +153,16 @@ posts_dir = ""
- `generate_feed` enable RSS feed generation (requires `base_url`)
- `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
### 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
### Embeds
- `\![link]`:
- local image/audio/video files are embedded as media tags
@@ -116,10 +171,30 @@ posts_dir = ""
- other global links are embedded as `<iframe>`
- `\![alt](link)` works the same, with `alt` used for images
- `\!![]` and `\!![alt](link)` force inline local file contents
- **Typed Embeds**: Force specific output regardless of extension:
- `\!i[link]` or `\!i[alt](link)`: **I**mage
- `\!v[link]`: **V**ideo
- `\!a[link]`: **A**udio
- `\!f[link]`: I**f**rame
- `\!e[link]`: Inline/**e**mbed text/code file directly
## Credits
### Frontmatter
- Default css style and html template based on _[kew](https://github.com/uint23/kew)_ by [uint23](https://github.com/uint23)
You can set metadata for a page using a `site.conf`-style frontmatter block at the very top of `.md` files:
```conf
---
title = "Custom Page Title"
date = "2026-03-23 11:32"
draft = false
---
```
- `title`: Overrides the page title, post name in index links, and RSS `<title>`.
- `date`: Overrides the post date and time. Supports `YYYY-MM-DD` and `YYYY-MM-DD HH:MM` (or `HH-MM`).
- `draft`: If `true`, the file is excluded from HTML generation
***
>[!WARNING]
>Most of this was coded at night, while sleepy and a bit sick, and after walking for about 4 hours around a forest, so...
>The base that all of this is built upon was coded at night, while sleepy and a bit sick, and after walking for about 4 hours around a forest, so...

View File

@@ -7,8 +7,8 @@ footer = "<a href=\"https://kewt.krzak.org\"><img src=\"/button.gif\" /></a>"
logo = ""
display_logo = false
display_title = true
logo_as_favicon = true
favicon = ""
logo_as_favicon = false
favicon = "favicon.ico"
order = ""
home_name = "Home"
show_home_in_nav = true
@@ -16,6 +16,7 @@ nav_links = ""
nav_extra = ""
generate_page_title = true
error_page = "not_found.html"
versioning = false
versioning = true
enable_header_links = true
base_url = "https://kewt.krzak.org"
custom_admonitions = ""

View File

@@ -1,25 +1,28 @@
:root {
--bg: #646c7f;
--fg: #fffde0;
--fg-link: #fff18f;
--code-bg: #32394a;
--code-border: #8f95a4;
--code-fg: #fffde0;
--code-sel: #fff18f;
--code-prop: #ffd27f;
--code-val: #cde7ff;
--code-var: #b9ffbe;
--code-com: #d0d0d0;
--adm-note-bg: #3f5666;
--adm-note-border: #a8d8ff;
--adm-tip-bg: #3f664c;
--adm-tip-border: #b9ffbe;
--adm-important-bg: #5a4a6c;
--adm-important-border: #e4c7ff;
--adm-warning-bg: #6b5539;
--adm-warning-border: #ffe0a8;
--adm-caution-bg: #6f3f3f;
--adm-caution-border: #ffb4b4;
--bg: #4a3b69;
--bg-deep: #352654;
--fg: #fbf5ff;
--fg-muted: #c8b9df;
--fg-link: #dfaeff;
--fg-heading: #debfff;
--code-bg: #31234c;
--code-border: #8060af;
--code-fg: #fbf5ff;
--code-sel: #ffef99;
--code-prop: #ffdfba;
--code-val: #cae2ff;
--code-var: #caffc2;
--code-com: #b8aac8;
--adm-note-bg: #353866;
--adm-note-border: #b8c5ff;
--adm-tip-bg: #295246;
--adm-tip-border: #aeffda;
--adm-important-bg: #533076;
--adm-important-border: #f4d9ff;
--adm-warning-bg: #634631;
--adm-warning-border: #ffe2bd;
--adm-caution-bg: #662d43;
--adm-caution-border: #ffc4d5;
}
body {
@@ -29,18 +32,22 @@ body {
color: var(--fg);
font-family: serif;
font-size: 16px;
line-height: 1.2;
line-height: 1.5;
}
header {
padding: 20px;
padding-bottom: 0;
border-bottom: 1px solid var(--code-border);
margin-bottom: 20px;
}
header h1 {
margin: 0;
font-size: 35px;
font-weight: normal;
font-weight: bold;
font-style: italic;
color: var(--fg-heading);
}
.site-logo {
@@ -57,18 +64,26 @@ header a {
text-decoration: none;
}
header a:hover {
color: var(--bg-deep);
background: var(--fg);
}
#side-bar {
position: absolute;
top: 80px;
left: 0;
width: 200px;
padding-left: 20px;
margin-right: 14px;
border-right: 1px solid var(--code-border);
padding-right: 7px;
}
.side-title {
font-size: 25px;
margin: 20px 0 8px 0;
color: var(--fg);
color: var(--fg-heading);
}
#side-bar ul {
@@ -87,6 +102,14 @@ a {
padding: 1px 2px;
}
#side-bar a.current-page {
font-weight: bold;
color: var(--fg);
border-left: 3px solid var(--fg-link);
padding-left: 7px;
margin-left: -10px;
}
a:hover {
background: var(--fg);
color: var(--bg);
@@ -100,7 +123,7 @@ article {
h3 {
margin-top: 30px;
font-size: 25px;
color: var(--fg);
color: var(--fg-heading);
font-weight: normal;
}
@@ -197,10 +220,11 @@ pre code {
}
footer {
padding-top: 80px;
padding-top: 60px;
font-style: italic;
font-size: 17px;
margin-bottom: 20px;
color: var(--fg-muted);
}
article,
@@ -220,3 +244,42 @@ footer img {
display: inline-block;
vertical-align: top;
}
hr {
height: 0;
margin: 24px 0;
border: 0;
border-top: 1px solid var(--code-border);
}
@media screen and (max-width: 600px) {
#side-bar {
position: relative;
top: auto;
left: auto;
width: auto;
border-right: none;
border-bottom: 1px solid var(--code-border);
padding: 0 0 20px 0;
margin: 0 20px 20px 20px;
}
article {
margin: 0 20px 0 20px;
}
footer {
margin-left: 20px;
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;
}

View File

@@ -26,7 +26,13 @@ exit $?
#==PAYLOAD==
EOF
tar -cz -C "$REPO_ROOT" kewt.sh markdown.sh awk styles >> "$OUT_FILE"
VERSION=$(git describe --tags 2>/dev/null || echo "standalone")
tmpbuild=$(mktemp -d)
cp -r "$REPO_ROOT/kewt.sh" "$REPO_ROOT/markdown.sh" "$REPO_ROOT/awk" "$REPO_ROOT/styles" "$tmpbuild/"
sed -e "s/kewt version git/kewt version $VERSION/" "$tmpbuild/kewt.sh" > "$tmpbuild/kewt.sh.tmp" && mv "$tmpbuild/kewt.sh.tmp" "$tmpbuild/kewt.sh"
chmod +x "$tmpbuild/kewt.sh" "$tmpbuild/markdown.sh"
tar -cz -C "$tmpbuild" kewt.sh markdown.sh awk styles >> "$OUT_FILE"
rm -rf "$tmpbuild"
chmod +x "$OUT_FILE"