diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml
index 6ad4707..624a5ee 100644
--- a/.gitea/workflows/release.yml
+++ b/.gitea/workflows/release.yml
@@ -20,51 +20,80 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
- go-version: '1.21'
+ go-version: "1.21"
- name: Upload Release Asset
uses: https://gitea.com/actions/release-action@main
with:
files: |-
kewt
- api_key: '${{secrets.GITEA_TOKEN}}'
+ packaging/bash/kewt.bash
+ api_key: "${{secrets.GITEA_TOKEN}}"
- name: Push to GitHub Release
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 "$PAYLOAD" || 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-fedora:
+ runs-on: local
+ needs: build
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get update || true
+ sudo apt-get install -y rpm || true
+
+ - name: Build Fedora Assets
+ run: |
+ if command -v rpmbuild >/dev/null; then
+ make srpm
+ else
+ echo "rpmbuild not found, generating spec file only"
+ TAG="${GITHUB_REF#refs/tags/}"
+ VERSION="${TAG#v}"
+ sed -e "s/VERSION_PLACEHOLDER/${VERSION}/g" packaging/fedora/kewt.spec.template > packaging/fedora/kewt.spec
+ fi
+
+ - name: Upload Fedora Assets
+ uses: https://gitea.com/actions/release-action@main
+ with:
+ files: |-
+ kewt-*.src.rpm
+ packaging/fedora/kewt.spec
+ api_key: "${{secrets.GITEA_TOKEN}}"
+
publish-aur:
runs-on: local
needs: build
@@ -76,21 +105,21 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y openssh-client curl jq
-
+
- 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
-
+
sed -e "s/VERSION_PLACEHOLDER/${VERSION}/g" \
-e "s/SHA256SUM_PLACEHOLDER/${CHECKSUM}/g" \
packaging/AUR/.SRCINFO.template > aur-work/.SRCINFO
diff --git a/.gitignore b/.gitignore
index d2efdfb..4df1170 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,2 @@
out/
-kewt
\ No newline at end of file
+kewt
diff --git a/Makefile b/Makefile
index a281bdd..bbdc1ce 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,7 @@
PREFIX ?= /usr/local
BINDIR = $(PREFIX)/bin
ZSHCOMPDIR ?= $(PREFIX)/share/zsh/site-functions
+BASHCOMPDIR ?= $(PREFIX)/share/bash-completion/completions
all: kewt
@@ -12,13 +13,25 @@ install: kewt
install -m 755 kewt $(DESTDIR)$(BINDIR)/kewt
install -d $(DESTDIR)$(ZSHCOMPDIR)
install -m 644 packaging/zsh/_kewt $(DESTDIR)$(ZSHCOMPDIR)/_kewt
+ install -d $(DESTDIR)$(BASHCOMPDIR)
+ install -m 644 packaging/bash/kewt.bash $(DESTDIR)$(BASHCOMPDIR)/kewt
uninstall:
rm -f $(DESTDIR)$(BINDIR)/kewt
rm -f $(DESTDIR)$(ZSHCOMPDIR)/_kewt
+ rm -f $(DESTDIR)$(BASHCOMPDIR)/kewt
clean:
- rm -f kewt
+ rm -f kewt kewt-*.tar.gz
+
+dist:
+ $(eval VERSION := $(shell git describe --tags --always | sed 's/^v//;s/-/./g'))
+ tar -czf kewt-$(VERSION).tar.gz --exclude-vcs --exclude=kewt --exclude=kewt-$(VERSION).tar.gz --transform "s|^|kewt-$(VERSION)/|" *
+
+srpm: dist
+ $(eval VERSION := $(shell git describe --tags --always | sed 's/^v//;s/-/./g'))
+ sed -e "s/VERSION_PLACEHOLDER/$(VERSION)/g" packaging/fedora/kewt.spec.template > packaging/fedora/kewt.spec
+ rpmbuild -bs --define "_sourcedir $(PWD)" --define "_srcrpmdir $(PWD)" packaging/fedora/kewt.spec
test:
sh tests/test_runner.sh
diff --git a/kewt.sh b/kewt.sh
index 54bab81..f48308f 100755
--- a/kewt.sh
+++ b/kewt.sh
@@ -45,6 +45,8 @@ positional_count=0
watch_mode="false"
serve_mode="false"
serve_port=""
+draft_mode="false"
+dry_run_mode="false"
while [ $# -gt 0 ]; do
case "$1" in
@@ -89,6 +91,8 @@ _kewt() {
'--to[Output directory]:directory:_directories'
'(-w --watch)'{-w,--watch}'[Watch for file changes and rebuild automatically]'
'(-s --serve)'{-s,--serve}'[Start a local HTTP server after building]::port:'
+ '(-d --draft)'{-d,--draft}'[Include draft pages in the build]'
+ '(-)--dry-run[Show what would be built without writing any files]'
)
_arguments -S -C $args '*: :_directories'
@@ -141,6 +145,12 @@ EOFCOMPS
shift
fi
;;
+ --draft|-d)
+ draft_mode="true"
+ ;;
+ --dry-run)
+ dry_run_mode="true"
+ ;;
--*)
die "Unknown option: $1"
;;
@@ -201,10 +211,24 @@ refresh_build_context
if [ "$clean_mode" = "true" ]; then
[ -d "$out" ] && rm -rf "$out"
fi
+
+if [ "$dry_run_mode" = "true" ]; then
+ dry_run_out="$KEWT_TMPDIR/dry_run_out"
+ mkdir -p "$dry_run_out"
+ out="$dry_run_out"
+fi
+
mkdir -p "$out"
build_site
+if [ "$dry_run_mode" = "true" ]; then
+ echo ""
+ echo "Dry run complete. Files that would be generated:"
+ find "$dry_run_out" -type f | sed "s|^$dry_run_out/||" | sort
+ exit 0
+fi
+
if [ "$serve_mode" = "true" ]; then
port="${serve_port:-8000}"
if command -v python3 >/dev/null 2>&1; then
diff --git a/lib/builder.sh b/lib/builder.sh
index 565aeb9..9171c7d 100644
--- a/lib/builder.sh
+++ b/lib/builder.sh
@@ -48,8 +48,10 @@ build_dir_entries_list() {
elif [ "${entry%.md}" != "$entry" ]; then
entry_rel_path="${entry#"$src"/}"
load_manifest_entry "$entry_rel_path" || continue
+ if [ "${draft_mode:-false}" != "true" ]; then
+ [ "$manifest_draft" = "true" ] && continue
+ fi
label="${name%.md}"
- [ "$manifest_draft" = "true" ] && continue
post_h="$manifest_title"
@@ -373,7 +375,9 @@ build_files() {
if [ "${file%.md}" != "$file" ] && [ "$is_preserved" -eq 0 ]; then
load_manifest_entry "$rel_path" || continue
- [ "$manifest_draft" = "true" ] && continue
+ if [ "${draft_mode:-false}" != "true" ]; then
+ [ "$manifest_draft" = "true" ] && 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
@@ -476,8 +480,15 @@ build_feed() {
post_url="$base_url_feed$manifest_url"
pub_date=$(format_rfc2822_utc "$post_date" "$post_time")
- printf ' - \n %s\n %s\n %s\n %s\n
\n' \
- "$feed_post_title" "$post_url" "$post_url" "$pub_date" >> "$feed_path"
+ if [ "$feed_full_content" = "true" ]; then
+ feed_content_file="$src/$post_rel_path"
+ feed_content_html=$(ENABLE_HEADER_LINKS="false" CUSTOM_ADMONITIONS="" MARKDOWN_SITE_ROOT="$src" MARKDOWN_FALLBACK_FILE="$script_dir/styles/$style.css" sh "$script_dir/markdown.sh" "$feed_content_file" | sed 's/\</g; s/>/\>/g')
+ printf ' - \n %s\n %s\n %s\n %s\n %s\n
\n' \
+ "$feed_post_title" "$post_url" "$post_url" "$pub_date" "$feed_content_html" >> "$feed_path"
+ else
+ printf ' - \n %s\n %s\n %s\n %s\n
\n' \
+ "$feed_post_title" "$post_url" "$post_url" "$pub_date" >> "$feed_path"
+ fi
done
printf ' \n\n' >> "$feed_path"
@@ -606,12 +617,24 @@ build_tags() {
}
build_error_page() {
- [ -n "$error_page" ] && [ ! -f "$out/$error_page" ] || return
+ [ -n "$error_page" ] || return
- temp_404="$KEWT_TMPDIR/404_gen.md"
- printf '# 404 - Not Found\n\nThe requested page could not be found.\n' > "$temp_404"
- render_markdown "$temp_404" "false" "/$error_page" > "$out/$error_page"
- rm -f "$temp_404"
+ error_base="${error_page%.html}"
+ error_md="$src/${error_base}.md"
+
+ if [ -f "$error_md" ]; then
+ if needs_rebuild "$error_md" "$out/$error_page"; then
+ is_home="false"
+ current_url="/$error_page"
+ parse_frontmatter "$error_md"
+ render_markdown "$error_md" "$is_home" "/$error_page" > "$out/$error_page"
+ fi
+ elif [ ! -f "$out/$error_page" ]; then
+ temp_404="$KEWT_TMPDIR/404_gen.md"
+ printf '# 404 - Not Found\n\nThe requested page could not be found.\n' > "$temp_404"
+ render_markdown "$temp_404" "false" "/$error_page" > "$out/$error_page"
+ rm -f "$temp_404"
+ fi
}
build_site() {
diff --git a/lib/commands.sh b/lib/commands.sh
index 5e2fde1..79dc688 100644
--- a/lib/commands.sh
+++ b/lib/commands.sh
@@ -22,6 +22,8 @@ Options:
--post Create a new empty post file in the configured posts_dir with current date and time as name
--generate-template [path] Generate a new template file at (default: template.html)
--version Show version information.
+ --draft, -d Include draft pages in the build.
+ --dry-run Show what would be built without writing any files.
--from Source directory (default: site)
--to Output directory (default: out)
--watch, -w Watch for file changes and rebuild automatically.
diff --git a/lib/config.sh b/lib/config.sh
index ca386e7..d3a6e95 100644
--- a/lib/config.sh
+++ b/lib/config.sh
@@ -34,7 +34,8 @@ tags_dir = "tags"
generate_search = false
search_in_footer = false
search_in_header = false
-include_cw_pages_in_search = false'
+include_cw_pages_in_search = false
+feed_full_content = false'
DEFAULT_TMPL='
@@ -129,6 +130,7 @@ _load_conf_line() {
search_in_footer) search_in_footer="$_lc_val" ;;
search_in_header) search_in_header="$_lc_val" ;;
include_cw_pages_in_search) include_cw_pages_in_search="$_lc_val" ;;
+ feed_full_content) feed_full_content="$_lc_val" ;;
esac
}
diff --git a/lib/manifest.sh b/lib/manifest.sh
index 16b980b..eb46b9f 100644
--- a/lib/manifest.sh
+++ b/lib/manifest.sh
@@ -187,8 +187,10 @@ build_markdown_manifest() {
eval "find \"$src\" \( $IGNORE_ARGS -o $HIDE_ARGS -o $PRESERVE_ARGS \) -prune -o -name \"*.md\" -print" | sort | while IFS= read -r visible_file; do
visible_rel_path="${visible_file#"$src"/}"
load_manifest_entry "$visible_rel_path" || continue
- [ "$manifest_draft" = "true" ] && continue
- manifest_dir_hidden_by_draft_index "$manifest_dir_rel" && continue
+ if [ "${draft_mode:-false}" != "true" ]; then
+ [ "$manifest_draft" = "true" ] && continue
+ manifest_dir_hidden_by_draft_index "$manifest_dir_rel" && continue
+ fi
printf '%s\n' "$visible_rel_path" >> "$manifest_visible_list"
done
}
diff --git a/packaging/AUR/.SRCINFO.git b/packaging/AUR/.SRCINFO.git
index e4272d4..10e9d37 100644
--- a/packaging/AUR/.SRCINFO.git
+++ b/packaging/AUR/.SRCINFO.git
@@ -1,12 +1,13 @@
pkgbase = kewt-git
pkgdesc = A minimalist, 100% POSIX, static site generator inspired by werc and kew
- pkgver = r0.0000001
+ pkgver = r0.0000002
pkgrel = 1
url = https://kewt.krzak.org
arch = any
license = ISC
makedepends = git
depends = sh
+ optdepends = bash-completion: bash shell completions
provides = kewt
conflicts = kewt
conflicts = kewt-bin
diff --git a/packaging/AUR/.SRCINFO.template b/packaging/AUR/.SRCINFO.template
index 2c953b4..7d75362 100644
--- a/packaging/AUR/.SRCINFO.template
+++ b/packaging/AUR/.SRCINFO.template
@@ -6,10 +6,13 @@ pkgbase = kewt-bin
arch = any
license = ISC
depends = sh
+ optdepends = bash-completion: bash shell completions
provides = kewt
conflicts = kewt
conflicts = kewt-git
source = kewt-bin-VERSION_PLACEHOLDER.sh::https://git.krzak.org/N0VA/kewt/releases/download/vVERSION_PLACEHOLDER/kewt
+ source = kewt-bin-VERSION_PLACEHOLDER.bash::https://git.krzak.org/N0VA/kewt/releases/download/vVERSION_PLACEHOLDER/kewt.bash
sha256sums = SHA256SUM_PLACEHOLDER
+ sha256sums = SKIP
pkgname = kewt-bin
diff --git a/packaging/AUR/PKGBUILD.git b/packaging/AUR/PKGBUILD.git
index fe8b041..1e6312e 100644
--- a/packaging/AUR/PKGBUILD.git
+++ b/packaging/AUR/PKGBUILD.git
@@ -1,6 +1,6 @@
# Maintainer: n0va
pkgname=kewt-git
-pkgver=r0.0000001
+pkgver=r0.0000002
pkgrel=1
pkgdesc="A minimalist, 100% POSIX, static site generator inspired by werc and kew"
arch=('any')
@@ -28,4 +28,5 @@ package() {
install -Dm755 kewt "${pkgdir}/usr/bin/kewt"
install -d "${pkgdir}/usr/share/zsh/site-functions"
"${pkgdir}/usr/bin/kewt" --dump-zsh-completions > "${pkgdir}/usr/share/zsh/site-functions/_kewt"
+ install -Dm644 packaging/bash/kewt.bash "${pkgdir}/usr/share/bash-completion/completions/kewt"
}
diff --git a/packaging/AUR/PKGBUILD.template b/packaging/AUR/PKGBUILD.template
index 6d7f6bf..b368771 100644
--- a/packaging/AUR/PKGBUILD.template
+++ b/packaging/AUR/PKGBUILD.template
@@ -9,8 +9,9 @@ license=('ISC')
depends=('sh')
provides=('kewt')
conflicts=('kewt' 'kewt-git')
-source=("${pkgname}-${pkgver}.sh::https://git.krzak.org/N0VA/kewt/releases/download/v${pkgver}/kewt")
-sha256sums=('SHA256SUM_PLACEHOLDER')
+source=("${pkgname}-${pkgver}.sh::https://git.krzak.org/N0VA/kewt/releases/download/v${pkgver}/kewt"
+ "${pkgname}-${pkgver}.bash::https://git.krzak.org/N0VA/kewt/releases/download/v${pkgver}/kewt.bash")
+sha256sums=('SHA256SUM_PLACEHOLDER' 'SKIP')
build() {
chmod +x "${srcdir}/${pkgname}-${pkgver}.sh"
@@ -20,4 +21,5 @@ package() {
install -Dm755 "${srcdir}/${pkgname}-${pkgver}.sh" "${pkgdir}/usr/bin/kewt"
install -d "${pkgdir}/usr/share/zsh/site-functions"
"${pkgdir}/usr/bin/kewt" --dump-zsh-completions > "${pkgdir}/usr/share/zsh/site-functions/_kewt"
+ install -Dm644 "${srcdir}/${pkgname}-${pkgver}.bash" "${pkgdir}/usr/share/bash-completion/completions/kewt"
}
diff --git a/packaging/bash/kewt.bash b/packaging/bash/kewt.bash
new file mode 100644
index 0000000..a8c87bf
--- /dev/null
+++ b/packaging/bash/kewt.bash
@@ -0,0 +1,37 @@
+_kewt() {
+ local cur prev opts
+ COMPREPLY=()
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
+
+ opts="--help --new --init --clean --no-clean --update --post --generate-template --version --from --to --watch -w --serve -s --draft --dry-run"
+
+ case "$prev" in
+ --from|--to)
+ COMPREPLY=$(compgen -d -- "$cur")
+ return 0
+ ;;
+ --serve|-s)
+ COMPREPLY=()
+ return 0
+ ;;
+ --new|--init|--update)
+ COMPREPLY=$(compgen -d -- "$cur")
+ return 0
+ ;;
+ --generate-template)
+ COMPREPLY=$(compgen -f -- "$cur")
+ return 0
+ ;;
+ esac
+
+ if [[ "$cur" == -* ]]; then
+ COMPREPLY=$(compgen -W "$opts" -- "$cur")
+ return 0
+ fi
+
+ COMPREPLY=$(compgen -d -- "$cur")
+ return 0
+}
+
+complete -F _kewt kewt
diff --git a/packaging/fedora/kewt.spec.template b/packaging/fedora/kewt.spec.template
new file mode 100644
index 0000000..9ce87e2
--- /dev/null
+++ b/packaging/fedora/kewt.spec.template
@@ -0,0 +1,41 @@
+Name: kewt
+Version: VERSION_PLACEHOLDER
+Release: 1%{?dist}
+Summary: A minimalist, 100% POSIX, static site generator inspired by werc
+
+License: ISC
+URL: https://kewt.krzak.org
+Source0: https://git.krzak.org/N0VA/kewt/archive/v%{version}.tar.gz
+
+BuildArch: noarch
+BuildRequires: make
+Requires: sh
+Requires: findutils
+Requires: grep
+Requires: sed
+Requires: gawk
+Recommends: python3
+Recommends: bash-completion
+
+%description
+A minimalist, 100% POSIX, static site generator inspired by werc and kew
+
+%prep
+%autosetup
+
+%build
+%make_build
+
+%install
+%make_install PREFIX=%{_prefix} BINDIR=%{_bindir} ZSHCOMPDIR=%{_datadir}/zsh/site-functions BASHCOMPDIR=%{_datadir}/bash-completion/completions
+
+%files
+%license LICENSE
+%doc README.md
+%{_bindir}/kewt
+%{_datadir}/zsh/site-functions/_kewt
+%{_datadir}/bash-completion/completions/kewt
+
+%changelog
+* Mon May 20 2024 n0va - VERSION_PLACEHOLDER-1
+- Initial package for Fedora
diff --git a/packaging/homebrew/kewt.rb.template b/packaging/homebrew/kewt.rb.template
index 1ef7f88..77da40a 100644
--- a/packaging/homebrew/kewt.rb.template
+++ b/packaging/homebrew/kewt.rb.template
@@ -9,7 +9,7 @@ class Kewt < Formula
def install
bin.install "kewt"
chmod 0755, bin/"kewt"
- generate_completions_from_executable(bin/"kewt", "--dump-zsh-completions", shells: [:zsh])
+ generate_completions_from_executable(bin/"kewt", "--dump-zsh-completions", shells: [:zsh, :bash])
end
test do
diff --git a/packaging/zsh/_kewt b/packaging/zsh/_kewt
index 4edf5d2..b2d9d8b 100644
--- a/packaging/zsh/_kewt
+++ b/packaging/zsh/_kewt
@@ -17,6 +17,8 @@ _kewt() {
'--to[Output directory]:directory:_directories'
'(-w --watch)'{-w,--watch}'[Watch for file changes and rebuild automatically]'
'(-s --serve)'{-s,--serve}'[Start a local HTTP server after building]::port:'
+ '(-d --draft)'{-d,--draft}'[Include draft pages in the build]'
+ '(-)--dry-run[Show what would be built without writing any files]'
)
_arguments -S -C $args '*: :_directories'
diff --git a/site/docs/installation.md b/site/docs/installation.md
index b419092..798bd3f 100644
--- a/site/docs/installation.md
+++ b/site/docs/installation.md
@@ -38,6 +38,14 @@ sudo make install
brew tap n0va-bot/tap
brew install kewt
```
+
+### Fedora
+
+```sh
+sudo dnf copr enable n0va-bot/kewt
+sudo dnf install kewt
+```
+
### bpkg
```sh
diff --git a/tests/test_builder.sh b/tests/test_builder.sh
new file mode 100644
index 0000000..8590777
--- /dev/null
+++ b/tests/test_builder.sh
@@ -0,0 +1,185 @@
+test_needs_rebuild_no_output() {
+ . "$project_dir/lib/config.sh"
+ . "$project_dir/lib/runtime.sh"
+ . "$project_dir/lib/builder.sh"
+
+ tmpdir="${TMPDIR:-/tmp}/kewt_test.$$"
+ mkdir -p "$tmpdir"
+ echo "test" > "$tmpdir/src.md"
+
+ needs_rebuild "$tmpdir/src.md" "$tmpdir/out.html"
+ result=$?
+ assert_eq "0" "$result" "rebuild when output missing"
+
+ rm -rf "$tmpdir"
+}
+
+test_needs_rebuild_output_newer() {
+ . "$project_dir/lib/config.sh"
+ . "$project_dir/lib/runtime.sh"
+ . "$project_dir/lib/builder.sh"
+
+ tmpdir="${TMPDIR:-/tmp}/kewt_test.$$"
+ mkdir -p "$tmpdir"
+ echo "test" > "$tmpdir/src.md"
+ sleep 1
+ echo "test" > "$tmpdir/out.html"
+
+ needs_rebuild "$tmpdir/src.md" "$tmpdir/out.html"
+ result=$?
+ assert_eq "1" "$result" "no rebuild when output newer"
+
+ rm -rf "$tmpdir"
+}
+
+test_needs_rebuild_source_newer() {
+ . "$project_dir/lib/config.sh"
+ . "$project_dir/lib/runtime.sh"
+ . "$project_dir/lib/builder.sh"
+
+ tmpdir="${TMPDIR:-/tmp}/kewt_test.$$"
+ mkdir -p "$tmpdir"
+ echo "old" > "$tmpdir/out.html"
+ sleep 1
+ echo "new" > "$tmpdir/src.md"
+
+ needs_rebuild "$tmpdir/src.md" "$tmpdir/out.html"
+ result=$?
+ assert_eq "0" "$result" "rebuild when source newer"
+
+ rm -rf "$tmpdir"
+}
+
+test_escape_html_text() {
+ . "$project_dir/lib/generator.sh"
+
+ result=$(escape_html_text "