Compare commits
101 Commits
9095f0db44
...
v1.6.3
| Author | SHA1 | Date | |
|---|---|---|---|
| c8df9a3da9 | |||
| b8e6c3afa8 | |||
| 3105c83290 | |||
| 206d9a650d | |||
| 105dce7d40 | |||
| 21dc56aa6d | |||
| 7df5daaa6c | |||
| 4f74dd5fe0 | |||
| 0751849492 | |||
| 009877ae76 | |||
| 69bd5832e7 | |||
| b525a5f1c2 | |||
| 2a03859390 | |||
| b65c4c6665 | |||
| 90d8e25b70 | |||
| 8814c12480 | |||
| 0c0f249226 | |||
| b29a5274e1 | |||
| 00f4bbb5f0 | |||
| 7fe204f9f9 | |||
| 81d3caff45 | |||
| 100979d28a | |||
| d35ddcf487 | |||
| aac9198878 | |||
| 99e1f5dd24 | |||
| 3970c6eb47 | |||
| 3075719963 | |||
| 0379d38234 | |||
| b22897135e | |||
| 185cf769c3 | |||
| b2acd26660 | |||
| 4069bafd52 | |||
| 9dbd41392e | |||
| 35eac48dcd | |||
| ef16ed4c88 | |||
| 30b7681234 | |||
| 13b6106efd | |||
| 831b081fc7 | |||
| fde423a32b | |||
| 55a82f75a9 | |||
| f85abd43c4 | |||
| 0f66ebf52a | |||
| 55a515ccd5 | |||
| de8cbefb8e | |||
| cc7fee573f | |||
| 137be9579a | |||
| 5afd0170e5 | |||
| 5a2053cfb4 | |||
| 2fc3d6fc6f | |||
| c5a9355871 | |||
| b8cd129c47 | |||
| 5e033a65e7 | |||
| 5bf2e2abe5 | |||
| 3a2056ff8f | |||
| fd829a3f22 | |||
| bad02decba | |||
| 2aef6ec4a1 | |||
| 78eac182dc | |||
| b7382a20ab | |||
| d8cf07ee2a | |||
| 76e2ae0117 | |||
| 2e331b5d9a | |||
| 9f5d1089a2 | |||
| 9ccba8fd4e | |||
| 95679abd85 | |||
| 8b1e793510 | |||
| 19f96553d9 | |||
| 1a7525a857 | |||
| e7d90d18e8 | |||
| 4019d2721d | |||
| b58604a4cf | |||
| 99e805b180 | |||
| 62075dea4a | |||
| 7afd041e53 | |||
| 64d08a0de3 | |||
| dd18bc3367 | |||
| f89661c1a5 | |||
| e0a3b66fa9 | |||
| 4e6c9dbeb5 | |||
| af453ca2ec | |||
| cea84de242 | |||
| ad1ec9c2e3 | |||
| fa3b5592da | |||
| 722d687afe | |||
| 77c0b29b4c | |||
| 9ae965662c | |||
| 9ced2af562 | |||
| f407b1c4af | |||
| bee12ce8c1 | |||
| 696dc1c142 | |||
| c2416e731b | |||
| 01fd55001a | |||
| b1f69673d1 | |||
| 6418b64672 | |||
| e3cc1c1688 | |||
| 1f5d63c035 | |||
| 6e445d5223 | |||
| da606918a2 | |||
| 7d4de6d07a | |||
| f580ed9cab | |||
| 3258616282 |
14
.gitattributes
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
|
*.sh text eol=lf
|
||||||
|
*.awk text eol=lf
|
||||||
|
*.css text eol=lf
|
||||||
|
*.html text eol=lf
|
||||||
|
*.js text eol=lf
|
||||||
|
*.md text eol=lf
|
||||||
|
*.conf text eol=lf
|
||||||
|
*.json text eol=lf
|
||||||
|
*.xml text eol=lf
|
||||||
|
*.png binary
|
||||||
|
*.gif binary
|
||||||
|
*.svg text eol=lf
|
||||||
|
*.ico binary
|
||||||
42
.gitea/workflows/deploy-website.yml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Deploy Website
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-website:
|
||||||
|
runs-on: local
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Ensure deploy tools are available
|
||||||
|
run: |
|
||||||
|
if ! command -v ssh >/dev/null 2>&1 || ! command -v rsync >/dev/null 2>&1 || ! command -v ssh-keyscan >/dev/null 2>&1; then
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y openssh-client rsync
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build website
|
||||||
|
run: |
|
||||||
|
sh kewt.sh --from site --to out
|
||||||
|
|
||||||
|
- name: Setup SSH
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
printf '%s\n' "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
|
||||||
|
chmod 600 ~/.ssh/id_rsa
|
||||||
|
|
||||||
|
REMOTE_HOST_VAL="${{ secrets.REMOTE_HOST }}"
|
||||||
|
REMOTE_ADDR=$(printf '%s\n' "$REMOTE_HOST_VAL" | awk -F@ '{print $NF}')
|
||||||
|
|
||||||
|
ssh-keyscan -H "$REMOTE_ADDR" >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
- name: Deploy website
|
||||||
|
run: |
|
||||||
|
rsync -az --delete \
|
||||||
|
-e "ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no" \
|
||||||
|
out/ "${{ secrets.REMOTE_HOST }}:/var/www/kewt.krzak.org/"
|
||||||
38
.gitea/workflows/publish-aur-git.yml
Normal 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 }}"
|
||||||
137
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
name: Release Standalone Builder
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
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/}"
|
||||||
|
|
||||||
|
# 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-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
|
||||||
|
|
||||||
|
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
|
||||||
|
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 }}"
|
||||||
|
|
||||||
|
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
|
||||||
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
out/
|
out/
|
||||||
|
kewt
|
||||||
34
LICENSE
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
ISC License
|
||||||
|
|
||||||
|
Copyright 2026 N0\A
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software
|
||||||
|
for any purpose with or without fee is hereby granted, provided
|
||||||
|
that the above copyright notice and this permission notice appear
|
||||||
|
in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||||
|
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
|
||||||
|
OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||||
|
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
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
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
23
Makefile
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
PREFIX ?= /usr/local
|
||||||
|
BINDIR = $(PREFIX)/bin
|
||||||
|
ZSHCOMPDIR ?= $(PREFIX)/share/zsh/site-functions
|
||||||
|
|
||||||
|
all: kewt
|
||||||
|
|
||||||
|
kewt:
|
||||||
|
./tools/build-standalone.sh
|
||||||
|
|
||||||
|
install: kewt
|
||||||
|
install -d $(DESTDIR)$(BINDIR)
|
||||||
|
install -m 755 kewt $(DESTDIR)$(BINDIR)/kewt
|
||||||
|
install -d $(DESTDIR)$(ZSHCOMPDIR)
|
||||||
|
install -m 644 packaging/zsh/_kewt $(DESTDIR)$(ZSHCOMPDIR)/_kewt
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
rm -f $(DESTDIR)$(BINDIR)/kewt
|
||||||
|
rm -f $(DESTDIR)$(ZSHCOMPDIR)/_kewt
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f kewt
|
||||||
|
|
||||||
|
.PHONY: all install uninstall clean
|
||||||
77
README.md
@@ -1,78 +1,21 @@
|
|||||||
# _kewt_
|
# 
|
||||||
### Pronounced "cute"
|
### Pronounced "cute"
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
# [Go to the website](https://kewt.krzak.org)
|
# [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)_
|
_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/docs/installation)
|
||||||
|
|
||||||
- No dependencies
|
## Contributing
|
||||||
- Supports many embed types
|
|
||||||
- Automatic css variable replacement for older browsers
|
|
||||||
- Automatic inlining and embedding of many filetypes with `\![link]` or `\`
|
|
||||||
- Inline html support
|
|
||||||
- MFM `$font` and `\<plain>` tags
|
|
||||||
- Admonition support (that's what the blocks like the warning block below are called)
|
|
||||||
|
|
||||||
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`.
|
||||||
|
|
||||||
## Usage
|
## License
|
||||||
|
|
||||||
```sh
|
ISC
|
||||||
./kewt.sh --help
|
|
||||||
./kewt.sh --new [title]
|
|
||||||
./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`.
|
|
||||||
|
|
||||||
## site.conf
|
|
||||||
|
|
||||||
```conf
|
|
||||||
title = "kewt"
|
|
||||||
style = "kewt"
|
|
||||||
dir_indexes = true
|
|
||||||
single_file_index = true
|
|
||||||
flatten = false
|
|
||||||
footer = "made with <a href="https://kewt.krzak.org">kewt</a>"
|
|
||||||
logo = ""
|
|
||||||
display_logo = false
|
|
||||||
display_title = true
|
|
||||||
logo_as_favicon = true
|
|
||||||
favicon = ""
|
|
||||||
```
|
|
||||||
|
|
||||||
- `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
|
|
||||||
- `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)
|
|
||||||
|
|
||||||
## 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>`
|
|
||||||
- `\` works the same, with `alt` used for images
|
|
||||||
- `\!![]` and `\!` force inline local file contents
|
|
||||||
|
|
||||||
## Credits
|
|
||||||
|
|
||||||
- Markdown to html conversion based on [markdown.bash](https://github.com/chadbraunduin/markdown.bash) by [chadbraunduin](https://github.com/chadbraunduin)
|
|
||||||
- Default css style and html template based on _[kew](https://github.com/uint23/kew)_ by [uint23](https://github.com/uint23)
|
|
||||||
|
|
||||||
# Warning
|
|
||||||
>![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...
|
|
||||||
|
|||||||
14
awk/blockquote.awk
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
BEGIN { in_bq = 0 }
|
||||||
|
/^>[[:space:]]?/ {
|
||||||
|
if (!in_bq) { print "<blockquote>"; in_bq = 1 }
|
||||||
|
sub(/^>[[:space:]]?/, "", $0)
|
||||||
|
print $0
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if (in_bq) { print "</blockquote>"; in_bq = 0 }
|
||||||
|
print
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
if (in_bq) print "</blockquote>"
|
||||||
|
}
|
||||||
58
awk/blockquote_to_admonition.awk
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
function cap(s) { return toupper(substr(s, 1, 1)) tolower(substr(s, 2)) }
|
||||||
|
BEGIN { count = 0 }
|
||||||
|
{ lines[++count] = $0 }
|
||||||
|
END {
|
||||||
|
i = 1
|
||||||
|
while (i <= count) {
|
||||||
|
if (lines[i] == "<blockquote>") {
|
||||||
|
j = i + 1
|
||||||
|
while (j <= count && lines[j] != "</blockquote>") j++
|
||||||
|
if (j <= count) {
|
||||||
|
first = ""
|
||||||
|
first_idx = 0
|
||||||
|
for (k = i + 1; k < j; k++) {
|
||||||
|
if (lines[k] != "") {
|
||||||
|
first = lines[k]
|
||||||
|
first_idx = k
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (first ~ /^\[![A-Za-z]+\]$/) {
|
||||||
|
kind = first
|
||||||
|
sub(/^\[!/, "", kind)
|
||||||
|
sub(/\]$/, "", kind)
|
||||||
|
lkind = tolower(kind)
|
||||||
|
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
|
||||||
|
for (k = first_idx + 1; k < j; k++) {
|
||||||
|
if (lines[k] != "") {
|
||||||
|
print "<p>" lines[k] "</p>"
|
||||||
|
has_body = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!has_body) print "<p></p>"
|
||||||
|
print "</div>"
|
||||||
|
i = j + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print lines[i]
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
5
awk/breaks.awk
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
if ($0 == "" && prev == "") print "<br />"
|
||||||
|
else print $0
|
||||||
|
prev = $0
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
|
src = ENVIRON["AWK_SRC"]
|
||||||
slen = length(src)
|
slen = length(src)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
62
awk/definition_lists.awk
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
BEGIN {
|
||||||
|
in_dl = 0
|
||||||
|
has_prev = 0
|
||||||
|
prev_line = ""
|
||||||
|
in_pre = 0
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if ($0 ~ /^<pre>/) {
|
||||||
|
if (!in_pre) in_pre = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_pre && $0 ~ /^:[ \t]+[^ \t]/) {
|
||||||
|
if (!in_dl) {
|
||||||
|
in_dl = 1
|
||||||
|
print "<dl>"
|
||||||
|
print "<dt>" prev_line "</dt>"
|
||||||
|
has_prev = 0
|
||||||
|
} else {
|
||||||
|
if (has_prev && prev_line != "") {
|
||||||
|
print "<dt>" prev_line "</dt>"
|
||||||
|
has_prev = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def_text = $0
|
||||||
|
sub(/^:[ \t]+/, "", def_text)
|
||||||
|
print "<dd>" def_text "</dd>"
|
||||||
|
|
||||||
|
if ($0 ~ /<\/pre>/) {
|
||||||
|
if (in_pre) in_pre = 0
|
||||||
|
}
|
||||||
|
next
|
||||||
|
} else {
|
||||||
|
if (in_dl) {
|
||||||
|
if ($0 == "") {
|
||||||
|
# End of definition list
|
||||||
|
print "</dl>"
|
||||||
|
in_dl = 0
|
||||||
|
print ""
|
||||||
|
has_prev = 0
|
||||||
|
next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (has_prev) {
|
||||||
|
print prev_line
|
||||||
|
}
|
||||||
|
prev_line = $0
|
||||||
|
has_prev = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($0 ~ /<\/pre>/) {
|
||||||
|
if (in_pre) in_pre = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
if (in_dl) {
|
||||||
|
print "</dl>"
|
||||||
|
} else {
|
||||||
|
if (has_prev) {
|
||||||
|
print prev_line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
awk/emoji.awk
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
BEGIN {
|
||||||
|
if (emoji_file == "") {
|
||||||
|
emoji_file = "emoji.tsv"
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((getline line < emoji_file) > 0) {
|
||||||
|
split(line, parts, "\t")
|
||||||
|
if (length(parts[1]) > 0) {
|
||||||
|
map[parts[1]] = parts[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(emoji_file)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if ($0 ~ /<pre>/) in_pre = 1
|
||||||
|
if (!in_pre) {
|
||||||
|
code_count = 0
|
||||||
|
line = $0
|
||||||
|
out = ""
|
||||||
|
|
||||||
|
while (match(line, /<code>[^<]*<\/code>/)) {
|
||||||
|
code_count++
|
||||||
|
code_store[code_count] = substr(line, RSTART, RLENGTH)
|
||||||
|
out = out substr(line, 1, RSTART - 1) "\034EC" code_count "\034"
|
||||||
|
line = substr(line, RSTART + RLENGTH)
|
||||||
|
}
|
||||||
|
out = out line
|
||||||
|
|
||||||
|
line = out
|
||||||
|
out = ""
|
||||||
|
while (match(line, /:[A-Za-z0-9_+\-]+:/)) {
|
||||||
|
token = substr(line, RSTART, RLENGTH)
|
||||||
|
out = out substr(line, 1, RSTART - 1)
|
||||||
|
if (token in map) {
|
||||||
|
out = out map[token]
|
||||||
|
} else {
|
||||||
|
out = out token
|
||||||
|
}
|
||||||
|
line = substr(line, RSTART + RLENGTH)
|
||||||
|
}
|
||||||
|
out = out line
|
||||||
|
|
||||||
|
for (i = 1; i <= code_count; i++) {
|
||||||
|
gsub("\034EC" i "\034", code_store[i], out)
|
||||||
|
delete code_store[i]
|
||||||
|
}
|
||||||
|
$0 = out
|
||||||
|
}
|
||||||
|
if ($0 ~ /<\/pre>/) in_pre = 0
|
||||||
|
print
|
||||||
|
}
|
||||||
827
awk/emoji.tsv
Normal file
@@ -0,0 +1,827 @@
|
|||||||
|
:+1: 👍
|
||||||
|
:100: 💯
|
||||||
|
:1234: 🔢
|
||||||
|
:8ball: 🎱
|
||||||
|
:a: 🅰️
|
||||||
|
:ab: 🆎
|
||||||
|
:abc: 🔤
|
||||||
|
:abcd: 🔡
|
||||||
|
:accept: 🉑
|
||||||
|
:aerial_tramway: 🚡
|
||||||
|
:airplane: ✈️
|
||||||
|
:alarm_clock: ⏰
|
||||||
|
:alien: 👽
|
||||||
|
:ambulance: 🚑
|
||||||
|
:anchor: ⚓
|
||||||
|
:angel: 👼
|
||||||
|
:anger: 💢
|
||||||
|
:angry: 😠
|
||||||
|
:anguished: 😧
|
||||||
|
:ant: 🐜
|
||||||
|
:apple: 🍎
|
||||||
|
:aquarius: ♒
|
||||||
|
:aries: ♈
|
||||||
|
:arrow_backward: ◀️
|
||||||
|
:arrow_double_down: ⏬
|
||||||
|
:arrow_double_up: ⏫
|
||||||
|
:arrow_down: ⬇️
|
||||||
|
:arrow_down_small: 🔽
|
||||||
|
:arrow_forward: ▶️
|
||||||
|
:arrow_heading_down: ⤵️
|
||||||
|
:arrow_heading_up: ⤴️
|
||||||
|
:arrow_left: ⬅️
|
||||||
|
:arrow_lower_left: ↙️
|
||||||
|
:arrow_lower_right: ↘️
|
||||||
|
:arrow_right: ➡️
|
||||||
|
:arrow_right_hook: ↪️
|
||||||
|
:arrow_up: ⬆️
|
||||||
|
:arrow_up_down: ↕️
|
||||||
|
:arrow_up_small: 🔼
|
||||||
|
:arrow_upper_left: ↖️
|
||||||
|
:arrow_upper_right: ↗️
|
||||||
|
:arrows_clockwise: 🔃
|
||||||
|
:arrows_counterclockwise: 🔄
|
||||||
|
:art: 🎨
|
||||||
|
:articulated_lorry: 🚛
|
||||||
|
:astonished: 😲
|
||||||
|
:atm: 🏧
|
||||||
|
:b: 🅱️
|
||||||
|
:baby: 👶
|
||||||
|
:baby_bottle: 🍼
|
||||||
|
:baby_chick: 🐤
|
||||||
|
:baby_symbol: 🚼
|
||||||
|
:baggage_claim: 🛄
|
||||||
|
:balloon: 🎈
|
||||||
|
:ballot_box_with_check: ☑️
|
||||||
|
:bamboo: 🎍
|
||||||
|
:banana: 🍌
|
||||||
|
:bangbang: ‼️
|
||||||
|
:bank: 🏦
|
||||||
|
:bar_chart: 📊
|
||||||
|
:barber: 💈
|
||||||
|
:baseball: ⚾
|
||||||
|
:basketball: 🏀
|
||||||
|
:bath: 🛀
|
||||||
|
:bathtub: 🛁
|
||||||
|
:battery: 🔋
|
||||||
|
:bear: 🐻
|
||||||
|
:beer: 🍺
|
||||||
|
:beers: 🍻
|
||||||
|
:beetle: 🪲
|
||||||
|
:beginner: 🔰
|
||||||
|
:bell: 🔔
|
||||||
|
:bento: 🍱
|
||||||
|
:bicyclist: 🚴
|
||||||
|
:bike: 🚲
|
||||||
|
:bikini: 👙
|
||||||
|
:bird: 🐦
|
||||||
|
:birthday: 🎂
|
||||||
|
:black_circle: ⚫
|
||||||
|
:black_joker: 🃏
|
||||||
|
:black_nib: ✒️
|
||||||
|
:black_square_button: 🔲
|
||||||
|
:blossom: 🌼
|
||||||
|
:blowfish: 🐡
|
||||||
|
:blue_book: 📘
|
||||||
|
:blue_car: 🚙
|
||||||
|
:blue_heart: 💙
|
||||||
|
:blush: 😊
|
||||||
|
:boar: 🐗
|
||||||
|
:boat: ⛵
|
||||||
|
:bomb: 💣
|
||||||
|
:book: 📖
|
||||||
|
:bookmark: 🔖
|
||||||
|
:bookmark_tabs: 📑
|
||||||
|
:books: 📚
|
||||||
|
:boom: 💥
|
||||||
|
:boot: 👢
|
||||||
|
:bouquet: 💐
|
||||||
|
:bow: 🙇
|
||||||
|
:bowling: 🎳
|
||||||
|
:boy: 👦
|
||||||
|
:bread: 🍞
|
||||||
|
:bride_with_veil: 👰♀️
|
||||||
|
:bridge_at_night: 🌉
|
||||||
|
:briefcase: 💼
|
||||||
|
:broken_heart: 💔
|
||||||
|
:bug: 🐛
|
||||||
|
:bulb: 💡
|
||||||
|
:bullettrain_front: 🚅
|
||||||
|
:bullettrain_side: 🚄
|
||||||
|
:bus: 🚌
|
||||||
|
:busstop: 🚏
|
||||||
|
:bust_in_silhouette: 👤
|
||||||
|
:busts_in_silhouette: 👥
|
||||||
|
:cactus: 🌵
|
||||||
|
:cake: 🍰
|
||||||
|
:calendar: 📆
|
||||||
|
:calling: 📲
|
||||||
|
:camel: 🐫
|
||||||
|
:camera: 📷
|
||||||
|
:cancer: ♋
|
||||||
|
:candy: 🍬
|
||||||
|
:capital_abcd: 🔠
|
||||||
|
:capricorn: ♑
|
||||||
|
:car: 🚗
|
||||||
|
:card_index: 📇
|
||||||
|
:carousel_horse: 🎠
|
||||||
|
:cat2: 🐈
|
||||||
|
:cat: 🐱
|
||||||
|
:cd: 💿
|
||||||
|
:chart: 💹
|
||||||
|
:chart_with_downwards_trend: 📉
|
||||||
|
:chart_with_upwards_trend: 📈
|
||||||
|
:checkered_flag: 🏁
|
||||||
|
:cherries: 🍒
|
||||||
|
:cherry_blossom: 🌸
|
||||||
|
:chestnut: 🌰
|
||||||
|
:chicken: 🐔
|
||||||
|
:children_crossing: 🚸
|
||||||
|
:chocolate_bar: 🍫
|
||||||
|
:christmas_tree: 🎄
|
||||||
|
:church: ⛪
|
||||||
|
:cinema: 🎦
|
||||||
|
:circus_tent: 🎪
|
||||||
|
:city_sunrise: 🌇
|
||||||
|
:city_sunset: 🌆
|
||||||
|
:cl: 🆑
|
||||||
|
:clap: 👏
|
||||||
|
:clapper: 🎬
|
||||||
|
:clipboard: 📋
|
||||||
|
:clock1030: 🕥
|
||||||
|
:clock10: 🕙
|
||||||
|
:clock1130: 🕦
|
||||||
|
:clock11: 🕚
|
||||||
|
:clock1230: 🕧
|
||||||
|
:clock12: 🕛
|
||||||
|
:clock130: 🕜
|
||||||
|
:clock1: 🕐
|
||||||
|
:clock230: 🕝
|
||||||
|
:clock2: 🕑
|
||||||
|
:clock330: 🕞
|
||||||
|
:clock3: 🕒
|
||||||
|
:clock430: 🕟
|
||||||
|
:clock4: 🕓
|
||||||
|
:clock530: 🕠
|
||||||
|
:clock5: 🕔
|
||||||
|
:clock630: 🕡
|
||||||
|
:clock6: 🕕
|
||||||
|
:clock730: 🕢
|
||||||
|
:clock7: 🕖
|
||||||
|
:clock830: 🕣
|
||||||
|
:clock8: 🕗
|
||||||
|
:clock930: 🕤
|
||||||
|
:clock9: 🕘
|
||||||
|
:closed_book: 📕
|
||||||
|
:closed_lock_with_key: 🔐
|
||||||
|
:closed_umbrella: 🌂
|
||||||
|
:cloud: ☁️
|
||||||
|
:clubs: ♣️
|
||||||
|
:cn: 🇨🇳
|
||||||
|
:cocktail: 🍸
|
||||||
|
:coffee: ☕
|
||||||
|
:cold_sweat: 😰
|
||||||
|
:computer: 💻
|
||||||
|
:confetti_ball: 🎊
|
||||||
|
:confounded: 😖
|
||||||
|
:confused: 😕
|
||||||
|
:congratulations: ㊗️
|
||||||
|
:construction: 🚧
|
||||||
|
:construction_worker: 👷
|
||||||
|
:convenience_store: 🏪
|
||||||
|
:cookie: 🍪
|
||||||
|
:cool: 🆒
|
||||||
|
:cop: 👮
|
||||||
|
:copyright: ©️
|
||||||
|
:corn: 🌽
|
||||||
|
:couple: 👫
|
||||||
|
:couple_with_heart: 💑
|
||||||
|
:couplekiss: 💏
|
||||||
|
:cow2: 🐄
|
||||||
|
:cow: 🐮
|
||||||
|
:credit_card: 💳
|
||||||
|
:crocodile: 🐊
|
||||||
|
:crossed_flags: 🎌
|
||||||
|
:crown: 👑
|
||||||
|
:cry: 😢
|
||||||
|
:crying_cat_face: 😿
|
||||||
|
:crystal_ball: 🔮
|
||||||
|
:cupid: 💘
|
||||||
|
:curly_loop: ➰
|
||||||
|
:currency_exchange: 💱
|
||||||
|
:curry: 🍛
|
||||||
|
:custard: 🍮
|
||||||
|
:customs: 🛃
|
||||||
|
:cyclone: 🌀
|
||||||
|
:dancer: 💃
|
||||||
|
:dancers: 👯
|
||||||
|
:dango: 🍡
|
||||||
|
:dart: 🎯
|
||||||
|
:dash: 💨
|
||||||
|
:date: 📅
|
||||||
|
:de: 🇩🇪
|
||||||
|
:deciduous_tree: 🌳
|
||||||
|
:department_store: 🏬
|
||||||
|
:diamond_shape_with_a_dot_inside: 💠
|
||||||
|
:diamonds: ♦️
|
||||||
|
:disappointed: 😞
|
||||||
|
:disappointed_relieved: 😥
|
||||||
|
:dizzy: 💫
|
||||||
|
:dizzy_face: 😵
|
||||||
|
:do_not_litter: 🚯
|
||||||
|
:dog2: 🐕
|
||||||
|
:dog: 🐶
|
||||||
|
:dollar: 💵
|
||||||
|
:dolls: 🎎
|
||||||
|
:dolphin: 🐬
|
||||||
|
:door: 🚪
|
||||||
|
:doughnut: 🍩
|
||||||
|
:dragon: 🐉
|
||||||
|
:dragon_face: 🐲
|
||||||
|
:dress: 👗
|
||||||
|
:dromedary_camel: 🐪
|
||||||
|
:droplet: 💧
|
||||||
|
:dvd: 📀
|
||||||
|
:ear: 👂
|
||||||
|
:ear_of_rice: 🌾
|
||||||
|
:earth_africa: 🌍
|
||||||
|
:earth_americas: 🌎
|
||||||
|
:earth_asia: 🌏
|
||||||
|
:egg: 🥚
|
||||||
|
:eggplant: 🍆
|
||||||
|
:eight: 8️⃣
|
||||||
|
:eight_pointed_black_star: ✴️
|
||||||
|
:eight_spoked_asterisk: ✳️
|
||||||
|
:electric_plug: 🔌
|
||||||
|
:elephant: 🐘
|
||||||
|
:email: 📧
|
||||||
|
:end: 🔚
|
||||||
|
:envelope: ✉️
|
||||||
|
:es: 🇪🇸
|
||||||
|
:euro: 💶
|
||||||
|
:european_castle: 🏰
|
||||||
|
:european_post_office: 🏤
|
||||||
|
:evergreen_tree: 🌲
|
||||||
|
:exclamation: ❗
|
||||||
|
:expressionless: 😑
|
||||||
|
:eyeglasses: 👓
|
||||||
|
:eyes: 👀
|
||||||
|
:factory: 🏭
|
||||||
|
:fallen_leaf: 🍂
|
||||||
|
:family: 👪
|
||||||
|
:fast_forward: ⏩
|
||||||
|
:fax: 📠
|
||||||
|
:fearful: 😨
|
||||||
|
:feet: 🐾
|
||||||
|
:ferris_wheel: 🎡
|
||||||
|
:file_folder: 📁
|
||||||
|
:fire: 🔥
|
||||||
|
:fire_engine: 🚒
|
||||||
|
:fireworks: 🎆
|
||||||
|
:first_quarter_moon: 🌓
|
||||||
|
:first_quarter_moon_with_face: 🌛
|
||||||
|
:fish: 🐟
|
||||||
|
:fish_cake: 🍥
|
||||||
|
:fishing_pole_and_fish: 🎣
|
||||||
|
:fist: ✊
|
||||||
|
:five: 5️⃣
|
||||||
|
:flags: 🎏
|
||||||
|
:flashlight: 🔦
|
||||||
|
:floppy_disk: 💾
|
||||||
|
:flower_playing_cards: 🎴
|
||||||
|
:flushed: 😳
|
||||||
|
:foggy: 🌁
|
||||||
|
:football: 🏈
|
||||||
|
:fork_and_knife: 🍴
|
||||||
|
:fountain: ⛲
|
||||||
|
:four: 4️⃣
|
||||||
|
:four_leaf_clover: 🍀
|
||||||
|
:fr: 🇫🇷
|
||||||
|
:free: 🆓
|
||||||
|
:fried_shrimp: 🍤
|
||||||
|
:fries: 🍟
|
||||||
|
:frog: 🐸
|
||||||
|
:frowning: 😦
|
||||||
|
:fuelpump: ⛽
|
||||||
|
:full_moon: 🌕
|
||||||
|
:full_moon_with_face: 🌝
|
||||||
|
:game_die: 🎲
|
||||||
|
:gem: 💎
|
||||||
|
:gemini: ♊
|
||||||
|
:ghost: 👻
|
||||||
|
:gift: 🎁
|
||||||
|
:gift_heart: 💝
|
||||||
|
:girl: 👧
|
||||||
|
:globe_with_meridians: 🌐
|
||||||
|
:goat: 🐐
|
||||||
|
:golf: ⛳
|
||||||
|
:grapes: 🍇
|
||||||
|
:green_apple: 🍏
|
||||||
|
:green_book: 📗
|
||||||
|
:green_heart: 💚
|
||||||
|
:grey_exclamation: ❕
|
||||||
|
:grey_question: ❔
|
||||||
|
:grimacing: 😬
|
||||||
|
:grin: 😁
|
||||||
|
:grinning: 😀
|
||||||
|
:guardsman: 💂♂️
|
||||||
|
:guitar: 🎸
|
||||||
|
:gun: 🔫
|
||||||
|
:haircut: 💇
|
||||||
|
:hamburger: 🍔
|
||||||
|
:hammer: 🔨
|
||||||
|
:hamster: 🐹
|
||||||
|
:handbag: 👜
|
||||||
|
:hankey: 💩
|
||||||
|
:hash: #️⃣
|
||||||
|
:hatched_chick: 🐥
|
||||||
|
:hatching_chick: 🐣
|
||||||
|
:headphones: 🎧
|
||||||
|
:hear_no_evil: 🙉
|
||||||
|
:heart: ❤️
|
||||||
|
:heart_decoration: 💟
|
||||||
|
:heart_eyes: 😍
|
||||||
|
:heart_eyes_cat: 😻
|
||||||
|
:heartbeat: 💓
|
||||||
|
:hearts: ♥️
|
||||||
|
:heavy_check_mark: ✔️
|
||||||
|
:heavy_division_sign: ➗
|
||||||
|
:heavy_dollar_sign: 💲
|
||||||
|
:heavy_minus_sign: ➖
|
||||||
|
:heavy_multiplication_x: ✖️
|
||||||
|
:heavy_plus_sign: ➕
|
||||||
|
:helicopter: 🚁
|
||||||
|
:herb: 🌿
|
||||||
|
:hibiscus: 🌺
|
||||||
|
:high_brightness: 🔆
|
||||||
|
:high_heel: 👠
|
||||||
|
:honey_pot: 🍯
|
||||||
|
:horse: 🐴
|
||||||
|
:horse_racing: 🏇
|
||||||
|
:hospital: 🏥
|
||||||
|
:hotel: 🏨
|
||||||
|
:hotsprings: ♨️
|
||||||
|
:hourglass: ⌛
|
||||||
|
:hourglass_flowing_sand: ⏳
|
||||||
|
:house: 🏠
|
||||||
|
:house_with_garden: 🏡
|
||||||
|
:hushed: 😯
|
||||||
|
:ice_cream: 🍨
|
||||||
|
:icecream: 🍦
|
||||||
|
:id: 🆔
|
||||||
|
:ideograph_advantage: 🉐
|
||||||
|
:imp: 👿
|
||||||
|
:inbox_tray: 📥
|
||||||
|
:incoming_envelope: 📨
|
||||||
|
:information_desk_person: 💁
|
||||||
|
:information_source: ℹ️
|
||||||
|
:innocent: 😇
|
||||||
|
:interrobang: ⁉️
|
||||||
|
:iphone: 📱
|
||||||
|
:it: 🇮🇹
|
||||||
|
:izakaya_lantern: 🏮
|
||||||
|
:jack_o_lantern: 🎃
|
||||||
|
:japan: 🗾
|
||||||
|
:japanese_castle: 🏯
|
||||||
|
:japanese_goblin: 👺
|
||||||
|
:japanese_ogre: 👹
|
||||||
|
:jeans: 👖
|
||||||
|
:joy: 😂
|
||||||
|
:joy_cat: 😹
|
||||||
|
:jp: 🇯🇵
|
||||||
|
:key: 🔑
|
||||||
|
:keycap_ten: 🔟
|
||||||
|
:kimono: 👘
|
||||||
|
:kiss: 💋
|
||||||
|
:kissing: 😗
|
||||||
|
:kissing_cat: 😽
|
||||||
|
:kissing_closed_eyes: 😚
|
||||||
|
:kissing_heart: 😘
|
||||||
|
:kissing_smiling_eyes: 😙
|
||||||
|
:koala: 🐨
|
||||||
|
:koko: 🈁
|
||||||
|
:kr: 🇰🇷
|
||||||
|
:large_blue_diamond: 🔷
|
||||||
|
:large_orange_diamond: 🔶
|
||||||
|
:last_quarter_moon: 🌗
|
||||||
|
:last_quarter_moon_with_face: 🌜
|
||||||
|
:laughing: 😆
|
||||||
|
:leaves: 🍃
|
||||||
|
:ledger: 📒
|
||||||
|
:left_luggage: 🛅
|
||||||
|
:left_right_arrow: ↔️
|
||||||
|
:leftwards_arrow_with_hook: ↩️
|
||||||
|
:lemon: 🍋
|
||||||
|
:leo: ♌
|
||||||
|
:leopard: 🐆
|
||||||
|
:libra: ♎
|
||||||
|
:light_rail: 🚈
|
||||||
|
:link: 🔗
|
||||||
|
:lips: 👄
|
||||||
|
:lipstick: 💄
|
||||||
|
:lock: 🔒
|
||||||
|
:lock_with_ink_pen: 🔏
|
||||||
|
:lollipop: 🍭
|
||||||
|
:loop: ➿
|
||||||
|
:loudspeaker: 📢
|
||||||
|
:love_hotel: 🏩
|
||||||
|
:love_letter: 💌
|
||||||
|
:low_brightness: 🔅
|
||||||
|
:m: Ⓜ️
|
||||||
|
:mag: 🔍
|
||||||
|
:mag_right: 🔎
|
||||||
|
:mahjong: 🀄
|
||||||
|
:mailbox: 📫
|
||||||
|
:mailbox_closed: 📪
|
||||||
|
:mailbox_with_mail: 📬
|
||||||
|
:mailbox_with_no_mail: 📭
|
||||||
|
:man: 👨
|
||||||
|
:man_with_gua_pi_mao: 👲
|
||||||
|
:man_with_turban: 👳♂️
|
||||||
|
:mans_shoe: 👞
|
||||||
|
:maple_leaf: 🍁
|
||||||
|
:mask: 😷
|
||||||
|
:massage: 💆
|
||||||
|
:meat_on_bone: 🍖
|
||||||
|
:mega: 📣
|
||||||
|
:melon: 🍈
|
||||||
|
:memo: 📝
|
||||||
|
:mens: 🚹
|
||||||
|
:metal: 🤘
|
||||||
|
:metro: 🚇
|
||||||
|
:microphone: 🎤
|
||||||
|
:microscope: 🔬
|
||||||
|
:milky_way: 🌌
|
||||||
|
:minibus: 🚐
|
||||||
|
:minidisc: 💽
|
||||||
|
:mobile_phone_off: 📴
|
||||||
|
:money_with_wings: 💸
|
||||||
|
:moneybag: 💰
|
||||||
|
:monkey: 🐒
|
||||||
|
:monkey_face: 🐵
|
||||||
|
:monorail: 🚝
|
||||||
|
:mortar_board: 🎓
|
||||||
|
:mount_fuji: 🗻
|
||||||
|
:mountain_bicyclist: 🚵
|
||||||
|
:mountain_cableway: 🚠
|
||||||
|
:mountain_railway: 🚞
|
||||||
|
:mouse2: 🐁
|
||||||
|
:mouse: 🐭
|
||||||
|
:movie_camera: 🎥
|
||||||
|
:moyai: 🗿
|
||||||
|
:muscle: 💪
|
||||||
|
:mushroom: 🍄
|
||||||
|
:musical_keyboard: 🎹
|
||||||
|
:musical_note: 🎵
|
||||||
|
:musical_score: 🎼
|
||||||
|
:mute: 🔇
|
||||||
|
:nail_care: 💅
|
||||||
|
:name_badge: 📛
|
||||||
|
:necktie: 👔
|
||||||
|
:negative_squared_cross_mark: ❎
|
||||||
|
:neutral_face: 😐
|
||||||
|
:new: 🆕
|
||||||
|
:new_moon: 🌑
|
||||||
|
:new_moon_with_face: 🌚
|
||||||
|
:newspaper: 📰
|
||||||
|
:ng: 🆖
|
||||||
|
:nine: 9️⃣
|
||||||
|
:no_bell: 🔕
|
||||||
|
:no_bicycles: 🚳
|
||||||
|
:no_entry: ⛔
|
||||||
|
:no_entry_sign: 🚫
|
||||||
|
:no_good: 🙅
|
||||||
|
:no_mobile_phones: 📵
|
||||||
|
:no_mouth: 😶
|
||||||
|
:no_pedestrians: 🚷
|
||||||
|
:no_smoking: 🚭
|
||||||
|
:nose: 👃
|
||||||
|
:notebook: 📓
|
||||||
|
:notebook_with_decorative_cover: 📔
|
||||||
|
:notes: 🎶
|
||||||
|
:nut_and_bolt: 🔩
|
||||||
|
:o2: 🅾️
|
||||||
|
:o: ⭕
|
||||||
|
:ocean: 🌊
|
||||||
|
:octopus: 🐙
|
||||||
|
:oden: 🍢
|
||||||
|
:office: 🏢
|
||||||
|
:ok: 🆗
|
||||||
|
:ok_hand: 👌
|
||||||
|
:ok_woman: 🙆♀️
|
||||||
|
:older_man: 👴
|
||||||
|
:older_woman: 👵
|
||||||
|
:on: 🔛
|
||||||
|
:oncoming_automobile: 🚘
|
||||||
|
:oncoming_bus: 🚍
|
||||||
|
:oncoming_police_car: 🚔
|
||||||
|
:oncoming_taxi: 🚖
|
||||||
|
:one: 1️⃣
|
||||||
|
:open_file_folder: 📂
|
||||||
|
:open_hands: 👐
|
||||||
|
:open_mouth: 😮
|
||||||
|
:ophiuchus: ⛎
|
||||||
|
:orange_book: 📙
|
||||||
|
:outbox_tray: 📤
|
||||||
|
:ox: 🐂
|
||||||
|
:page_facing_up: 📄
|
||||||
|
:page_with_curl: 📃
|
||||||
|
:pager: 📟
|
||||||
|
:palm_tree: 🌴
|
||||||
|
:panda_face: 🐼
|
||||||
|
:paperclip: 📎
|
||||||
|
:parking: 🅿️
|
||||||
|
:part_alternation_mark: 〽️
|
||||||
|
:partly_sunny: ⛅
|
||||||
|
:passport_control: 🛂
|
||||||
|
:paw_prints: 🐾
|
||||||
|
:peach: 🍑
|
||||||
|
:pear: 🍐
|
||||||
|
:pencil2: ✏️
|
||||||
|
:pencil: 📝
|
||||||
|
:penguin: 🐧
|
||||||
|
:pensive: 😔
|
||||||
|
:performing_arts: 🎭
|
||||||
|
:persevere: 😣
|
||||||
|
:pig2: 🐖
|
||||||
|
:pig: 🐷
|
||||||
|
:pig_nose: 🐽
|
||||||
|
:pill: 💊
|
||||||
|
:pineapple: 🍍
|
||||||
|
:pisces: ♓
|
||||||
|
:pizza: 🍕
|
||||||
|
:point_down: 👇
|
||||||
|
:point_left: 👈
|
||||||
|
:point_right: 👉
|
||||||
|
:point_up: ☝️
|
||||||
|
:point_up_2: 👆
|
||||||
|
:police_car: 🚓
|
||||||
|
:poodle: 🐩
|
||||||
|
:poop: 💩
|
||||||
|
:post_office: 🏣
|
||||||
|
:postal_horn: 📯
|
||||||
|
:postbox: 📮
|
||||||
|
:potable_water: 🚰
|
||||||
|
:pouch: 👝
|
||||||
|
:poultry_leg: 🍗
|
||||||
|
:pound: 💷
|
||||||
|
:pouting_cat: 😾
|
||||||
|
:pray: 🙏
|
||||||
|
:princess: 👸
|
||||||
|
:punch: 👊
|
||||||
|
:purple_heart: 💜
|
||||||
|
:purse: 👛
|
||||||
|
:pushpin: 📌
|
||||||
|
:put_litter_in_its_place: 🚮
|
||||||
|
:question: ❓
|
||||||
|
:rabbit2: 🐇
|
||||||
|
:rabbit: 🐰
|
||||||
|
:racehorse: 🐎
|
||||||
|
:radio: 📻
|
||||||
|
:radio_button: 🔘
|
||||||
|
:rage: 😡
|
||||||
|
:railway_car: 🚃
|
||||||
|
:rainbow: 🌈
|
||||||
|
:raised_hand: ✋
|
||||||
|
:raised_hands: 🙌
|
||||||
|
:raising_hand: 🙋
|
||||||
|
:ram: 🐏
|
||||||
|
:ramen: 🍜
|
||||||
|
:rat: 🐀
|
||||||
|
:recycle: ♻️
|
||||||
|
:red_car: 🚗
|
||||||
|
:red_circle: 🔴
|
||||||
|
:registered: ®️
|
||||||
|
:relaxed: ☺️
|
||||||
|
:relieved: 😌
|
||||||
|
:repeat: 🔁
|
||||||
|
:repeat_one: 🔂
|
||||||
|
:restroom: 🚻
|
||||||
|
:revolving_hearts: 💞
|
||||||
|
:rewind: ⏪
|
||||||
|
:ribbon: 🎀
|
||||||
|
:rice: 🍚
|
||||||
|
:rice_ball: 🍙
|
||||||
|
:rice_cracker: 🍘
|
||||||
|
:rice_scene: 🎑
|
||||||
|
:ring: 💍
|
||||||
|
:rocket: 🚀
|
||||||
|
:roller_coaster: 🎢
|
||||||
|
:rooster: 🐓
|
||||||
|
:rose: 🌹
|
||||||
|
:rotating_light: 🚨
|
||||||
|
:round_pushpin: 📍
|
||||||
|
:rowboat: 🚣
|
||||||
|
:ru: 🇷🇺
|
||||||
|
:rugby_football: 🏉
|
||||||
|
:runner: 🏃
|
||||||
|
:running_shirt_with_sash: 🎽
|
||||||
|
:sa: 🈂️
|
||||||
|
:sagittarius: ♐
|
||||||
|
:sailboat: ⛵
|
||||||
|
:sake: 🍶
|
||||||
|
:sandal: 👡
|
||||||
|
:santa: 🎅
|
||||||
|
:satellite: 📡
|
||||||
|
:satisfied: 😆
|
||||||
|
:saxophone: 🎷
|
||||||
|
:school: 🏫
|
||||||
|
:school_satchel: 🎒
|
||||||
|
:scissors: ✂️
|
||||||
|
:scorpius: ♏
|
||||||
|
:scream: 😱
|
||||||
|
:scream_cat: 🙀
|
||||||
|
:scroll: 📜
|
||||||
|
:seat: 💺
|
||||||
|
:secret: ㊙️
|
||||||
|
:see_no_evil: 🙈
|
||||||
|
:seedling: 🌱
|
||||||
|
:seven: 7️⃣
|
||||||
|
:shaved_ice: 🍧
|
||||||
|
:sheep: 🐑
|
||||||
|
:shell: 🐚
|
||||||
|
:ship: 🚢
|
||||||
|
:shirt: 👕
|
||||||
|
:shit: 💩
|
||||||
|
:shower: 🚿
|
||||||
|
:signal_strength: 📶
|
||||||
|
:six: 6️⃣
|
||||||
|
:six_pointed_star: 🔯
|
||||||
|
:ski: 🎿
|
||||||
|
:skull: 💀
|
||||||
|
:sleeping: 😴
|
||||||
|
:sleepy: 😪
|
||||||
|
:slot_machine: 🎰
|
||||||
|
:small_blue_diamond: 🔹
|
||||||
|
:small_orange_diamond: 🔸
|
||||||
|
:small_red_triangle: 🔺
|
||||||
|
:small_red_triangle_down: 🔻
|
||||||
|
:smile: 😄
|
||||||
|
:smile_cat: 😸
|
||||||
|
:smiley: 😃
|
||||||
|
:smiley_cat: 😺
|
||||||
|
:smiling_imp: 😈
|
||||||
|
:smirk: 😏
|
||||||
|
:smirk_cat: 😼
|
||||||
|
:smoking: 🚬
|
||||||
|
:snail: 🐌
|
||||||
|
:snake: 🐍
|
||||||
|
:snowboarder: 🏂
|
||||||
|
:snowflake: ❄️
|
||||||
|
:snowman: ⛄
|
||||||
|
:sob: 😭
|
||||||
|
:soccer: ⚽
|
||||||
|
:soon: 🔜
|
||||||
|
:sos: 🆘
|
||||||
|
:sound: 🔉
|
||||||
|
:space_invader: 👾
|
||||||
|
:spades: ♠️
|
||||||
|
:spaghetti: 🍝
|
||||||
|
:sparkler: 🎇
|
||||||
|
:sparkles: ✨
|
||||||
|
:sparkling_heart: 💖
|
||||||
|
:speak_no_evil: 🙊
|
||||||
|
:speaker: 🔈
|
||||||
|
:speech_balloon: 💬
|
||||||
|
:speedboat: 🚤
|
||||||
|
:star2: 🌟
|
||||||
|
:star: ⭐
|
||||||
|
:stars: 🌠
|
||||||
|
:station: 🚉
|
||||||
|
:statue_of_liberty: 🗽
|
||||||
|
:steam_locomotive: 🚂
|
||||||
|
:stew: 🍲
|
||||||
|
:straight_ruler: 📏
|
||||||
|
:strawberry: 🍓
|
||||||
|
:stuck_out_tongue: 😛
|
||||||
|
:stuck_out_tongue_closed_eyes: 😝
|
||||||
|
:stuck_out_tongue_winking_eye: 😜
|
||||||
|
:sun_with_face: 🌞
|
||||||
|
:sunflower: 🌻
|
||||||
|
:sunglasses: 😎
|
||||||
|
:sunny: ☀️
|
||||||
|
:sunrise: 🌅
|
||||||
|
:sunrise_over_mountains: 🌄
|
||||||
|
:surfer: 🏄
|
||||||
|
:sushi: 🍣
|
||||||
|
:suspension_railway: 🚟
|
||||||
|
:sweat: 😓
|
||||||
|
:sweat_drops: 💦
|
||||||
|
:sweat_smile: 😅
|
||||||
|
:sweet_potato: 🍠
|
||||||
|
:swimmer: 🏊
|
||||||
|
:symbols: 🔣
|
||||||
|
:syringe: 💉
|
||||||
|
:tada: 🎉
|
||||||
|
:tanabata_tree: 🎋
|
||||||
|
:tangerine: 🍊
|
||||||
|
:taurus: ♉
|
||||||
|
:taxi: 🚕
|
||||||
|
:tea: 🍵
|
||||||
|
:telephone: ☎️
|
||||||
|
:telephone_receiver: 📞
|
||||||
|
:telescope: 🔭
|
||||||
|
:tennis: 🎾
|
||||||
|
:tent: ⛺
|
||||||
|
:thought_balloon: 💭
|
||||||
|
:three: 3️⃣
|
||||||
|
:thumbsdown: 👎
|
||||||
|
:thumbsup: 👍
|
||||||
|
:ticket: 🎫
|
||||||
|
:tiger2: 🐅
|
||||||
|
:tiger: 🐯
|
||||||
|
:tired_face: 😫
|
||||||
|
:tm: ™️
|
||||||
|
:toilet: 🚽
|
||||||
|
:tokyo_tower: 🗼
|
||||||
|
:tomato: 🍅
|
||||||
|
:tongue: 👅
|
||||||
|
:top: 🔝
|
||||||
|
:tophat: 🎩
|
||||||
|
:tractor: 🚜
|
||||||
|
:traffic_light: 🚥
|
||||||
|
:train2: 🚆
|
||||||
|
:train: 🚋
|
||||||
|
:tram: 🚊
|
||||||
|
:triangular_flag_on_post: 🚩
|
||||||
|
:triangular_ruler: 📐
|
||||||
|
:trident: 🔱
|
||||||
|
:triumph: 😤
|
||||||
|
:trolleybus: 🚎
|
||||||
|
:trophy: 🏆
|
||||||
|
:tropical_drink: 🍹
|
||||||
|
:tropical_fish: 🐠
|
||||||
|
:truck: 🚚
|
||||||
|
:trumpet: 🎺
|
||||||
|
:tulip: 🌷
|
||||||
|
:turtle: 🐢
|
||||||
|
:tv: 📺
|
||||||
|
:twisted_rightwards_arrows: 🔀
|
||||||
|
:two: 2️⃣
|
||||||
|
:two_men_holding_hands: 👬
|
||||||
|
:two_women_holding_hands: 👭
|
||||||
|
:u5272: 🈹
|
||||||
|
:u5408: 🈴
|
||||||
|
:u55b6: 🈺
|
||||||
|
:u6307: 🈯
|
||||||
|
:u6708: 🈷️
|
||||||
|
:u6709: 🈶
|
||||||
|
:u6e80: 🈵
|
||||||
|
:u7121: 🈚
|
||||||
|
:u7533: 🈸
|
||||||
|
:u7981: 🈲
|
||||||
|
:u7a7a: 🈳
|
||||||
|
:umbrella: ☔
|
||||||
|
:unamused: 😒
|
||||||
|
:underage: 🔞
|
||||||
|
:unlock: 🔓
|
||||||
|
:up: 🆙
|
||||||
|
:us: 🇺🇸
|
||||||
|
:v: ✌️
|
||||||
|
:vertical_traffic_light: 🚦
|
||||||
|
:vhs: 📼
|
||||||
|
:vibration_mode: 📳
|
||||||
|
:video_camera: 📹
|
||||||
|
:video_game: 🎮
|
||||||
|
:violin: 🎻
|
||||||
|
:virgo: ♍
|
||||||
|
:volcano: 🌋
|
||||||
|
:vs: 🆚
|
||||||
|
:walking: 🚶
|
||||||
|
:waning_crescent_moon: 🌘
|
||||||
|
:waning_gibbous_moon: 🌖
|
||||||
|
:warning: ⚠️
|
||||||
|
:watch: ⌚
|
||||||
|
:water_buffalo: 🐃
|
||||||
|
:watermelon: 🍉
|
||||||
|
:wave: 👋
|
||||||
|
:wavy_dash: 〰️
|
||||||
|
:waxing_crescent_moon: 🌒
|
||||||
|
:waxing_gibbous_moon: 🌔
|
||||||
|
:wc: 🚾
|
||||||
|
:weary: 😩
|
||||||
|
:wedding: 💒
|
||||||
|
:whale2: 🐋
|
||||||
|
:whale: 🐳
|
||||||
|
:wheelchair: ♿
|
||||||
|
:white_check_mark: ✅
|
||||||
|
:white_circle: ⚪
|
||||||
|
:white_flower: 💮
|
||||||
|
:white_square_button: 🔳
|
||||||
|
:wind_chime: 🎐
|
||||||
|
:wine_glass: 🍷
|
||||||
|
:wink: 😉
|
||||||
|
:wolf: 🐺
|
||||||
|
:woman: 👩
|
||||||
|
:womans_clothes: 👚
|
||||||
|
:womans_hat: 👒
|
||||||
|
:womens: 🚺
|
||||||
|
:worried: 😟
|
||||||
|
:wrench: 🔧
|
||||||
|
:x: ❌
|
||||||
|
:yellow_heart: 💛
|
||||||
|
:yen: 💴
|
||||||
|
:yum: 😋
|
||||||
|
:zap: ⚡
|
||||||
|
:zero: 0️⃣
|
||||||
|
:zzz: 💤
|
||||||
|
44
awk/fenced_code.awk
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
if (in_fence) {
|
||||||
|
gsub(/&/, "\\&"); gsub(/</, "\\<"); gsub(/>/, "\\>")
|
||||||
|
if (first_line) {
|
||||||
|
first_line = 0
|
||||||
|
printf "%s", "<pre>" code_tag
|
||||||
|
if ($0 == "") {
|
||||||
|
print ""
|
||||||
|
next
|
||||||
|
}
|
||||||
|
print $0
|
||||||
|
} else {
|
||||||
|
print
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
if (in_fence) {
|
||||||
|
if (first_line) printf "%s", "<pre>" code_tag
|
||||||
|
print "</code></pre>"
|
||||||
|
}
|
||||||
|
}
|
||||||
51
awk/footnotes.awk
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
BEGIN { fn_count = 0 }
|
||||||
|
|
||||||
|
# Match [^id]: text
|
||||||
|
/^\[\^[a-zA-Z0-9_-]+\]:/ {
|
||||||
|
id_start = index($0, "[^") + 2
|
||||||
|
id_end = index($0, "]:")
|
||||||
|
id = substr($0, id_start, id_end - id_start)
|
||||||
|
text = substr($0, id_end + 2)
|
||||||
|
# Trim leading space
|
||||||
|
sub(/^[ \t]+/, "", text)
|
||||||
|
|
||||||
|
fn_ids[++fn_count] = id
|
||||||
|
fn_texts[id] = text
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
lines[++line_count] = $0
|
||||||
|
}
|
||||||
|
|
||||||
|
END {
|
||||||
|
for (i = 1; i <= line_count; i++) {
|
||||||
|
line = lines[i]
|
||||||
|
|
||||||
|
for (j = 1; j <= fn_count; j++) {
|
||||||
|
id = fn_ids[j]
|
||||||
|
marker = "[^" id "]"
|
||||||
|
repl = "<sup><a href=\"#fn:" id "\" id=\"fnref:" id "\">" id "</a></sup>"
|
||||||
|
|
||||||
|
while ((pos = index(line, marker)) > 0) {
|
||||||
|
line = substr(line, 1, pos - 1) repl substr(line, pos + length(marker))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print line
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fn_count > 0) {
|
||||||
|
print "<hr />"
|
||||||
|
print "<section class=\"footnotes\">"
|
||||||
|
print "<ol>"
|
||||||
|
for (j = 1; j <= fn_count; j++) {
|
||||||
|
id = fn_ids[j]
|
||||||
|
text = fn_texts[id]
|
||||||
|
print "<li id=\"fn:" id "\">"
|
||||||
|
print "<p>" text " <a href=\"#fnref:" id "\" class=\"reversefootnote\">↩</a></p>"
|
||||||
|
print "</li>"
|
||||||
|
}
|
||||||
|
print "</ol>"
|
||||||
|
print "</section>"
|
||||||
|
}
|
||||||
|
}
|
||||||
46
awk/frontmatter.awk
Normal 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
|
||||||
|
}
|
||||||
@@ -3,8 +3,92 @@ function title_from_name(name) {
|
|||||||
gsub(/-/, " ", name)
|
gsub(/-/, " ", name)
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
function url_encode_path(path) {
|
||||||
|
gsub(/%/, "%25", path)
|
||||||
|
gsub(/ /, "%20", path)
|
||||||
|
gsub(/#/, "%23", path)
|
||||||
|
gsub(/\?/, "%3F", path)
|
||||||
|
gsub(/"/, "%22", path)
|
||||||
|
gsub(/'/, "%27", path)
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_title(path, default_title, full_path, line, title, in_fm) {
|
||||||
|
full_path = src "/" path
|
||||||
|
if (path !~ /\.md$/) {
|
||||||
|
full_path = full_path "/index.md"
|
||||||
|
}
|
||||||
|
|
||||||
|
title = ""
|
||||||
|
in_fm = 0
|
||||||
|
while ((getline line < full_path) > 0) {
|
||||||
|
if (line ~ /^---[[:space:]]*$/) {
|
||||||
|
if (in_fm == 0) {
|
||||||
|
in_fm = 1
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (in_fm) {
|
||||||
|
if (line ~ /^[[:space:]]*title[[:space:]]*=/) {
|
||||||
|
sub(/^[[:space:]]*title[[:space:]]*=[[:space:]]*/, "", line)
|
||||||
|
if (line ~ /^".*"$/) {
|
||||||
|
title = substr(line, 2, length(line) - 2)
|
||||||
|
} else if (line ~ /^'.*'$/) {
|
||||||
|
title = substr(line, 2, length(line) - 2)
|
||||||
|
} else {
|
||||||
|
title = line
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(full_path)
|
||||||
|
|
||||||
|
if (title != "") return title
|
||||||
|
return default_title
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function compare_paths(p1, p2, parts1, parts2, n1, n2, i, name1, name2, lname1, lname2, w1, w2) {
|
||||||
|
n1 = split(p1, parts1, "/")
|
||||||
|
n2 = split(p2, parts2, "/")
|
||||||
|
for (i = 1; i <= n1 && i <= n2; i++) {
|
||||||
|
name1 = parts1[i]
|
||||||
|
name2 = parts2[i]
|
||||||
|
if (i == n1) gsub(/\.md$/, "", name1)
|
||||||
|
if (i == n2) gsub(/\.md$/, "", name2)
|
||||||
|
lname1 = tolower(name1)
|
||||||
|
lname2 = tolower(name2)
|
||||||
|
|
||||||
|
if (lname1 == "index" && i == n1 && lname2 != "index") return -1
|
||||||
|
if (lname2 == "index" && i == n2 && lname1 != "index") return 1
|
||||||
|
|
||||||
|
w1 = (lname1 in custom_order ? custom_order[lname1] : 999999)
|
||||||
|
w2 = (lname2 in custom_order ? custom_order[lname2] : 999999)
|
||||||
|
|
||||||
|
if (w1 < w2) return -1
|
||||||
|
if (w1 > w2) return 1
|
||||||
|
|
||||||
|
if (lname1 < lname2) return -1
|
||||||
|
if (lname1 > lname2) return 1
|
||||||
|
}
|
||||||
|
if (n1 < n2) return -1
|
||||||
|
if (n1 > n2) return 1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
BEGIN {
|
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")
|
n_dlines = split(dinfo, dlines, "\n")
|
||||||
for (i = 1; i <= n_dlines; i++) {
|
for (i = 1; i <= n_dlines; i++) {
|
||||||
if (split(dlines[i], dparts, "|") == 3) {
|
if (split(dlines[i], dparts, "|") == 3) {
|
||||||
@@ -12,6 +96,16 @@ BEGIN {
|
|||||||
d_dirs[dparts[1]] = dparts[3]
|
d_dirs[dparts[1]] = dparts[3]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
n_order = split(order, oparts, ",")
|
||||||
|
for (i = 1; i <= n_order; i++) {
|
||||||
|
name = oparts[i]
|
||||||
|
sub(/^[[:space:]]*/, "", name)
|
||||||
|
sub(/[[:space:]]*$/, "", name)
|
||||||
|
if (name != "") {
|
||||||
|
custom_order[tolower(name)] = i
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -31,9 +125,20 @@ BEGIN {
|
|||||||
}
|
}
|
||||||
|
|
||||||
END {
|
END {
|
||||||
|
for (i = 0; i < count - 1; i++) {
|
||||||
|
for (j = 0; j < count - i - 1; j++) {
|
||||||
|
if (compare_paths(ordered_paths[j], ordered_paths[j+1]) > 0) {
|
||||||
|
tmp = ordered_paths[j]
|
||||||
|
ordered_paths[j] = ordered_paths[j+1]
|
||||||
|
ordered_paths[j+1] = tmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
print "<ul>"
|
print "<ul>"
|
||||||
if ("index.md" in all_paths) {
|
if (show_home_in_nav == "true" && "index.md" in all_paths) {
|
||||||
print "<li><a href=\"/index.html\">Home</a></li>"
|
if (home_name == "") home_name = "Home"
|
||||||
|
print "<li><a href=\"/index.html\">" home_name "</a></li>"
|
||||||
}
|
}
|
||||||
|
|
||||||
depth = 0
|
depth = 0
|
||||||
@@ -76,7 +181,7 @@ END {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
printf "<li><a href=\"/%sindex.html\">%s</a><ul>\n", dir_path, title_from_name(parts[i])
|
printf "<li><a href=\"/%sindex.html\">%s</a><ul>\n", url_encode_path(dir_path), get_title(this_d, title_from_name(parts[i]))
|
||||||
opened_levels[++depth] = i
|
opened_levels[++depth] = i
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,9 +192,9 @@ END {
|
|||||||
is_single = (single_file_index == "true" && md_count[curr_dir] == 1 && !has_index[curr_dir])
|
is_single = (single_file_index == "true" && md_count[curr_dir] == 1 && !has_index[curr_dir])
|
||||||
|
|
||||||
if (parts[n] != "index.md" && !is_single) {
|
if (parts[n] != "index.md" && !is_single) {
|
||||||
path = "/" rel
|
path = url_encode_path("/" rel)
|
||||||
gsub(/\.md$/, ".html", path)
|
gsub(/\.md$/, ".html", path)
|
||||||
printf "<li><a href=\"%s\">%s</a></li>\n", path, title_from_name(parts[n])
|
printf "<li><a href=\"%s\">%s</a></li>\n", path, get_title(rel, title_from_name(parts[n]))
|
||||||
}
|
}
|
||||||
|
|
||||||
prev_n = n
|
prev_n = n
|
||||||
|
|||||||
101
awk/headers.awk
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
function strip_markdown(s) {
|
||||||
|
gsub(/<[^>]+>/, "", 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 unique_id(raw_id, candidate) {
|
||||||
|
candidate = raw_id
|
||||||
|
if (candidate == "") candidate = "section"
|
||||||
|
if (!(candidate in seen_ids)) {
|
||||||
|
seen_ids[candidate] = 1
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
seen_ids[candidate]++
|
||||||
|
return candidate "-" seen_ids[candidate]
|
||||||
|
}
|
||||||
|
function print_heading(tag, line, id) {
|
||||||
|
id = unique_id(strip_markdown(line))
|
||||||
|
if (enable_header_links == "true") {
|
||||||
|
print "<" tag " id=\"" id "\">" line " <a href=\"#" id "\" class=\"header-anchor\" aria-label=\"Link to this section\">#</a></" tag ">"
|
||||||
|
} else {
|
||||||
|
print "<" tag " id=\"" id "\">" line "</" tag ">"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function print_header(line) {
|
||||||
|
tag = ""
|
||||||
|
if (line ~ /^# /) { tag = "h1"; sub(/^# /, "", line) }
|
||||||
|
else if (line ~ /^## /) { tag = "h2"; sub(/^## /, "", line) }
|
||||||
|
else if (line ~ /^### /) { tag = "h3"; sub(/^### /, "", line) }
|
||||||
|
else if (line ~ /^#### /) { tag = "h4"; sub(/^#### /, "", line) }
|
||||||
|
else if (line ~ /^##### /) { tag = "h5"; sub(/^##### /, "", line) }
|
||||||
|
else if (line ~ /^###### /) { tag = "h6"; sub(/^###### /, "", line) }
|
||||||
|
|
||||||
|
if (tag != "") {
|
||||||
|
print_heading(tag, line)
|
||||||
|
} else {
|
||||||
|
print line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BEGIN {
|
||||||
|
has_prev = 0
|
||||||
|
in_pre = 0
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if ($0 ~ /^<pre><code/) {
|
||||||
|
in_pre = 1
|
||||||
|
if (has_prev && prev != "") { print_header(prev); has_prev = 0 }
|
||||||
|
print
|
||||||
|
next
|
||||||
|
}
|
||||||
|
if (in_pre) {
|
||||||
|
if ($0 ~ /<\/code><\/pre>/) in_pre = 0
|
||||||
|
print
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($0 ~ /^=+$/) {
|
||||||
|
if (has_prev && prev != "" && prev !~ /^<[a-z]/) {
|
||||||
|
print_heading("h1", prev)
|
||||||
|
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_heading("h2", prev)
|
||||||
|
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) {
|
||||||
|
print_header(prev)
|
||||||
|
}
|
||||||
|
prev = $0
|
||||||
|
has_prev = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
if (has_prev) {
|
||||||
|
print_header(prev)
|
||||||
|
}
|
||||||
|
}
|
||||||
20
awk/indented_code.awk
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
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(/&/, "\\&"); gsub(/</, "\\<"); gsub(/>/, "\\>")
|
||||||
|
print
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_code) {
|
||||||
|
print "</code></pre>"
|
||||||
|
in_code = 0
|
||||||
|
}
|
||||||
|
print
|
||||||
|
}
|
||||||
|
END { if (in_code) print "</code></pre>" }
|
||||||
85
awk/lists.awk
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
BEGIN {
|
||||||
|
depth = 0
|
||||||
|
in_pre = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
if ($0 ~ /^<pre>/) in_pre = 1
|
||||||
|
if (in_pre) {
|
||||||
|
while (depth > 0) { print "</" cur_type[depth] ">"; depth-- }
|
||||||
|
print
|
||||||
|
if ($0 ~ /<\/pre>/) in_pre = 0
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
line = $0
|
||||||
|
type = ""
|
||||||
|
# match list marker and its preceding spaces
|
||||||
|
if (line ~ /^[ \t]*[*+-] /) {
|
||||||
|
type = "ul"
|
||||||
|
match(line, /^[ \t]*[*+-] /)
|
||||||
|
marker_len = RLENGTH
|
||||||
|
} else if (line ~ /^[ \t]*[0-9]+\. /) {
|
||||||
|
type = "ol"
|
||||||
|
match(line, /^[ \t]*[0-9]+\. /)
|
||||||
|
marker_len = RLENGTH
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type != "") {
|
||||||
|
content = substr(line, marker_len + 1)
|
||||||
|
# get indentation level
|
||||||
|
match(line, /^[ \t]*/)
|
||||||
|
indent = RLENGTH
|
||||||
|
|
||||||
|
if (depth == 0 || indent > cur_indent[depth]) {
|
||||||
|
depth++
|
||||||
|
cur_indent[depth] = indent
|
||||||
|
cur_type[depth] = type
|
||||||
|
print "<" type ">"
|
||||||
|
} else {
|
||||||
|
while (depth > 1 && indent < cur_indent[depth]) {
|
||||||
|
print "</" cur_type[depth] ">"
|
||||||
|
depth--
|
||||||
|
}
|
||||||
|
if (type != cur_type[depth]) {
|
||||||
|
print "</" cur_type[depth] ">"
|
||||||
|
cur_type[depth] = type
|
||||||
|
print "<" type ">"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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] ">"
|
||||||
|
depth--
|
||||||
|
}
|
||||||
|
print line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
END {
|
||||||
|
while (depth > 0) {
|
||||||
|
print "</" cur_type[depth] ">"
|
||||||
|
depth--
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -97,6 +97,23 @@ function read_file(path, out, line, rc) {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function read_file_or_render_md(path, ext, cmd, content, line, rc) {
|
||||||
|
content = ""
|
||||||
|
if (ext == "md") {
|
||||||
|
cmd = "sh \"" script_dir "/markdown.sh\" \"" path "\""
|
||||||
|
while ((cmd | getline line) > 0) {
|
||||||
|
content = content line "\n"
|
||||||
|
}
|
||||||
|
close(cmd)
|
||||||
|
} else {
|
||||||
|
while ((rc = getline line < path) > 0) {
|
||||||
|
content = content line "\n"
|
||||||
|
}
|
||||||
|
close(path)
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
function escape_html(s, t) {
|
function escape_html(s, t) {
|
||||||
t = s
|
t = s
|
||||||
gsub(/&/, "\\&", t)
|
gsub(/&/, "\\&", t)
|
||||||
@@ -119,11 +136,35 @@ function css_highlight_line(line, m, prop, val) {
|
|||||||
return "<span class=\"tok-punc\">}</span>"
|
return "<span class=\"tok-punc\">}</span>"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (match(line, /^([[:space:]]*)(--?[A-Za-z0-9_-]+)([[:space:]]*:[[:space:]]*)([^;]*)(;?[[:space:]]*)$/, m)) {
|
if (line ~ /^[[:space:]]*--?[A-Za-z0-9_-]+[[:space:]]*:[[:space:]]*[^;]*;?[[:space:]]*$/) {
|
||||||
prop = "<span class=\"tok-prop\">" m[2] "</span>"
|
match(line, /:[[:space:]]*/)
|
||||||
gsub(/var\(--[A-Za-z0-9_-]+\)/, "<span class=\"tok-var\">&</span>", m[4])
|
sep_pos = RSTART
|
||||||
val = "<span class=\"tok-val\">" m[4] "</span>"
|
sep_len = RLENGTH
|
||||||
return m[1] prop m[3] val m[5]
|
|
||||||
|
pre_sep = substr(line, 1, sep_pos - 1)
|
||||||
|
sep = substr(line, sep_pos, sep_len)
|
||||||
|
post_sep = substr(line, sep_pos + sep_len)
|
||||||
|
|
||||||
|
match(pre_sep, /--?[A-Za-z0-9_-]+/)
|
||||||
|
prop_pos = RSTART
|
||||||
|
prop_len = RLENGTH
|
||||||
|
|
||||||
|
indent = substr(pre_sep, 1, prop_pos - 1)
|
||||||
|
prop_name = substr(pre_sep, prop_pos, prop_len)
|
||||||
|
|
||||||
|
if (match(post_sep, /;[[:space:]]*$/)) {
|
||||||
|
val_part = substr(post_sep, 1, RSTART - 1)
|
||||||
|
suffix = substr(post_sep, RSTART)
|
||||||
|
} else {
|
||||||
|
val_part = post_sep
|
||||||
|
suffix = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
prop = "<span class=\"tok-prop\">" prop_name "</span>"
|
||||||
|
gsub(/var\(--[A-Za-z0-9_-]+\)/, "<span class=\"tok-var\">&</span>", val_part)
|
||||||
|
val = "<span class=\"tok-val\">" val_part "</span>"
|
||||||
|
|
||||||
|
return indent prop sep val suffix
|
||||||
}
|
}
|
||||||
|
|
||||||
return line
|
return line
|
||||||
@@ -165,7 +206,8 @@ function render_embed(src, alt, has_alt, force_inline, ext, local_path, conte
|
|||||||
if (force_inline && !is_global_url(src)) {
|
if (force_inline && !is_global_url(src)) {
|
||||||
local_path = resolve_local_path(src)
|
local_path = resolve_local_path(src)
|
||||||
if (local_path != "") {
|
if (local_path != "") {
|
||||||
content = read_file(local_path)
|
ext = ext_of(src)
|
||||||
|
content = read_file_or_render_md(local_path, ext)
|
||||||
if (content ~ /\n$/) sub(/\n$/, "", content)
|
if (content ~ /\n$/) sub(/\n$/, "", content)
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
@@ -180,7 +222,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_audio_ext(ext)) return "<audio controls src=\"" src "\"></audio>"
|
||||||
if (is_video_ext(ext)) return "<video controls src=\"" src "\"></video>"
|
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)) {
|
if (is_image_ext(ext)) {
|
||||||
@@ -193,13 +235,35 @@ function render_embed(src, alt, has_alt, force_inline, ext, local_path, conte
|
|||||||
if (is_inline_text_ext(ext)) {
|
if (is_inline_text_ext(ext)) {
|
||||||
local_path = resolve_local_path(src)
|
local_path = resolve_local_path(src)
|
||||||
if (local_path != "") {
|
if (local_path != "") {
|
||||||
content = read_file(local_path)
|
content = read_file_or_render_md(local_path, ext)
|
||||||
if (content ~ /\n$/) sub(/\n$/, "", content)
|
if (content ~ /\n$/) sub(/\n$/, "", content)
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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_or_render_md(local_path, ext_of(src))
|
||||||
|
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) {
|
function extract_attr(tag, attr, pat, m, token) {
|
||||||
@@ -295,7 +359,7 @@ function apply_td_vertical_align(line, out, rest, seg, td_tag, img_tag, after
|
|||||||
return out rest
|
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 = ""
|
out = ""
|
||||||
rest = line
|
rest = line
|
||||||
while (match(rest, /<img[^>]*\/?>/)) {
|
while (match(rest, /<img[^>]*\/?>/)) {
|
||||||
@@ -305,9 +369,19 @@ function rewrite_img_tags(line, out, rest, tag, src, alt, force_inline_tag, p
|
|||||||
src = extract_attr(tag, "src")
|
src = extract_attr(tag, "src")
|
||||||
alt = extract_attr(tag, "alt")
|
alt = extract_attr(tag, "alt")
|
||||||
force_inline_tag = extract_attr(tag, "data-force-inline")
|
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.
|
# Preserve hand-written <img> attributes (style/class/etc) for normal images.
|
||||||
repl = tag
|
repl = tag
|
||||||
|
} else if (force_inline_tag != "" && !is_global_url(src) && is_inline_text_ext(ext_of(src))) {
|
||||||
|
repl = render_code_include(src, 1)
|
||||||
|
if (repl != "") {
|
||||||
|
repl = "<pre><code>" repl "</code></pre>"
|
||||||
|
} else {
|
||||||
|
repl = render_embed(src, alt, (alt != ""), 1)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
repl = render_embed(src, alt, (alt != ""), (force_inline_tag != ""))
|
repl = render_embed(src, alt, (alt != ""), (force_inline_tag != ""))
|
||||||
}
|
}
|
||||||
@@ -332,7 +406,12 @@ function rewrite_double_bang_with_parens(line, out, rest, token, inside, src,
|
|||||||
src = substr(inside, sep + 2)
|
src = substr(inside, sep + 2)
|
||||||
sub(/\)$/, "", src)
|
sub(/\)$/, "", src)
|
||||||
|
|
||||||
|
repl = render_code_include(src, 1)
|
||||||
|
if (repl != "") {
|
||||||
|
repl = "<pre><code>" repl "</code></pre>"
|
||||||
|
} else {
|
||||||
repl = render_embed(src, alt, (alt != ""), 1)
|
repl = render_embed(src, alt, (alt != ""), 1)
|
||||||
|
}
|
||||||
out = out pre repl
|
out = out pre repl
|
||||||
rest = post
|
rest = post
|
||||||
}
|
}
|
||||||
@@ -349,7 +428,12 @@ function rewrite_double_bang_bare(line, out, rest, token, src, pre, post, rep
|
|||||||
src = token
|
src = token
|
||||||
sub(/^!!\[/, "", src)
|
sub(/^!!\[/, "", src)
|
||||||
sub(/\]$/, "", src)
|
sub(/\]$/, "", src)
|
||||||
|
repl = render_code_include(src, 1)
|
||||||
|
if (repl != "") {
|
||||||
|
repl = "<pre><code>" repl "</code></pre>"
|
||||||
|
} else {
|
||||||
repl = render_embed(src, "", 0, 1)
|
repl = render_embed(src, "", 0, 1)
|
||||||
|
}
|
||||||
out = out pre repl
|
out = out pre repl
|
||||||
rest = post
|
rest = post
|
||||||
}
|
}
|
||||||
@@ -509,6 +593,75 @@ function restore_plain_markers(line) {
|
|||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function break_code_double_bang(line, out, rest, pstart, pend, code_content, token, pre, post, inside, sep, src, alt, repl) {
|
||||||
|
out = ""
|
||||||
|
rest = line
|
||||||
|
while (1) {
|
||||||
|
pstart = index(rest, "<code>")
|
||||||
|
if (pstart == 0) {
|
||||||
|
out = out rest
|
||||||
|
break
|
||||||
|
}
|
||||||
|
out = out substr(rest, 1, pstart - 1)
|
||||||
|
rest = substr(rest, pstart)
|
||||||
|
pend = index(substr(rest, 7), "</code>")
|
||||||
|
if (pend == 0) {
|
||||||
|
out = out rest
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pend = pend + 6
|
||||||
|
code_content = substr(rest, 7, pend - 7)
|
||||||
|
rest = substr(rest, pend + 7)
|
||||||
|
if (match(code_content, /!!\[[^\]]*\]\([^)]*\)/)) {
|
||||||
|
token = substr(code_content, RSTART, RLENGTH)
|
||||||
|
pre = substr(code_content, 1, RSTART - 1)
|
||||||
|
post = substr(code_content, RSTART + RLENGTH)
|
||||||
|
inside = token
|
||||||
|
sub(/^!!\[/, "", inside)
|
||||||
|
sep = index(inside, "](")
|
||||||
|
alt = substr(inside, 1, sep - 1)
|
||||||
|
src = substr(inside, sep + 2)
|
||||||
|
sub(/\)$/, "", src)
|
||||||
|
repl = render_code_include(src, 1)
|
||||||
|
if (repl != "") {
|
||||||
|
repl = "<pre><code>" repl "</code></pre>"
|
||||||
|
} else {
|
||||||
|
repl = render_embed(src, alt, (alt != ""), 1)
|
||||||
|
}
|
||||||
|
if (repl == "") {
|
||||||
|
out = out "<code>" code_content "</code>"
|
||||||
|
} else {
|
||||||
|
if (pre != "") out = out "<code>" pre "</code>"
|
||||||
|
out = out repl
|
||||||
|
if (post != "") out = out "<code>" post "</code>"
|
||||||
|
}
|
||||||
|
} else if (match(code_content, /!!\[[^\]]+\]/)) {
|
||||||
|
token = substr(code_content, RSTART, RLENGTH)
|
||||||
|
pre = substr(code_content, 1, RSTART - 1)
|
||||||
|
post = substr(code_content, RSTART + RLENGTH)
|
||||||
|
src = token
|
||||||
|
sub(/^!!\[/, "", src)
|
||||||
|
sub(/\]$/, "", src)
|
||||||
|
repl = render_code_include(src, 1)
|
||||||
|
if (repl != "") {
|
||||||
|
repl = "<pre><code>" repl "</code></pre>"
|
||||||
|
} else {
|
||||||
|
repl = render_embed(src, "", 0, 1)
|
||||||
|
}
|
||||||
|
if (repl == "") {
|
||||||
|
out = out "<code>" code_content "</code>"
|
||||||
|
} else {
|
||||||
|
if (pre != "") out = out "<code>" pre "</code>"
|
||||||
|
out = out repl
|
||||||
|
if (post != "") out = out "<code>" post "</code>"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out = out "<code>" code_content "</code>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
BEGIN {
|
BEGIN {
|
||||||
input_dir = dirname_of(input_file)
|
input_dir = dirname_of(input_file)
|
||||||
in_pre_code = 0
|
in_pre_code = 0
|
||||||
@@ -534,6 +687,9 @@ BEGIN {
|
|||||||
|
|
||||||
line = apply_td_vertical_align(line)
|
line = apply_td_vertical_align(line)
|
||||||
line = restore_plain_markers(line)
|
line = restore_plain_markers(line)
|
||||||
|
if (!(in_pre_code || start_pre)) {
|
||||||
|
line = break_code_double_bang(line)
|
||||||
|
}
|
||||||
print line
|
print line
|
||||||
|
|
||||||
if (start_pre && !end_pre) {
|
if (start_pre && !end_pre) {
|
||||||
|
|||||||
236
awk/markdown_inline.awk
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
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, val) {
|
||||||
|
for (i = 1; i <= html_tag_count; i++) {
|
||||||
|
val = html_tag_value[i]
|
||||||
|
gsub(/&/, "\\\\&", val)
|
||||||
|
gsub(html_tag_token[i], val, s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
if ($0 ~ /<pre>/) {
|
||||||
|
in_pre = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_pre) {
|
||||||
|
print
|
||||||
|
if ($0 ~ /<\/pre>/) {
|
||||||
|
in_pre = 0
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
line = $0
|
||||||
|
|
||||||
|
# automatic links
|
||||||
|
while (match(line, /<https?:\/\/[^>]+>/)) {
|
||||||
|
start = RSTART; len = RLENGTH
|
||||||
|
url = substr(line, start + 1, len - 2)
|
||||||
|
repl = "<a href=\"" url "\">" url "</a>"
|
||||||
|
line = substr(line, 1, start - 1) repl substr(line, start + len)
|
||||||
|
}
|
||||||
|
|
||||||
|
# automatic email address links
|
||||||
|
while (match(line, /<[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}>/)) {
|
||||||
|
start = RSTART; len = RLENGTH
|
||||||
|
email = substr(line, start + 1, len - 2)
|
||||||
|
repl = "<a href=\"mailto:" email "\">" email "</a>"
|
||||||
|
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
|
||||||
|
token = substr(line, start, len)
|
||||||
|
match(token, /\[[^\]]*\]/); alt = substr(token, RSTART + 1, RLENGTH - 2)
|
||||||
|
match(token, /"[^"]*"/); title = substr(token, RSTART + 1, RLENGTH - 2)
|
||||||
|
match(token, /\([^\)]+/); inner = substr(token, RSTART + 1, RLENGTH - 1)
|
||||||
|
sub(/[[:space:]]*"[^"]*"/, "", inner); src = inner
|
||||||
|
repl = "<img data-force-inline=\"1\" alt=\"" alt "\" src=\"" src "\" title=\"" title "\" />"
|
||||||
|
line = substr(line, 1, start - 1) repl substr(line, start + len)
|
||||||
|
}
|
||||||
|
while (match(line, /!!\[[^\]]*\]\([^\)]+\)/)) {
|
||||||
|
start = RSTART; len = RLENGTH
|
||||||
|
token = substr(line, start, len)
|
||||||
|
match(token, /\[[^\]]*\]/); alt = substr(token, RSTART + 1, RLENGTH - 2)
|
||||||
|
match(token, /\([^\)]+/); src = substr(token, RSTART + 1, RLENGTH - 1)
|
||||||
|
repl = "<img data-force-inline=\"1\" alt=\"" alt "\" src=\"" src "\" />"
|
||||||
|
line = substr(line, 1, start - 1) repl substr(line, start + len)
|
||||||
|
}
|
||||||
|
|
||||||
|
# inline image
|
||||||
|
while (match(line, /!\[[^\]]*\]\([^\)]+ "[^"]*"\)/)) {
|
||||||
|
start = RSTART; len = RLENGTH
|
||||||
|
if (start > 1 && substr(line, start - 1, 1) == "\\") break
|
||||||
|
token = substr(line, start, len)
|
||||||
|
match(token, /\[[^\]]*\]/); alt = substr(token, RSTART + 1, RLENGTH - 2)
|
||||||
|
match(token, /"[^"]*"/); title = substr(token, RSTART + 1, RLENGTH - 2)
|
||||||
|
match(token, /\([^\)]+/); inner = substr(token, RSTART + 1, RLENGTH - 1)
|
||||||
|
sub(/[[:space:]]*"[^"]*"/, "", inner); src = inner
|
||||||
|
repl = "<img alt=\"" alt "\" src=\"" src "\" title=\"" title "\" />"
|
||||||
|
line = substr(line, 1, start - 1) repl substr(line, start + len)
|
||||||
|
}
|
||||||
|
while (match(line, /!\[[^\]]*\]\([^\)]+\)/)) {
|
||||||
|
start = RSTART; len = RLENGTH
|
||||||
|
if (start > 1 && substr(line, start - 1, 1) == "\\") break
|
||||||
|
token = substr(line, start, len)
|
||||||
|
match(token, /\[[^\]]*\]/); alt = substr(token, RSTART + 1, RLENGTH - 2)
|
||||||
|
match(token, /\([^\)]+/); src = substr(token, RSTART + 1, RLENGTH - 1)
|
||||||
|
repl = "<img alt=\"" alt "\" src=\"" src "\" />"
|
||||||
|
line = substr(line, 1, start - 1) repl substr(line, start + len)
|
||||||
|
}
|
||||||
|
|
||||||
|
# inline link
|
||||||
|
while (match(line, /\[[^\]]*\]\([^\)]+ "[^"]*"\)/)) {
|
||||||
|
start = RSTART; len = RLENGTH
|
||||||
|
if (start > 1 && (substr(line, start - 1, 1) == "\\" || substr(line, start - 1, 1) == "!")) break
|
||||||
|
token = substr(line, start, len)
|
||||||
|
match(token, /\[[^\]]*\]/); text = substr(token, RSTART + 1, RLENGTH - 2)
|
||||||
|
match(token, /"[^"]*"/); title = substr(token, RSTART + 1, RLENGTH - 2)
|
||||||
|
match(token, /\([^\)]+/); inner = substr(token, RSTART + 1, RLENGTH - 1)
|
||||||
|
sub(/[[:space:]]*"[^"]*"/, "", inner); href = inner
|
||||||
|
repl = "<a href=\"" href "\" title=\"" title "\">" text "</a>"
|
||||||
|
line = substr(line, 1, start - 1) repl substr(line, start + len)
|
||||||
|
}
|
||||||
|
while (match(line, /\[[^\]]*\]\([^\)]+\)/)) {
|
||||||
|
start = RSTART; len = RLENGTH
|
||||||
|
if (start > 1 && (substr(line, start - 1, 1) == "\\" || substr(line, start - 1, 1) == "!")) break
|
||||||
|
token = substr(line, start, len)
|
||||||
|
match(token, /\[[^\]]*\]/); text = substr(token, RSTART + 1, RLENGTH - 2)
|
||||||
|
match(token, /\([^\)]+/); href = substr(token, RSTART + 1, RLENGTH - 1)
|
||||||
|
repl = "<a href=\"" href "\">" text "</a>"
|
||||||
|
line = substr(line, 1, start - 1) repl substr(line, start + len)
|
||||||
|
}
|
||||||
|
|
||||||
|
# MFM font syntax
|
||||||
|
while (match(line, /\$\[font\.serif [^\]]+\]/)) {
|
||||||
|
start = RSTART; len = RLENGTH
|
||||||
|
content = substr(line, start + 13, len - 14)
|
||||||
|
line = substr(line, 1, start - 1) "<span style=\"font-family: serif;\">" content "</span>" substr(line, start + len)
|
||||||
|
}
|
||||||
|
while (match(line, /\$\[font\.monospace [^\]]+\]/)) {
|
||||||
|
start = RSTART; len = RLENGTH
|
||||||
|
content = substr(line, start + 17, len - 18)
|
||||||
|
line = substr(line, 1, start - 1) "<span style=\"font-family: monospace;\">" content "</span>" substr(line, start + len)
|
||||||
|
}
|
||||||
|
while (match(line, /\$\[font\.sans [^\]]+\]/)) {
|
||||||
|
start = RSTART; len = RLENGTH
|
||||||
|
content = substr(line, start + 12, len - 13)
|
||||||
|
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, /\*\*[^*]+\*\*/)) {
|
||||||
|
start = RSTART; len = RLENGTH
|
||||||
|
content = substr(line, start + 2, len - 4)
|
||||||
|
repl = "<strong>" content "</strong>"
|
||||||
|
line = substr(line, 1, start - 1) repl substr(line, start + len)
|
||||||
|
}
|
||||||
|
# Strong Bold __
|
||||||
|
while (match(line, /__[^_]+__/)) {
|
||||||
|
start = RSTART; len = RLENGTH
|
||||||
|
content = substr(line, start + 2, len - 4)
|
||||||
|
repl = "<strong>" content "</strong>"
|
||||||
|
line = substr(line, 1, start - 1) repl substr(line, start + len)
|
||||||
|
}
|
||||||
|
# Italic *
|
||||||
|
while (match(line, /\*[^*]+\*/)) {
|
||||||
|
start = RSTART; len = RLENGTH
|
||||||
|
content = substr(line, start + 1, len - 2)
|
||||||
|
repl = "<em>" content "</em>"
|
||||||
|
line = substr(line, 1, start - 1) repl substr(line, start + len)
|
||||||
|
}
|
||||||
|
# Italic _
|
||||||
|
while (match(line, /_[^_]+_/)) {
|
||||||
|
start = RSTART; len = RLENGTH
|
||||||
|
if (start > 1 && substr(line, start - 1, 1) == "\\") break
|
||||||
|
content = substr(line, start + 1, len - 2)
|
||||||
|
repl = "<em>" content "</em>"
|
||||||
|
line = substr(line, 1, start - 1) repl substr(line, start + len)
|
||||||
|
}
|
||||||
|
# Strikethrough ~~
|
||||||
|
while (match(line, /~~[^~]+~~/)) {
|
||||||
|
start = RSTART; len = RLENGTH
|
||||||
|
content = substr(line, start + 2, len - 4)
|
||||||
|
repl = "<strike>" content "</strike>"
|
||||||
|
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(/&/, "&", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
p = 1
|
||||||
|
while (match(substr(line, p), /</)) {
|
||||||
|
start = p + RSTART - 1
|
||||||
|
next_char = substr(line, start + 1, 1)
|
||||||
|
if (next_char !~ /^[\/A-Za-z]/) {
|
||||||
|
line = substr(line, 1, start - 1) "<" substr(line, start + 1)
|
||||||
|
p = start + 4
|
||||||
|
} else {
|
||||||
|
p = start + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gsub(/<a href="https?:\/\/[^"]*"/, "& rel=\"noopener noreferrer\"", line)
|
||||||
|
|
||||||
|
print line
|
||||||
|
}
|
||||||
91
awk/mask_inline_code.awk
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
function mask(s, t) {
|
||||||
|
t = s
|
||||||
|
gsub(/\*/, "\034P0\034", t)
|
||||||
|
gsub(/_/, "\034P1\034", t)
|
||||||
|
gsub(/`/, "\034P2\034", t)
|
||||||
|
gsub(/\[/, "\034P3\034", t)
|
||||||
|
gsub(/\]/, "\034P4\034", t)
|
||||||
|
gsub(/\(/, "\034P5\034", t)
|
||||||
|
gsub(/\)/, "\034P6\034", t)
|
||||||
|
gsub(/!/, "\034P7\034", t)
|
||||||
|
gsub(/\$/, "\034P8\034", t)
|
||||||
|
gsub(/#/, "\034P9\034", t)
|
||||||
|
gsub(/\+/, "\034P10\034", t)
|
||||||
|
gsub(/-/, "\034P11\034", t)
|
||||||
|
gsub(/\\/, "\034P12\034", t)
|
||||||
|
gsub(/</, "\034P13\034", t)
|
||||||
|
gsub(/>/, "\034P14\034", t)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
{
|
||||||
|
# backslash escapes
|
||||||
|
gsub(/\\\*/, "\034P0\034")
|
||||||
|
gsub(/\\_/, "\034P1\034")
|
||||||
|
gsub(/\\`/, "\034P2\034")
|
||||||
|
gsub(/\\\[/, "\034P3\034")
|
||||||
|
gsub(/\\\]/, "\034P4\034")
|
||||||
|
gsub(/\\\(/, "\034P5\034")
|
||||||
|
gsub(/\\\)/, "\034P6\034")
|
||||||
|
gsub(/\\!/, "\034P7\034")
|
||||||
|
gsub(/\\\$/, "\034P8\034")
|
||||||
|
gsub(/\\#/, "\034P9\034")
|
||||||
|
gsub(/\\\+/, "\034P10\034")
|
||||||
|
gsub(/\\\-/, "\034P11\034")
|
||||||
|
gsub(/\\\\/, "\034P12\034")
|
||||||
|
gsub(/\\</, "\034P13\034")
|
||||||
|
gsub(/\\>/, "\034P14\034")
|
||||||
|
|
||||||
|
# inline code (1 or 2 backticks)
|
||||||
|
line = $0
|
||||||
|
if (line ~ /^```/) {
|
||||||
|
print line
|
||||||
|
next
|
||||||
|
}
|
||||||
|
out = ""
|
||||||
|
p = 1
|
||||||
|
while (match(substr(line, p), /`+/)) {
|
||||||
|
pstart = p + RSTART - 1
|
||||||
|
plen = RLENGTH
|
||||||
|
|
||||||
|
# Found backtick sequence at pstart
|
||||||
|
# Search for closing marker of same length
|
||||||
|
marker = substr(line, pstart, plen)
|
||||||
|
tail = substr(line, pstart + plen)
|
||||||
|
mpos = index(tail, marker)
|
||||||
|
if (mpos > 0) {
|
||||||
|
# Check if it is followed by more backticks
|
||||||
|
if (substr(tail, mpos + plen, 1) == "`") {
|
||||||
|
# Not a match, treat as literal
|
||||||
|
out = out substr(line, p, pstart - p + plen)
|
||||||
|
p = pstart + plen
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Found match!
|
||||||
|
content = substr(tail, 1, mpos - 1)
|
||||||
|
out = out substr(line, p, pstart - p)
|
||||||
|
if (plen >= 2 && substr(content, 1, 1) == " " && substr(content, length(content), 1) == " ") {
|
||||||
|
content = substr(content, 2, length(content) - 2)
|
||||||
|
}
|
||||||
|
if (content ~ /!!\[/) {
|
||||||
|
_rb_test = content
|
||||||
|
gsub(/!!\[[^\]]*\]\([^)]*\)/, "", _rb_test)
|
||||||
|
gsub(/!!\[[^\]]+\]/, "", _rb_test)
|
||||||
|
gsub(/[[:space:]]+/, "", _rb_test)
|
||||||
|
if (_rb_test == "") {
|
||||||
|
out = out content
|
||||||
|
p = pstart + plen + mpos + plen - 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out = out "<code>" mask(content) "</code>"
|
||||||
|
p = pstart + plen + mpos + plen - 1
|
||||||
|
} else {
|
||||||
|
# No closing marker, treat as literal
|
||||||
|
out = out substr(line, p, pstart - p + plen)
|
||||||
|
p = pstart + plen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out = out substr(line, p)
|
||||||
|
print out
|
||||||
|
}
|
||||||
88
awk/mask_plain.awk
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
function find_unescaped_tag(s, tag, p, off, pos) {
|
||||||
|
p = 1
|
||||||
|
while (1) {
|
||||||
|
off = index(substr(s, p), tag)
|
||||||
|
if (off == 0) return 0
|
||||||
|
pos = p + off - 1
|
||||||
|
if (pos == 1 || substr(s, pos - 1, 1) != "\\") return pos
|
||||||
|
p = pos + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mask_plain(s, t) {
|
||||||
|
t = s
|
||||||
|
gsub(/\*/, "\034P0\034", t)
|
||||||
|
gsub(/_/, "\034P1\034", t)
|
||||||
|
gsub(/`/, "\034P2\034", t)
|
||||||
|
gsub(/\[/, "\034P3\034", t)
|
||||||
|
gsub(/\]/, "\034P4\034", t)
|
||||||
|
gsub(/\(/, "\034P5\034", t)
|
||||||
|
gsub(/\)/, "\034P6\034", t)
|
||||||
|
gsub(/!/, "\034P7\034", t)
|
||||||
|
gsub(/\$/, "\034P8\034", t)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
BEGIN { in_plain = 0; in_script_style = 0 }
|
||||||
|
{
|
||||||
|
line = $0
|
||||||
|
out = ""
|
||||||
|
while (1) {
|
||||||
|
if (!in_plain) {
|
||||||
|
pos = find_unescaped_tag(line, "<plain>")
|
||||||
|
if (pos == 0) {
|
||||||
|
out = out line
|
||||||
|
break
|
||||||
|
}
|
||||||
|
out = out substr(line, 1, pos - 1) "<mfmplain>"
|
||||||
|
line = substr(line, pos + 7)
|
||||||
|
in_plain = 1
|
||||||
|
} else {
|
||||||
|
pos = find_unescaped_tag(line, "</plain>")
|
||||||
|
if (pos == 0) {
|
||||||
|
out = out mask_plain(line)
|
||||||
|
line = ""
|
||||||
|
break
|
||||||
|
}
|
||||||
|
out = out mask_plain(substr(line, 1, pos - 1)) "</mfmplain>"
|
||||||
|
line = substr(line, pos + 8)
|
||||||
|
in_plain = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tmp_line = out
|
||||||
|
out2 = ""
|
||||||
|
while (1) {
|
||||||
|
if (!in_script_style) {
|
||||||
|
pos_script = match(tolower(tmp_line), /<script([ >]|$)/)
|
||||||
|
script_start = RSTART; script_len = RLENGTH
|
||||||
|
pos_style = match(tolower(tmp_line), /<style([ >]|$)/)
|
||||||
|
style_start = RSTART; style_len = RLENGTH
|
||||||
|
|
||||||
|
if (pos_script == 0 && pos_style == 0) {
|
||||||
|
out2 = out2 tmp_line
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (pos_script > 0 && (pos_style == 0 || pos_script < pos_style)) {
|
||||||
|
out2 = out2 substr(tmp_line, 1, script_start + script_len - 1)
|
||||||
|
tmp_line = substr(tmp_line, script_start + script_len)
|
||||||
|
in_script_style = 1
|
||||||
|
end_tag = "</script>"
|
||||||
|
} else {
|
||||||
|
out2 = out2 substr(tmp_line, 1, style_start + style_len - 1)
|
||||||
|
tmp_line = substr(tmp_line, style_start + style_len)
|
||||||
|
in_script_style = 1
|
||||||
|
end_tag = "</style>"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pos_end = match(tolower(tmp_line), end_tag)
|
||||||
|
if (pos_end == 0) {
|
||||||
|
out2 = out2 mask_plain(tmp_line)
|
||||||
|
tmp_line = ""
|
||||||
|
break
|
||||||
|
}
|
||||||
|
out2 = out2 mask_plain(substr(tmp_line, 1, RSTART - 1)) substr(tmp_line, RSTART, RLENGTH)
|
||||||
|
tmp_line = substr(tmp_line, RSTART + RLENGTH)
|
||||||
|
in_script_style = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print out2
|
||||||
|
}
|
||||||
52
awk/paragraphs.awk
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
BEGIN {
|
||||||
|
in_p = 0
|
||||||
|
in_pre = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
if ($0 ~ /<pre>/) in_pre = 1
|
||||||
|
|
||||||
|
if (in_pre) {
|
||||||
|
if (in_p) { print "</p>"; in_p = 0 }
|
||||||
|
print
|
||||||
|
if ($0 ~ /<\/pre>/) in_pre = 0
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($0 ~ /^<\/?(div|table|p|[ou]l|h[1-6]|[bh]r|blockquote|li|hr|section|article|nav|aside|header|footer|dl|dt|dd|script|style|iframe|details|summary|figure|figcaption|audio|video|picture)/) {
|
||||||
|
if (in_p) {
|
||||||
|
print "</p>"
|
||||||
|
in_p = 0
|
||||||
|
}
|
||||||
|
print
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($0 ~ /^[\t ]*!([a-zA-Z])?\[[^\]]*\](\([^)]*\))?[\t ]*$/ || $0 ~ /^[\t ]*!!?\[[^\]]*\](\([^)]*\))?[\t ]*$/) {
|
||||||
|
if (in_p) {
|
||||||
|
print "</p>"
|
||||||
|
in_p = 0
|
||||||
|
}
|
||||||
|
print
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($0 == "") {
|
||||||
|
if (in_p) {
|
||||||
|
print "</p>"
|
||||||
|
in_p = 0
|
||||||
|
}
|
||||||
|
print
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_p) {
|
||||||
|
print "<p>"
|
||||||
|
in_p = 1
|
||||||
|
}
|
||||||
|
print
|
||||||
|
}
|
||||||
|
|
||||||
|
END {
|
||||||
|
if (in_p) print "</p>"
|
||||||
|
}
|
||||||
143
awk/pipe_tables.awk
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
function trim(s) {
|
||||||
|
sub(/^[[:space:]]+/, "", s)
|
||||||
|
sub(/[[:space:]]+$/, "", s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function is_table_row(line, t) {
|
||||||
|
t = line
|
||||||
|
return (t ~ /^[[:space:]]*\|/ && t ~ /\|[[:space:]]*$/)
|
||||||
|
}
|
||||||
|
|
||||||
|
function is_table_sep(line, t) {
|
||||||
|
if (!is_table_row(line)) return 0
|
||||||
|
t = line
|
||||||
|
gsub(/[|:\-[:space:]]/, "", t)
|
||||||
|
return (t == "" && line ~ /-/)
|
||||||
|
}
|
||||||
|
|
||||||
|
function split_row(line, out, n, i, raw) {
|
||||||
|
raw = line
|
||||||
|
sub(/^[[:space:]]*\|/, "", raw)
|
||||||
|
sub(/\|[[:space:]]*$/, "", raw)
|
||||||
|
n = split(raw, out, /\|/)
|
||||||
|
for (i = 1; i <= n; i++) out[i] = trim(out[i])
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
function align_for(sep, t) {
|
||||||
|
t = trim(sep)
|
||||||
|
if (t ~ /^:-+:$/) return "center"
|
||||||
|
if (t ~ /^:-+$/) return "left"
|
||||||
|
if (t ~ /^-+:$/) return "right"
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function render_cell(cell, inner) {
|
||||||
|
inner = trim(cell)
|
||||||
|
if (inner ~ /^```.*```$/) {
|
||||||
|
sub(/^```[[:space:]]*/, "", inner)
|
||||||
|
sub(/[[:space:]]*```$/, "", inner)
|
||||||
|
return "<pre><code>" inner "</code></pre>"
|
||||||
|
}
|
||||||
|
return inner
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN { count = 0 }
|
||||||
|
{ lines[++count] = $0 }
|
||||||
|
|
||||||
|
END {
|
||||||
|
in_pre = 0
|
||||||
|
i = 1
|
||||||
|
while (i <= count) {
|
||||||
|
if (lines[i] ~ /^<pre><code/) {
|
||||||
|
in_pre = 1
|
||||||
|
print lines[i]
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (in_pre) {
|
||||||
|
print lines[i]
|
||||||
|
if (lines[i] ~ /^<\/code><\/pre>/) in_pre = 0
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < count && is_table_row(lines[i]) && is_table_sep(lines[i + 1])) {
|
||||||
|
n_header = split_row(lines[i], header)
|
||||||
|
n_sep = split_row(lines[i + 1], sep)
|
||||||
|
n_cols = (n_header > n_sep ? n_header : n_sep)
|
||||||
|
|
||||||
|
print "<table>"
|
||||||
|
print "<thead>"
|
||||||
|
print "<tr>"
|
||||||
|
for (c = 1; c <= n_cols; c++) {
|
||||||
|
cell = (c <= n_header ? render_cell(header[c]) : "")
|
||||||
|
a = (c <= n_sep ? align_for(sep[c]) : "")
|
||||||
|
if (a != "") print "<th style=\"text-align: " a ";\">" cell "</th>"
|
||||||
|
else print "<th>" cell "</th>"
|
||||||
|
}
|
||||||
|
print "</tr>"
|
||||||
|
print "</thead>"
|
||||||
|
|
||||||
|
j = i + 2
|
||||||
|
print "<tbody>"
|
||||||
|
while (j <= count && is_table_row(lines[j])) {
|
||||||
|
n_body = split_row(lines[j], body)
|
||||||
|
print "<tr>"
|
||||||
|
for (c = 1; c <= n_cols; c++) {
|
||||||
|
cell = (c <= n_body ? render_cell(body[c]) : "")
|
||||||
|
a = (c <= n_sep ? align_for(sep[c]) : "")
|
||||||
|
if (a != "") print "<td style=\"text-align: " a ";\">" cell "</td>"
|
||||||
|
else print "<td>" cell "</td>"
|
||||||
|
}
|
||||||
|
print "</tr>"
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
print "</tbody>"
|
||||||
|
print "</table>"
|
||||||
|
|
||||||
|
i = j
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_table_sep(lines[i]) && i < count && is_table_row(lines[i + 1])) {
|
||||||
|
n_sep = split_row(lines[i], sep)
|
||||||
|
n_cols = n_sep
|
||||||
|
|
||||||
|
print "<table>"
|
||||||
|
print "<thead>"
|
||||||
|
print "<tr>"
|
||||||
|
for (c = 1; c <= n_cols; c++) {
|
||||||
|
a = align_for(sep[c])
|
||||||
|
if (a != "") print "<th style=\"text-align: " a ";\"></th>"
|
||||||
|
else print "<th></th>"
|
||||||
|
}
|
||||||
|
print "</tr>"
|
||||||
|
print "</thead>"
|
||||||
|
|
||||||
|
j = i + 1
|
||||||
|
print "<tbody>"
|
||||||
|
while (j <= count && is_table_row(lines[j])) {
|
||||||
|
n_body = split_row(lines[j], body)
|
||||||
|
print "<tr>"
|
||||||
|
for (c = 1; c <= n_cols; c++) {
|
||||||
|
cell = (c <= n_body ? render_cell(body[c]) : "")
|
||||||
|
a = (c <= n_sep ? align_for(sep[c]) : "")
|
||||||
|
if (a != "") print "<td style=\"text-align: " a ";\">" cell "</td>"
|
||||||
|
else print "<td>" cell "</td>"
|
||||||
|
}
|
||||||
|
print "</tr>"
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
print "</tbody>"
|
||||||
|
print "</table>"
|
||||||
|
|
||||||
|
i = j
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
print lines[i]
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,41 @@
|
|||||||
function replace_all(text, token, value, pos, token_len) {
|
function replace_all(text, token, value, pos, token_len, res) {
|
||||||
token_len = length(token)
|
token_len = length(token)
|
||||||
|
res = ""
|
||||||
while ((pos = index(text, token)) > 0) {
|
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"]
|
||||||
|
header_search = ENVIRON["AWK_HEADER_SEARCH"]
|
||||||
|
lang = ENVIRON["AWK_LANG"]
|
||||||
|
version = ENVIRON["AWK_VERSION"]
|
||||||
|
content_warning = ENVIRON["AWK_CONTENT_WARNING"]
|
||||||
|
if (current_url != "") {
|
||||||
|
nav = replace_all(nav, "href=\"" current_url "\"", "href=\"" current_url "\" class=\"current-page\"")
|
||||||
}
|
}
|
||||||
return text
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
line = $0
|
line = $0
|
||||||
line = replace_all(line, "{{TITLE}}", title)
|
line = replace_all(line, "{{TITLE}}", title)
|
||||||
|
line = replace_all(line, "{{LANG}}", lang)
|
||||||
line = replace_all(line, "{{NAV}}", nav)
|
line = replace_all(line, "{{NAV}}", nav)
|
||||||
line = replace_all(line, "{{FOOTER}}", footer)
|
line = replace_all(line, "{{FOOTER}}", footer)
|
||||||
line = replace_all(line, "{{CSS}}", style_path)
|
line = replace_all(line, "{{CSS}}", style_path)
|
||||||
line = replace_all(line, "{{HEAD_EXTRA}}", head_extra)
|
line = replace_all(line, "{{HEAD_EXTRA}}", head_extra)
|
||||||
line = replace_all(line, "{{HEADER_BRAND}}", header_brand)
|
line = replace_all(line, "{{HEADER_BRAND}}", header_brand)
|
||||||
|
line = replace_all(line, "{{HEADER_SEARCH}}", header_search)
|
||||||
|
line = replace_all(line, "{{VERSION}}", version)
|
||||||
|
|
||||||
pos = index(line, "{{CONTENT}}")
|
pos = index(line, "{{CONTENT}}")
|
||||||
if (pos > 0) {
|
if (pos > 0) {
|
||||||
|
|||||||
50
awk/toc.awk
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
BEGIN {
|
||||||
|
toc_str = "<ol class=\"toc\">\n"
|
||||||
|
has_toc = 0
|
||||||
|
}
|
||||||
|
{
|
||||||
|
lines[++n] = $0
|
||||||
|
if ($0 ~ /<pre>/) in_pre = 1
|
||||||
|
if (!in_pre && $0 ~ /\{\{TOC\}\}/) {
|
||||||
|
has_toc = 1
|
||||||
|
toc_lines[n] = 1
|
||||||
|
}
|
||||||
|
if ($0 ~ /<\/pre>/) in_pre = 0
|
||||||
|
if (match($0, /<h[23][^>]*>/)) {
|
||||||
|
tag_len = RLENGTH
|
||||||
|
title_start = RSTART + tag_len
|
||||||
|
title_str = substr($0, title_start)
|
||||||
|
title_end = index(title_str, "</h")
|
||||||
|
if (title_end > 0) {
|
||||||
|
title = substr(title_str, 1, title_end - 1)
|
||||||
|
gsub(/<[^>]+>/, "", title)
|
||||||
|
|
||||||
|
# extract id
|
||||||
|
id_start = match($0, /id="[^"]*"/)
|
||||||
|
if (id_start > 0) {
|
||||||
|
id_str = substr($0, id_start + 4)
|
||||||
|
id_end = index(id_str, "\"")
|
||||||
|
id = substr(id_str, 1, id_end - 1)
|
||||||
|
|
||||||
|
# what tag? level
|
||||||
|
level = substr($0, match($0, /<h[23]/) + 2, 1)
|
||||||
|
|
||||||
|
if (level == "2") {
|
||||||
|
toc_str = toc_str "<li class=\"toc-h2\"><a href=\"#" id "\">" title "</a></li>\n"
|
||||||
|
} else if (level == "3") {
|
||||||
|
toc_str = toc_str "<li class=\"toc-h3\"><a href=\"#" id "\">" title "</a></li>\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
toc_str = toc_str "</ol>"
|
||||||
|
for (i = 1; i <= n; i++) {
|
||||||
|
if (has_toc && toc_lines[i] && lines[i] ~ /^[[:space:]]*\{\{TOC\}\}[[:space:]]*$/) {
|
||||||
|
toc_lines[i] = 0 # Mark as processed if we want, but not strictly needed
|
||||||
|
sub(/\{\{TOC\}\}/, toc_str, lines[i])
|
||||||
|
}
|
||||||
|
print lines[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
13
awk/update_site_conf.awk
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
BEGIN {
|
||||||
|
new_title = ENVIRON["AWK_NEW_TITLE"]
|
||||||
|
done = 0
|
||||||
|
}
|
||||||
|
/^title[[:space:]]*=/ {
|
||||||
|
print "title = \"" new_title "\""
|
||||||
|
done = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
END {
|
||||||
|
if (!done) print "title = \"" new_title "\""
|
||||||
|
}
|
||||||
BIN
button.gif
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
49
button.svg
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="88"
|
||||||
|
height="31"
|
||||||
|
viewBox="0 0 88 31"
|
||||||
|
>
|
||||||
|
<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="88" height="31" fill="#000" />
|
||||||
|
<rect x="1" y="1" width="86" height="29" fill="url(#bg-grad)" />
|
||||||
|
<text
|
||||||
|
x="5"
|
||||||
|
y="12"
|
||||||
|
font-family="Georgia, 'Times New Roman', Times, serif"
|
||||||
|
font-size="10"
|
||||||
|
font-weight="bold"
|
||||||
|
fill="#debfff"
|
||||||
|
style="letter-spacing: -0.5px;"
|
||||||
|
>made</text>
|
||||||
|
<text
|
||||||
|
x="5"
|
||||||
|
y="22"
|
||||||
|
font-family="Georgia, 'Times New Roman', Times, serif"
|
||||||
|
font-size="10"
|
||||||
|
font-weight="bold"
|
||||||
|
fill="#debfff"
|
||||||
|
style="letter-spacing: -0.5px;"
|
||||||
|
>with</text>
|
||||||
|
<text
|
||||||
|
x="80"
|
||||||
|
y="16"
|
||||||
|
text-anchor="end"
|
||||||
|
dominant-baseline="central"
|
||||||
|
font-family="Georgia, 'Times New Roman', Times, serif"
|
||||||
|
font-size="28"
|
||||||
|
font-weight="bold"
|
||||||
|
font-style="italic"
|
||||||
|
fill="#debfff"
|
||||||
|
letter-spacing="-2"
|
||||||
|
>kewt</text>
|
||||||
|
<rect x="1" y="1" width="86" height="1" fill="#ffffff" fill-opacity="0.2" />
|
||||||
|
<rect x="1" y="2" width="1" height="27" fill="#ffffff" fill-opacity="0.2" />
|
||||||
|
<rect x="1" y="29" width="86" height="1" fill="#000000" fill-opacity="0.4" />
|
||||||
|
<rect x="86" y="2" width="1" height="28" fill="#000000" fill-opacity="0.4" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
21
icon.svg
Normal 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 |
461
kewt.sh
@@ -5,110 +5,46 @@ die() {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
usage() {
|
make_temp_dir() {
|
||||||
cat <<EOF
|
_temp_base="${TMPDIR:-/tmp}/kewt_run.$$"
|
||||||
Usage: $0 [--from <src>] [--to <out>]
|
_temp_try=0
|
||||||
$0 [src] [out]
|
while [ "$_temp_try" -lt 1000 ]; do
|
||||||
$0 --new [title]
|
_temp_path="${_temp_base}.${_temp_try}"
|
||||||
$0 --help
|
if (umask 077 && mkdir "$_temp_path") 2>/dev/null; then
|
||||||
|
printf '%s\n' "$_temp_path"
|
||||||
Options:
|
return 0
|
||||||
--help Show this help message.
|
fi
|
||||||
--new [title] Create a new site directory (default: site)
|
_temp_try=$((_temp_try + 1))
|
||||||
--from <src> Source directory (default: site)
|
done
|
||||||
--to <out> Output directory (default: out)
|
return 1
|
||||||
EOF
|
|
||||||
}
|
}
|
||||||
|
|
||||||
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
script_dir=$(CDPATH="" cd -- "$(dirname -- "$0")" && pwd)
|
||||||
awk_dir="$script_dir/awk"
|
awk_dir="$script_dir/awk"
|
||||||
|
|
||||||
ensure_root_defaults() {
|
KEWT_TMPDIR=$(make_temp_dir) || die "Could not create temporary directory."
|
||||||
if [ ! -f "./site.conf" ]; then
|
trap 'rm -rf "$KEWT_TMPDIR"' EXIT
|
||||||
cat > "./site.conf" <<'EOF'
|
trap 'exit 0' HUP INT TERM
|
||||||
title = "kewt"
|
|
||||||
style = "kewt"
|
|
||||||
dir_indexes = true
|
|
||||||
single_file_index = true
|
|
||||||
flatten = false
|
|
||||||
footer = "made with <a href="https://kewt.krzak.org">kewt</a>"
|
|
||||||
logo = ""
|
|
||||||
display_logo = false
|
|
||||||
display_title = true
|
|
||||||
logo_as_favicon = true
|
|
||||||
favicon = ""
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f "./template.html" ]; then
|
. "$script_dir/lib/config.sh"
|
||||||
cat > "./template.html" <<'EOF'
|
. "$script_dir/lib/metadata.sh"
|
||||||
<!doctype html>
|
. "$script_dir/lib/manifest.sh"
|
||||||
<html>
|
. "$script_dir/lib/commands.sh"
|
||||||
<head>
|
. "$script_dir/lib/generator.sh"
|
||||||
<meta charset="UTF-8" />
|
. "$script_dir/lib/builder.sh"
|
||||||
<title>{{TITLE}}</title>
|
. "$script_dir/lib/runtime.sh"
|
||||||
|
|
||||||
<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
|
|
||||||
}
|
|
||||||
|
|
||||||
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" '
|
|
||||||
BEGIN { done = 0 }
|
|
||||||
/^title[[:space:]]*=/ {
|
|
||||||
print "title = \"" new_title "\""
|
|
||||||
done = 1
|
|
||||||
next
|
|
||||||
}
|
|
||||||
{ print }
|
|
||||||
END {
|
|
||||||
if (!done) print "title = \"" new_title "\""
|
|
||||||
}
|
|
||||||
' "$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'."
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
generate_nav() {
|
|
||||||
dinfo=$(find "$1" -not -path '*/.*' | sort -r | awk -v src="$1" -f "$awk_dir/collect_dir_info.awk")
|
|
||||||
find "$1" -name "*.md" | sort | awk -v src="$1" -v single_file_index="$single_file_index" -v flatten="$flatten" -v dinfo="$dinfo" -f "$awk_dir/generate_sidebar.awk"
|
|
||||||
}
|
|
||||||
|
|
||||||
src=""
|
src=""
|
||||||
out=""
|
out=""
|
||||||
new_mode="false"
|
new_mode="false"
|
||||||
new_title=""
|
new_title=""
|
||||||
|
clean_mode="true"
|
||||||
|
post_mode="false"
|
||||||
|
post_title=""
|
||||||
positional_count=0
|
positional_count=0
|
||||||
|
watch_mode="false"
|
||||||
|
serve_mode="false"
|
||||||
|
serve_port=""
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -116,13 +52,75 @@ while [ $# -gt 0 ]; do
|
|||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
--new)
|
--new|--init)
|
||||||
new_mode="true"
|
new_mode="true"
|
||||||
if [ $# -gt 1 ] && [ "${2#-}" = "$2" ]; then
|
if [ $# -gt 1 ] && [ "${2#-}" = "$2" ]; then
|
||||||
new_title="$2"
|
new_title="$2"
|
||||||
shift
|
shift
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
--no-clean)
|
||||||
|
clean_mode="false"
|
||||||
|
;;
|
||||||
|
--clean)
|
||||||
|
clean_mode="true"
|
||||||
|
;;
|
||||||
|
--version|-v)
|
||||||
|
echo "kewt version git"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--dump-zsh-completions)
|
||||||
|
cat <<'EOFCOMPS'
|
||||||
|
#compdef kewt
|
||||||
|
_kewt() {
|
||||||
|
local -a args
|
||||||
|
args=(
|
||||||
|
'--help[Show help message]'
|
||||||
|
'(-h)--help[Show help message]'
|
||||||
|
'(-)--new[Create a new site directory]'
|
||||||
|
'(-)--init[Create a new site directory (alias for --new)]'
|
||||||
|
'(-)--clean[Clean the output directory before building]'
|
||||||
|
'(-)--no-clean[Do not clean the output directory before building]'
|
||||||
|
'(-)--update[Update site.conf and template.html with latest defaults]'
|
||||||
|
'(-)--post[Create a new empty post file in the configured posts_dir]'
|
||||||
|
'(-)--generate-template[Generate a new template file]'
|
||||||
|
'(-v --version)'{-v,--version}'[Show version information]'
|
||||||
|
'--from[Source directory]:directory:_directories'
|
||||||
|
'--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:'
|
||||||
|
)
|
||||||
|
|
||||||
|
_arguments -S -C $args '*: :_directories'
|
||||||
|
}
|
||||||
|
|
||||||
|
_kewt "$@"
|
||||||
|
EOFCOMPS
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--post)
|
||||||
|
post_mode="true"
|
||||||
|
if [ $# -gt 1 ] && [ "${2#-}" = "$2" ]; then
|
||||||
|
post_title="$2"
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
--generate-template)
|
||||||
|
generate_template_path="template.html"
|
||||||
|
if [ $# -gt 1 ] && [ "${2#-}" = "$2" ]; then
|
||||||
|
generate_template_path="$2"
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
generate_template "$generate_template_path"
|
||||||
|
;;
|
||||||
|
--update)
|
||||||
|
update_dir="."
|
||||||
|
if [ $# -gt 1 ] && [ "${2#-}" = "$2" ]; then
|
||||||
|
update_dir="$2"
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
update_site "$update_dir"
|
||||||
|
;;
|
||||||
--from)
|
--from)
|
||||||
[ $# -lt 2 ] && die "--from requires a value."
|
[ $# -lt 2 ] && die "--from requires a value."
|
||||||
src="$2"
|
src="$2"
|
||||||
@@ -133,15 +131,33 @@ while [ $# -gt 0 ]; do
|
|||||||
out="$2"
|
out="$2"
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
--watch|-w)
|
||||||
|
watch_mode="true"
|
||||||
|
;;
|
||||||
|
--serve|-s)
|
||||||
|
serve_mode="true"
|
||||||
|
if [ $# -ge 2 ] && echo "$2" | grep -qE '^[0-9]+$'; then
|
||||||
|
serve_port="$2"
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
;;
|
||||||
--*)
|
--*)
|
||||||
die "Unknown option: $1"
|
die "Unknown option: $1"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
positional_count=$((positional_count + 1))
|
positional_count=$((positional_count + 1))
|
||||||
if [ "$positional_count" -eq 1 ]; then
|
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
|
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
|
else
|
||||||
die "Too many positional arguments."
|
die "Too many positional arguments."
|
||||||
fi
|
fi
|
||||||
@@ -152,220 +168,81 @@ done
|
|||||||
|
|
||||||
[ "$new_mode" = "true" ] && create_new_site "$new_title"
|
[ "$new_mode" = "true" ] && create_new_site "$new_title"
|
||||||
|
|
||||||
ensure_root_defaults
|
if [ -z "$src" ]; then
|
||||||
|
if [ "$post_mode" = "true" ] && [ -f "./site.conf" ]; then
|
||||||
[ -z "$src" ] && src="site"
|
src="."
|
||||||
|
else
|
||||||
|
src="site"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
[ -z "$out" ] && out="out"
|
[ -z "$out" ] && out="out"
|
||||||
|
|
||||||
[ -d "$src" ] || die "Source directory '$src' does not exist."
|
src="${src%/}"
|
||||||
|
out="${out%/}"
|
||||||
|
|
||||||
title="kewt"
|
if [ ! -d "$src" ]; then
|
||||||
style="kewt"
|
if [ "$src" = "site" ]; then
|
||||||
footer="made with <a href=\"https://kewt.krzak.org\">kewt</a>"
|
usage
|
||||||
dir_indexes="true"
|
exit 1
|
||||||
single_file_index="true"
|
fi
|
||||||
flatten="false"
|
die "Source directory '$src' does not exist."
|
||||||
logo=""
|
fi
|
||||||
display_logo="false"
|
|
||||||
display_title="true"
|
|
||||||
logo_as_favicon="true"
|
|
||||||
favicon=""
|
|
||||||
|
|
||||||
load_config() {
|
BASE_IGNORE_ARGS=$(build_rule_args "$src" ".kewtignore" "-name '.kewtignore' -o -path '$src/.*'")
|
||||||
[ -f "$1" ] || return
|
BASE_HIDE_ARGS=$(build_rule_args "$src" ".kewthide" "-name '.kewtignore' -o -name '.kewthide' -o -name '.kewtpreserve' -o -path '$src/.*'")
|
||||||
while IFS= read -r line; do
|
PRESERVE_ARGS=$(build_rule_args "$src" ".kewtpreserve" "-false")
|
||||||
case "$line" in
|
IGNORE_ARGS="$BASE_IGNORE_ARGS"
|
||||||
''|'#'*) continue ;;
|
HIDE_ARGS="$BASE_HIDE_ARGS"
|
||||||
*=*) ;;
|
|
||||||
*) continue ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
key=${line%%=*}
|
refresh_build_context
|
||||||
val=${line#*=}
|
|
||||||
|
|
||||||
key=$(printf '%s' "$key" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
|
[ "$post_mode" = "true" ] && create_new_post "$src" "$post_title"
|
||||||
val=$(printf '%s' "$val" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
|
|
||||||
case "$val" in
|
|
||||||
\"*\") val=${val#\"}; val=${val%\"} ;;
|
|
||||||
\'*\') val=${val#\'}; val=${val%\'} ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
case "$key" in
|
|
||||||
title) title="$val" ;;
|
|
||||||
style) style="$val" ;;
|
|
||||||
dir_indexes) dir_indexes="$val" ;;
|
|
||||||
single_file_index) single_file_index="$val" ;;
|
|
||||||
flatten) flatten="$val" ;;
|
|
||||||
footer) footer="$val" ;;
|
|
||||||
logo) logo="$val" ;;
|
|
||||||
display_logo) display_logo="$val" ;;
|
|
||||||
display_title) display_title="$val" ;;
|
|
||||||
logo_as_favicon) logo_as_favicon="$val" ;;
|
|
||||||
favicon) favicon="$val" ;;
|
|
||||||
esac
|
|
||||||
done < "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
load_config "./site.conf"
|
|
||||||
load_config "$src/site.conf"
|
|
||||||
|
|
||||||
template="$src/template.html"
|
|
||||||
[ -f "$template" ] || template="./template.html"
|
|
||||||
[ -f "$template" ] || die "Template '$template' not found."
|
|
||||||
|
|
||||||
|
if [ "$clean_mode" = "true" ]; then
|
||||||
[ -d "$out" ] && rm -rf "$out"
|
[ -d "$out" ] && rm -rf "$out"
|
||||||
|
fi
|
||||||
mkdir -p "$out"
|
mkdir -p "$out"
|
||||||
|
|
||||||
nav=$(generate_nav "$src")
|
build_site
|
||||||
|
|
||||||
find_closest() {
|
if [ "$serve_mode" = "true" ]; then
|
||||||
target="$1"
|
port="${serve_port:-8000}"
|
||||||
start_dir="$2"
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
curr="$start_dir"
|
python3 -m http.server "$port" -d "$out" >/dev/null 2>&1 &
|
||||||
while [ "$curr" != "$src" ] && [ "$curr" != "." ] && [ "$curr" != "/" ]; do
|
server_pid=$!
|
||||||
if [ -f "$curr/$target" ]; then
|
echo "Serving '$out' on http://localhost:$port (python3)"
|
||||||
echo "$curr/$target"
|
elif command -v busybox >/dev/null 2>&1; then
|
||||||
return
|
busybox httpd -f -p "$port" -h "$out" >/dev/null 2>&1 &
|
||||||
fi
|
server_pid=$!
|
||||||
curr=$(dirname "$curr")
|
echo "Serving '$out' on http://localhost:$port (busybox)"
|
||||||
done
|
|
||||||
if [ -f "$src/$target" ]; then
|
|
||||||
echo "$src/$target"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
copy_style_with_resolved_vars() {
|
|
||||||
src_style="$1"
|
|
||||||
out_style="$2"
|
|
||||||
awk -f "$awk_dir/replace_variables.awk" "$src_style" > "$out_style"
|
|
||||||
}
|
|
||||||
|
|
||||||
render_markdown() {
|
|
||||||
file="$1"
|
|
||||||
local_template=$(find_closest "template.html" "$(dirname "$file")")
|
|
||||||
[ -z "$local_template" ] && local_template="$template"
|
|
||||||
|
|
||||||
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/}"
|
|
||||||
case "$closest_style_src" in
|
|
||||||
"$src/styles.css") style_rel_to_src="styles.css" ;;
|
|
||||||
"$src/style.css") style_rel_to_src="style.css" ;;
|
|
||||||
esac
|
|
||||||
style_path="/${style_rel_to_src%styles.css}"
|
|
||||||
style_path="${style_path%style.css}styles.css"
|
|
||||||
else
|
else
|
||||||
style_path="/styles.css"
|
die "Neither python3 nor busybox httpd is available to serve."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
logo_html=""
|
trap 'kill $server_pid 2>/dev/null; rm -rf "$KEWT_TMPDIR"' EXIT
|
||||||
if [ "$display_logo" = "true" ] && [ -n "$logo" ]; then
|
trap 'kill $server_pid 2>/dev/null; exit 0' HUP INT TERM
|
||||||
logo_html="<img class=\"site-logo\" src=\"$logo\" alt=\"$title\" />"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
brand_text=""
|
if [ "$watch_mode" = "true" ]; then
|
||||||
if [ "$display_title" = "true" ]; then
|
echo "Watching for changes in '$src'..."
|
||||||
brand_text="$title"
|
touch "$KEWT_TMPDIR/watch_mark"
|
||||||
|
while true; do
|
||||||
|
sleep 1
|
||||||
|
changed=$(watch_for_changes "$KEWT_TMPDIR/watch_mark")
|
||||||
|
|
||||||
|
if [ -n "$changed" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "Change detected, rebuilding..."
|
||||||
|
|
||||||
|
if [ "$clean_mode" = "true" ]; then
|
||||||
|
find "$out" -mindepth 1 -delete 2>/dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$logo_html" ] && [ -n "$brand_text" ]; then
|
refresh_build_context
|
||||||
header_brand="<a href=\"/index.html\">$logo_html $brand_text</a>"
|
build_site
|
||||||
elif [ -n "$logo_html" ]; then
|
touch "$KEWT_TMPDIR/watch_mark"
|
||||||
header_brand="<a href=\"/index.html\">$logo_html</a>"
|
|
||||||
elif [ -n "$brand_text" ]; then
|
|
||||||
header_brand="<a href=\"/index.html\">$brand_text</a>"
|
|
||||||
else
|
|
||||||
header_brand="<a href=\"/index.html\">$title</a>"
|
|
||||||
fi
|
|
||||||
|
|
||||||
favicon_src=""
|
|
||||||
if [ "$logo_as_favicon" = "true" ] && [ -n "$logo" ]; then
|
|
||||||
favicon_src="$logo"
|
|
||||||
elif [ -n "$favicon" ]; then
|
|
||||||
favicon_src="$favicon"
|
|
||||||
fi
|
|
||||||
head_extra=""
|
|
||||||
if [ -n "$favicon_src" ]; then
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Building site from '$src' to '$out'..."
|
|
||||||
|
|
||||||
find "$src" -type d | sort | while read -r dir; do
|
|
||||||
rel_dir="${dir#$src/}"
|
|
||||||
[ "$dir" = "$src" ] && rel_dir="."
|
|
||||||
out_dir="$out/$rel_dir"
|
|
||||||
mkdir -p "$out_dir"
|
|
||||||
|
|
||||||
if [ -f "$dir/styles.css" ]; then
|
|
||||||
copy_style_with_resolved_vars "$dir/styles.css" "$out_dir/styles.css"
|
|
||||||
elif [ -f "$dir/style.css" ]; then
|
|
||||||
copy_style_with_resolved_vars "$dir/style.css" "$out_dir/styles.css"
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ "$dir_indexes" != "true" ] && continue
|
|
||||||
|
|
||||||
if [ ! -f "$dir/index.md" ]; then
|
|
||||||
if [ "$single_file_index" = "true" ]; then
|
|
||||||
md_count=$(find "$dir" -maxdepth 1 -name "*.md" | wc -l)
|
|
||||||
if [ "$md_count" -eq 1 ]; then
|
|
||||||
md_file=$(find "$dir" -maxdepth 1 -name "*.md")
|
|
||||||
render_markdown "$md_file" > "$out_dir/index.html"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
temp_index="/tmp/kewt_index_$$.md"
|
|
||||||
display_dir="${rel_dir#.}"
|
|
||||||
[ -z "$display_dir" ] && display_dir="/"
|
|
||||||
echo "# Index of $display_dir" > "$temp_index"
|
|
||||||
echo "" >> "$temp_index"
|
|
||||||
find "$dir" -maxdepth 1 -not -path '*/.*' -not -path "$dir" | sort | while read -r entry; do
|
|
||||||
name="${entry##*/}"
|
|
||||||
case "$name" in
|
|
||||||
template.html|site.conf|style.css|index.md) continue ;;
|
|
||||||
esac
|
|
||||||
if [ -d "$entry" ]; then
|
|
||||||
echo "- [${name}/](${name}/index.html)" >> "$temp_index"
|
|
||||||
elif [ "${entry%.md}" != "$entry" ]; then
|
|
||||||
echo "- [${name%.md}](${name%.md}.html)" >> "$temp_index"
|
|
||||||
else
|
|
||||||
echo "- [$name]($name)" >> "$temp_index"
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
render_markdown "$temp_index" > "$out_dir/index.html"
|
elif [ "$serve_mode" = "true" ]; then
|
||||||
rm "$temp_index"
|
wait "$server_pid"
|
||||||
fi
|
fi
|
||||||
done
|
|
||||||
|
|
||||||
if [ ! -f "$out/styles.css" ] && [ -f "styles/$style.css" ]; then
|
|
||||||
copy_style_with_resolved_vars "styles/$style.css" "$out/styles.css"
|
|
||||||
fi
|
|
||||||
|
|
||||||
find "$src" -type f | sort | while IFS= read -r file; do
|
|
||||||
rel_path="${file#$src/}"
|
|
||||||
dir_rel=$(dirname "$rel_path")
|
|
||||||
out_dir="$out/$dir_rel"
|
|
||||||
|
|
||||||
case "${file##*/}" in
|
|
||||||
template.html|site.conf|style.css|styles.css) continue ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ "$single_file_index" = "true" ] && [ "${file%.md}" != "$file" ] && [ ! -f "$(dirname "$file")/index.md" ]; then
|
|
||||||
md_count=$(find "$(dirname "$file")" -maxdepth 1 -name "*.md" | wc -l)
|
|
||||||
[ "$md_count" -eq 1 ] && continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "${file%.md}" != "$file" ]; then
|
|
||||||
out_file="$out/${rel_path%.md}.html"
|
|
||||||
render_markdown "$file" > "$out_file"
|
|
||||||
else
|
|
||||||
cp "$file" "$out/$rel_path"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Build complete."
|
|
||||||
|
|||||||
608
lib/builder.sh
Normal file
@@ -0,0 +1,608 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
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
|
||||||
|
[ -f "$script_dir/styles/$style.root.css" ] && [ "$script_dir/styles/$style.root.css" -nt "$out_file" ] && return 0
|
||||||
|
[ -f "$src/styles.root.css" ] && [ "$src/styles.root.css" -nt "$out_file" ] && return 0
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
write_content_warning_outputs() {
|
||||||
|
_source_file="$1"
|
||||||
|
_content_out_file="$2"
|
||||||
|
_content_rel_url="$3"
|
||||||
|
_target_url="$4"
|
||||||
|
_landing_out_file="$5"
|
||||||
|
_is_home="$6"
|
||||||
|
|
||||||
|
is_cw_content_page="true"
|
||||||
|
render_markdown "$_source_file" "$_is_home" "$_target_url" > "$_content_out_file"
|
||||||
|
is_cw_content_page="false"
|
||||||
|
|
||||||
|
generate_content_warning_page "$fm_title" "$fm_content_warning" "$_content_rel_url" "$_target_url" "$_landing_out_file" "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_site() {
|
||||||
|
echo "Building site from '$src' to '$out'..."
|
||||||
|
|
||||||
|
build_markdown_manifest
|
||||||
|
build_full_nav
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
has_custom_index="false"
|
||||||
|
has_list="false"
|
||||||
|
if [ -f "$dir/index.md" ]; then
|
||||||
|
has_custom_index="true"
|
||||||
|
if grep -q '^[[:space:]]*{{LIST}}[[:space:]]*$' "$dir/index.md" 2>/dev/null; then
|
||||||
|
has_list="true"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$has_custom_index" = "false" ] || [ "$has_list" = "true" ]; then
|
||||||
|
is_posts_dir="false"
|
||||||
|
if is_posts_directory_rel "$rel_dir"; then
|
||||||
|
is_posts_dir="true"
|
||||||
|
fi
|
||||||
|
if [ "$single_file_index" = "true" ] && [ "$is_posts_dir" = "false" ] && [ "$has_list" = "false" ]; then
|
||||||
|
if load_manifest_dir_entry "$rel_dir" && [ "$dir_md_count" -eq 1 ]; then
|
||||||
|
md_file="$src/$dir_first_md"
|
||||||
|
is_home="false"; [ "$dir" = "$src" ] && is_home="true"
|
||||||
|
target_url=$(directory_index_url "$rel_dir")
|
||||||
|
if needs_rebuild "$md_file" "$out_dir/index.html"; then
|
||||||
|
parse_frontmatter "$md_file"
|
||||||
|
if [ -n "$fm_content_warning" ]; then
|
||||||
|
content_out_file="$out_dir/content.html"
|
||||||
|
if [ "$rel_dir" = "." ]; then
|
||||||
|
content_rel_url="/content.html"
|
||||||
|
else
|
||||||
|
content_rel_url="/$(encode_url_path "$rel_dir")/content.html"
|
||||||
|
fi
|
||||||
|
write_content_warning_outputs "$md_file" "$content_out_file" "$content_rel_url" "$target_url" "$out_dir/index.html" "$is_home"
|
||||||
|
else
|
||||||
|
render_markdown "$md_file" "$is_home" "$target_url" > "$out_dir/index.html"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
temp_index="$KEWT_TMPDIR/index.md"
|
||||||
|
temp_list="$KEWT_TMPDIR/list.md"
|
||||||
|
: > "$temp_list"
|
||||||
|
|
||||||
|
if [ "$has_custom_index" = "false" ]; then
|
||||||
|
display_dir="${rel_dir#.}"
|
||||||
|
[ -z "$display_dir" ] && display_dir="/"
|
||||||
|
echo "# Index of $display_dir" > "$temp_index"
|
||||||
|
echo "" >> "$temp_index"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
sort_args=""
|
||||||
|
# If this is the posts dir reverse
|
||||||
|
if is_posts_directory_rel "$rel_dir"; then
|
||||||
|
sort_args="-r"
|
||||||
|
fi
|
||||||
|
|
||||||
|
temp_entries="$KEWT_TMPDIR/entries_$$.txt"
|
||||||
|
: > "$temp_entries"
|
||||||
|
|
||||||
|
find "$dir" ! -name "$(basename "$dir")" -prune ! -name ".*" -print | while read -r entry; do
|
||||||
|
name="${entry##*/}"
|
||||||
|
case "$name" in
|
||||||
|
template.html|site.conf|style.css|styles.root.css|index.md) continue ;;
|
||||||
|
esac
|
||||||
|
if [ -d "$entry" ]; then
|
||||||
|
entry_rel_dir="${entry#"$src"/}"
|
||||||
|
manifest_dir_hidden_by_draft_index "$entry_rel_dir" && continue
|
||||||
|
dir_url="$(encode_url_path "$name")/index.html"
|
||||||
|
echo "${name}|- [${name}/](${dir_url})" >> "$temp_entries"
|
||||||
|
elif [ "${entry%.md}" != "$entry" ]; then
|
||||||
|
entry_rel_path="${entry#"$src"/}"
|
||||||
|
load_manifest_entry "$entry_rel_path" || continue
|
||||||
|
label="${name%.md}"
|
||||||
|
[ "$manifest_draft" = "true" ] && continue
|
||||||
|
|
||||||
|
post_h="$manifest_title"
|
||||||
|
|
||||||
|
is_post_entry="false"
|
||||||
|
if is_posts_directory_rel "$rel_dir"; then
|
||||||
|
is_post_entry="true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$post_h" ]; then
|
||||||
|
if [ "$is_post_entry" = "true" ]; then
|
||||||
|
if [ -n "$manifest_post_time" ]; then
|
||||||
|
label="$post_h - $manifest_post_date $manifest_post_time"
|
||||||
|
else
|
||||||
|
label="$post_h - $manifest_post_date"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
label="$post_h"
|
||||||
|
fi
|
||||||
|
elif [ "$is_post_entry" = "true" ]; then
|
||||||
|
if [ -n "$manifest_post_time" ]; then
|
||||||
|
label="$manifest_post_date $manifest_post_time"
|
||||||
|
else
|
||||||
|
label="$manifest_post_date"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ "$is_post_entry" = "true" ]; then
|
||||||
|
sort_key="${manifest_post_date} ${manifest_post_time}"
|
||||||
|
else
|
||||||
|
sort_key="$name"
|
||||||
|
fi
|
||||||
|
entry_url=$(encode_url_path "${name%.md}.html")
|
||||||
|
echo "${sort_key}|- [$label](${entry_url})|$name|${entry_url}" >> "$temp_entries"
|
||||||
|
else
|
||||||
|
asset_url=$(encode_url_path "$name")
|
||||||
|
echo "${name}|- [$name]($asset_url)|$name|$asset_url" >> "$temp_entries"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$is_posts_dir" = "true" ]; then
|
||||||
|
LC_ALL=C sort $sort_args "$temp_entries" > "$KEWT_TMPDIR/sorted_entries_$$.txt"
|
||||||
|
cut -d'|' -f2 "$KEWT_TMPDIR/sorted_entries_$$.txt" >> "$temp_list"
|
||||||
|
mkdir -p "$KEWT_TMPDIR/prevnext"
|
||||||
|
awk -F'|' '
|
||||||
|
{
|
||||||
|
name[NR] = $3
|
||||||
|
url[NR] = $4
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
for(i=1; i<=NR; i++) {
|
||||||
|
prev_str = ""
|
||||||
|
next_str = ""
|
||||||
|
if(i > 1) {
|
||||||
|
next_str = "[Next >](" url[i-1] ")"
|
||||||
|
}
|
||||||
|
if(i < NR) {
|
||||||
|
prev_str = "[< Previous](" url[i+1] ")"
|
||||||
|
}
|
||||||
|
if (prev_str != "" || next_str != "") {
|
||||||
|
out = "'"$KEWT_TMPDIR"'/prevnext/" name[i]
|
||||||
|
printf "%s|%s\n", prev_str, next_str > out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$KEWT_TMPDIR/sorted_entries_$$.txt"
|
||||||
|
rm -f "$KEWT_TMPDIR/sorted_entries_$$.txt"
|
||||||
|
else
|
||||||
|
LC_ALL=C sort $sort_args "$temp_entries" | cut -d'|' -f2 >> "$temp_list"
|
||||||
|
fi
|
||||||
|
rm -f "$temp_entries"
|
||||||
|
|
||||||
|
is_home="false"; [ "$dir" = "$src" ] && is_home="true"
|
||||||
|
target_url=$(directory_index_url "$rel_dir")
|
||||||
|
|
||||||
|
num_items=$(wc -l < "$temp_list")
|
||||||
|
if [ "$is_posts_dir" = "true" ] && [ -n "$posts_per_page" ] && [ "$posts_per_page" -gt 0 ] && [ "$num_items" -gt "$posts_per_page" ]; then
|
||||||
|
num_pages=$(( (num_items + posts_per_page - 1) / posts_per_page ))
|
||||||
|
p=1
|
||||||
|
while [ "$p" -le "$num_pages" ]; do
|
||||||
|
chunk_list="$KEWT_TMPDIR/chunk.md"
|
||||||
|
start_line=$(( (p - 1) * posts_per_page + 1 ))
|
||||||
|
tail -n +$start_line "$temp_list" | head -n "$posts_per_page" > "$chunk_list"
|
||||||
|
|
||||||
|
base_url_dir="$(dirname "$target_url")"
|
||||||
|
[ "$base_url_dir" = "/" ] && base_url_dir=""
|
||||||
|
|
||||||
|
nav_html="<div class=\"pagination\">"
|
||||||
|
if [ "$p" -gt 1 ]; then
|
||||||
|
if [ "$p" -eq 2 ]; then
|
||||||
|
nav_html="$nav_html <a href=\"$base_url_dir/index.html\" class=\"prev-page\">« Prev</a> "
|
||||||
|
else
|
||||||
|
nav_html="$nav_html <a href=\"$base_url_dir/page/$((p-1))/index.html\" class=\"prev-page\">« Prev</a> "
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
nav_html="$nav_html <span class=\"page-number\">Page $p of $num_pages</span> "
|
||||||
|
if [ "$p" -lt "$num_pages" ]; then
|
||||||
|
nav_html="$nav_html <a href=\"$base_url_dir/page/$((p+1))/index.html\" class=\"next-page\">Next »</a> "
|
||||||
|
fi
|
||||||
|
nav_html="$nav_html</div>"
|
||||||
|
|
||||||
|
echo "" >> "$chunk_list"
|
||||||
|
echo "$nav_html" >> "$chunk_list"
|
||||||
|
|
||||||
|
temp_index_p="$KEWT_TMPDIR/index_p$p.md"
|
||||||
|
if [ "$has_custom_index" = "false" ]; then
|
||||||
|
display_dir="${rel_dir#.}"
|
||||||
|
[ -z "$display_dir" ] && display_dir="/"
|
||||||
|
echo "# Index of $display_dir" > "$temp_index_p"
|
||||||
|
echo "" >> "$temp_index_p"
|
||||||
|
else
|
||||||
|
: > "$temp_index_p"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$has_custom_index" = "true" ]; then
|
||||||
|
awk '
|
||||||
|
/^[[:space:]]*\{\{LIST\}\}[[:space:]]*$/ {
|
||||||
|
while((getline line < "'"$chunk_list"'") > 0) print line
|
||||||
|
close("'"$chunk_list"'")
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
' "$dir/index.md" >> "$temp_index_p"
|
||||||
|
else
|
||||||
|
cat "$chunk_list" >> "$temp_index_p"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$p" -eq 1 ]; then
|
||||||
|
out_file="$out_dir/index.html"
|
||||||
|
target_url_p="$target_url"
|
||||||
|
else
|
||||||
|
out_file="$out_dir/page/$p/index.html"
|
||||||
|
target_url_p="$base_url_dir/page/$p/index.html"
|
||||||
|
mkdir -p "$(dirname "$out_file")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
render_markdown "$temp_index_p" "$is_home" "$target_url_p" > "$out_file"
|
||||||
|
rm -f "$temp_index_p" "$chunk_list"
|
||||||
|
p=$((p + 1))
|
||||||
|
done
|
||||||
|
else
|
||||||
|
if [ "$has_custom_index" = "true" ]; then
|
||||||
|
awk '
|
||||||
|
/^[[:space:]]*\{\{LIST\}\}[[:space:]]*$/ {
|
||||||
|
while((getline line < "'"$temp_list"'") > 0) print line
|
||||||
|
close("'"$temp_list"'")
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
' "$dir/index.md" > "$temp_index"
|
||||||
|
else
|
||||||
|
cat "$temp_list" >> "$temp_index"
|
||||||
|
fi
|
||||||
|
|
||||||
|
do_rebuild="false"
|
||||||
|
needs_rebuild "$dir" "$out_dir/index.html" && do_rebuild="true"
|
||||||
|
[ "$has_custom_index" = "true" ] && needs_rebuild "$dir/index.md" "$out_dir/index.html" && do_rebuild="true"
|
||||||
|
|
||||||
|
if [ "$do_rebuild" = "false" ] && [ -f "$out_dir/index.html" ]; then
|
||||||
|
for _child in "$dir"/*; do
|
||||||
|
[ -e "$_child" ] || continue
|
||||||
|
if [ "$_child" -nt "$out_dir/index.html" ]; then
|
||||||
|
do_rebuild="true"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$do_rebuild" = "true" ]; then
|
||||||
|
if [ "$has_custom_index" = "true" ]; then
|
||||||
|
parse_frontmatter "$dir/index.md"
|
||||||
|
else
|
||||||
|
fm_content_warning=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$fm_content_warning" ]; then
|
||||||
|
content_out_file="$out_dir/content.html"
|
||||||
|
if [ "$rel_dir" = "." ]; then
|
||||||
|
content_rel_url="/content.html"
|
||||||
|
else
|
||||||
|
content_rel_url="/$(encode_url_path "$rel_dir")/content.html"
|
||||||
|
fi
|
||||||
|
write_content_warning_outputs "$temp_index" "$content_out_file" "$content_rel_url" "$target_url" "$out_dir/index.html" "$is_home"
|
||||||
|
else
|
||||||
|
render_markdown "$temp_index" "$is_home" "$target_url" > "$out_dir/index.html"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
rm -f "$temp_index" "$temp_list"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -f "$src/styles.css" ] && [ ! -f "$src/style.css" ]; then
|
||||||
|
if [ -f "$src/styles.root.css" ]; then
|
||||||
|
_base_css="$script_dir/styles/$style.css"
|
||||||
|
[ ! -f "$_base_css" ] && _base_css="$script_dir/styles/kewt.css"
|
||||||
|
if [ ! -f "$out/styles.css" ] || [ "$src/styles.root.css" -nt "$out/styles.css" ] || [ "$_base_css" -nt "$out/styles.css" ]; then
|
||||||
|
merge_root_style "$src/styles.root.css" "$_base_css" "$out/styles.css"
|
||||||
|
fi
|
||||||
|
elif [ -f "$script_dir/styles/$style.css" ]; then
|
||||||
|
if 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
|
||||||
|
elif [ -f "$script_dir/styles/$style.root.css" ]; then
|
||||||
|
_base_css="$script_dir/styles/kewt.css"
|
||||||
|
if [ ! -f "$out/styles.css" ] || [ "$script_dir/styles/$style.root.css" -nt "$out/styles.css" ] || [ "$_base_css" -nt "$out/styles.css" ]; then
|
||||||
|
merge_root_style "$script_dir/styles/$style.root.css" "$_base_css" "$out/styles.css"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
case "${file##*/}" in
|
||||||
|
template.html|site.conf|style.css|styles.css|styles.root.css) continue ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "${file##*/}" = "index.md" ] && grep -q '^[[:space:]]*{{LIST}}[[:space:]]*$' "$file" 2>/dev/null; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
is_preserved=0
|
||||||
|
if [ -n "$(eval "find \"$file\" \( $PRESERVE_ARGS \) -print")" ]; then
|
||||||
|
is_preserved=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
is_posts_dir_2="false"
|
||||||
|
if is_posts_directory_rel "$dir_rel"; 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
|
||||||
|
load_manifest_dir_entry "$dir_rel" && [ "$dir_md_count" -eq 1 ] && continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${file%.md}" != "$file" ] && [ "$is_preserved" -eq 0 ]; then
|
||||||
|
load_manifest_entry "$rel_path" || continue
|
||||||
|
[ "$manifest_draft" = "true" ] && continue
|
||||||
|
is_home="false"; [ "$file" = "$src/index.md" ] && is_home="true"
|
||||||
|
out_file="$out/${rel_path%.md}.html"
|
||||||
|
if needs_rebuild "$file" "$out_file"; then
|
||||||
|
fm_title="$manifest_title"
|
||||||
|
fm_content_warning="$manifest_content_warning"
|
||||||
|
if [ -n "$manifest_content_warning" ]; then
|
||||||
|
content_out_file="$out/${rel_path%.md}-content.html"
|
||||||
|
content_rel_url="/$(encode_url_path "${rel_path%.md}")-content.html"
|
||||||
|
orig_rel_url="$manifest_url"
|
||||||
|
write_content_warning_outputs "$file" "$content_out_file" "$content_rel_url" "$orig_rel_url" "$out_file" "$is_home"
|
||||||
|
else
|
||||||
|
render_markdown "$file" "$is_home" > "$out_file"
|
||||||
|
fi
|
||||||
|
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
|
||||||
|
temp_404="$KEWT_TMPDIR/404_gen.md"
|
||||||
|
echo "# 404 - Not Found" > "$temp_404"
|
||||||
|
echo "" >> "$temp_404"
|
||||||
|
echo "The requested page could not be found." >> "$temp_404"
|
||||||
|
render_markdown "$temp_404" "false" "/$error_page" > "$out/$error_page"
|
||||||
|
rm -f "$temp_404"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$base_url" ]; then
|
||||||
|
sitemap_file="$out/sitemap.xml"
|
||||||
|
base_url="${base_url%/}"
|
||||||
|
today=$(date +%Y-%m-%d)
|
||||||
|
|
||||||
|
printf '<?xml version="1.0" encoding="UTF-8"?>\n' > "$sitemap_file"
|
||||||
|
printf '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n' >> "$sitemap_file"
|
||||||
|
|
||||||
|
find "$out" -type f -name "*.html" -print | sort | while IFS= read -r html_file; do
|
||||||
|
rel_url="${html_file#"$out"}"
|
||||||
|
|
||||||
|
# Don't include 404 in the sitemap (duh)
|
||||||
|
[ "${rel_url#/}" = "$error_page" ] && continue
|
||||||
|
|
||||||
|
{
|
||||||
|
printf ' <url>\n'
|
||||||
|
printf ' <loc>%s%s</loc>\n' "$base_url" "$rel_url"
|
||||||
|
printf ' <lastmod>%s</lastmod>\n' "$today"
|
||||||
|
printf ' </url>\n'
|
||||||
|
} >> "$sitemap_file"
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '</urlset>\n' >> "$sitemap_file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$generate_feed" = "true" ] && [ -n "$base_url" ]; then
|
||||||
|
feed_path="$out/$feed_file"
|
||||||
|
base_url_feed="${base_url%/}"
|
||||||
|
build_date=$(date -u '+%a, %d %b %Y %H:%M:%S +0000')
|
||||||
|
|
||||||
|
printf '<?xml version="1.0" encoding="UTF-8"?>\n' > "$feed_path"
|
||||||
|
{
|
||||||
|
printf '<rss version="2.0">\n'
|
||||||
|
printf ' <channel>\n'
|
||||||
|
printf ' <title>%s</title>\n' "$title"
|
||||||
|
printf ' <link>%s</link>\n' "$base_url_feed"
|
||||||
|
printf ' <description>%s</description>\n' "$title"
|
||||||
|
printf ' <lastBuildDate>%s</lastBuildDate>\n' "$build_date"
|
||||||
|
} >> "$feed_path"
|
||||||
|
|
||||||
|
temp_feed_files="$KEWT_TMPDIR/feed_files_$$.txt"
|
||||||
|
: > "$temp_feed_files"
|
||||||
|
|
||||||
|
while IFS= read -r manifest_rel_path; do
|
||||||
|
case "$manifest_rel_path" in
|
||||||
|
*"${posts_dir:-__no_posts__}"*) ;;
|
||||||
|
*) continue ;;
|
||||||
|
esac
|
||||||
|
load_manifest_entry "$manifest_rel_path" || continue
|
||||||
|
[ "$manifest_draft" = "true" ] && continue
|
||||||
|
printf '%s %s|%s\n' "$manifest_post_date" "$manifest_post_time" "$manifest_rel_path" >> "$temp_feed_files"
|
||||||
|
done < "$manifest_all_list"
|
||||||
|
|
||||||
|
LC_ALL=C sort -r "$temp_feed_files" | cut -d'|' -f2- | while IFS= read -r post_rel_path; do
|
||||||
|
load_manifest_entry "$post_rel_path" || continue
|
||||||
|
[ "$manifest_draft" = "true" ] && continue
|
||||||
|
|
||||||
|
post_date="$manifest_post_date"
|
||||||
|
post_time="$manifest_post_time"
|
||||||
|
post_heading="$manifest_title"
|
||||||
|
post_slug="$manifest_post_slug"
|
||||||
|
if [ -z "$post_heading" ] && [ -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')
|
||||||
|
fi
|
||||||
|
feed_post_title="$post_heading - $post_date $post_time"
|
||||||
|
|
||||||
|
post_url="$base_url_feed$manifest_url"
|
||||||
|
|
||||||
|
pub_date=$(format_rfc2822_utc "$post_date" "$post_time")
|
||||||
|
|
||||||
|
{
|
||||||
|
printf ' <item>\n'
|
||||||
|
printf ' <title>%s</title>\n' "$feed_post_title"
|
||||||
|
printf ' <link>%s</link>\n' "$post_url"
|
||||||
|
printf ' <guid>%s</guid>\n' "$post_url"
|
||||||
|
printf ' <pubDate>%s</pubDate>\n' "$pub_date"
|
||||||
|
printf ' </item>\n'
|
||||||
|
} >> "$feed_path"
|
||||||
|
done
|
||||||
|
|
||||||
|
printf ' </channel>\n' >> "$feed_path"
|
||||||
|
printf '</rss>\n' >> "$feed_path"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$generate_search" = "true" ] || [ "$generate_tags" = "true" ]; then
|
||||||
|
if [ "$generate_search" = "true" ]; then
|
||||||
|
printf '[\n' > "$out/search.json"
|
||||||
|
fi
|
||||||
|
first_search_item="true"
|
||||||
|
temp_tags="$KEWT_TMPDIR/tags_$$.txt"
|
||||||
|
: > "$temp_tags"
|
||||||
|
|
||||||
|
while IFS= read -r rel_path; do
|
||||||
|
load_manifest_entry "$rel_path" || continue
|
||||||
|
|
||||||
|
if [ "$manifest_is_index" = "true" ]; then
|
||||||
|
if [ "$rel_path" = "index.md" ]; then
|
||||||
|
md_url="/index.html"
|
||||||
|
else
|
||||||
|
md_url=$(directory_index_url "${rel_path%/index.md}")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
md_url="$manifest_url"
|
||||||
|
if [ "$single_file_index" = "true" ]; then
|
||||||
|
rel_dir_of_file="$manifest_dir_rel"
|
||||||
|
[ -z "$rel_dir_of_file" ] && rel_dir_of_file="."
|
||||||
|
if [ "$rel_dir_of_file" = "." ]; then
|
||||||
|
dir_of_file="$src"
|
||||||
|
else
|
||||||
|
dir_of_file="$src/$rel_dir_of_file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
is_posts_dir_search="false"
|
||||||
|
if [ -n "$posts_dir" ] && { [ "$rel_dir_of_file" = "$posts_dir" ] || [ "./$rel_dir_of_file" = "$posts_dir" ]; }; then
|
||||||
|
is_posts_dir_search="true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$is_posts_dir_search" = "false" ] && [ ! -f "$dir_of_file/index.md" ]; then
|
||||||
|
if load_manifest_dir_entry "$rel_dir_of_file" && [ "$dir_md_count" -eq 1 ]; then
|
||||||
|
if [ "$rel_dir_of_file" = "." ]; then
|
||||||
|
md_url="/index.html"
|
||||||
|
else
|
||||||
|
md_url=$(directory_index_url "$rel_dir_of_file")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ "$manifest_draft" = "true" ] && continue
|
||||||
|
md_heading="$manifest_title"
|
||||||
|
|
||||||
|
if [ "$generate_search" = "true" ]; then
|
||||||
|
if [ -z "$manifest_content_warning" ] || [ "$include_cw_pages_in_search" = "true" ]; then
|
||||||
|
md_content="$manifest_search_content"
|
||||||
|
if [ "$first_search_item" = "false" ]; then
|
||||||
|
printf ',\n' >> "$out/search.json"
|
||||||
|
fi
|
||||||
|
printf ' {"url": "%s", "title": "%s", "content": "%s"}' "$md_url" "$md_heading" "$md_content" >> "$out/search.json"
|
||||||
|
first_search_item="false"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$generate_tags" = "true" ] && [ -n "$manifest_tags" ]; then
|
||||||
|
old_ifs=$IFS
|
||||||
|
IFS=','
|
||||||
|
for tag in $manifest_tags; do
|
||||||
|
tag=$(echo "$tag" | sed 's/^[ \t]*//;s/[ \t]*$//')
|
||||||
|
[ -z "$tag" ] && continue
|
||||||
|
printf '%s|%s|%s\n' "$tag" "$md_url" "$md_heading" >> "$temp_tags"
|
||||||
|
done
|
||||||
|
IFS=$old_ifs
|
||||||
|
fi
|
||||||
|
done < "$manifest_visible_list"
|
||||||
|
|
||||||
|
if [ "$generate_search" = "true" ]; then
|
||||||
|
printf '\n]\n' >> "$out/search.json"
|
||||||
|
|
||||||
|
cp "$script_dir/lib/search.js" "$out/search.js"
|
||||||
|
|
||||||
|
search_md="$KEWT_TMPDIR/search_$$.md"
|
||||||
|
printf '%s\n' '# Search' '' \
|
||||||
|
'<form class="kewt-search-page" action="/search.html" method="get">' \
|
||||||
|
' <input type="text" id="search-box" name="q" placeholder="Search..." required>' \
|
||||||
|
' <button type="submit">Search</button>' \
|
||||||
|
'</form>' '' \
|
||||||
|
'<div id="search-results-list">' \
|
||||||
|
' <p>Loading...</p>' \
|
||||||
|
'</div>' '' \
|
||||||
|
'<script src="/search.js"></script>' > "$search_md"
|
||||||
|
render_markdown "$search_md" "false" "/search.html" > "$out/search.html"
|
||||||
|
rm -f "$search_md"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$generate_tags" = "true" ]; then
|
||||||
|
tags_out_dir="$out/$tags_dir"
|
||||||
|
mkdir -p "$tags_out_dir"
|
||||||
|
|
||||||
|
tags_index_md="$KEWT_TMPDIR/tags_index_$$.md"
|
||||||
|
echo "# Tags" > "$tags_index_md"
|
||||||
|
echo "" >> "$tags_index_md"
|
||||||
|
|
||||||
|
cut -d'|' -f1 "$temp_tags" | sort -u | while IFS= read -r tag; do
|
||||||
|
tag_slug=$(echo "$tag" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g')
|
||||||
|
|
||||||
|
echo "- [$tag](/$(echo "$tags_dir" | sed 's|^\/||; s|\/$||')/$tag_slug.html)" >> "$tags_index_md"
|
||||||
|
|
||||||
|
tag_page_md="$KEWT_TMPDIR/tag_page_$$.md"
|
||||||
|
echo "# Tag: $tag" > "$tag_page_md"
|
||||||
|
echo "" >> "$tag_page_md"
|
||||||
|
echo "Posts tagged with **$tag**:" >> "$tag_page_md"
|
||||||
|
echo "" >> "$tag_page_md"
|
||||||
|
|
||||||
|
grep "^${tag}|" "$temp_tags" | while IFS='|' read -r _t t_url t_title; do
|
||||||
|
echo "- [$t_title]($t_url)" >> "$tag_page_md"
|
||||||
|
done
|
||||||
|
|
||||||
|
render_markdown "$tag_page_md" "false" "/$tags_dir/$tag_slug.html" > "$tags_out_dir/$tag_slug.html"
|
||||||
|
rm -f "$tag_page_md"
|
||||||
|
done
|
||||||
|
|
||||||
|
render_markdown "$tags_index_md" "false" "/$tags_dir/index.html" > "$tags_out_dir/index.html"
|
||||||
|
rm -f "$tags_index_md"
|
||||||
|
fi
|
||||||
|
rm -f "$temp_tags"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Build complete."
|
||||||
|
}
|
||||||
144
lib/commands.sh
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
invoked_as=$(basename "${KEWT_INVOKED_AS:-$0}")
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $invoked_as [--from <src>] [--to <out>]
|
||||||
|
$invoked_as [src] [out]
|
||||||
|
$invoked_as --new [title]
|
||||||
|
$invoked_as --update [dir]
|
||||||
|
$invoked_as --post
|
||||||
|
$invoked_as --generate-template
|
||||||
|
$invoked_as --version
|
||||||
|
$invoked_as --help
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--help Show this help message.
|
||||||
|
--new, --init [title] Create a new site directory (default: site)
|
||||||
|
--clean Clean the output directory before building (default).
|
||||||
|
--no-clean Do not clean the output directory before building.
|
||||||
|
--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
|
||||||
|
--generate-template [path] Generate a new template file at <path> (default: template.html)
|
||||||
|
--version Show version information.
|
||||||
|
--from <src> Source directory (default: site)
|
||||||
|
--to <out> Output directory (default: out)
|
||||||
|
--watch, -w Watch for file changes and rebuild automatically.
|
||||||
|
--serve, -s [port] Start a local HTTP server after building (default port: 8000).
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
generate_template() {
|
||||||
|
_gt_path="$1"
|
||||||
|
[ -e "$_gt_path" ] && die "File '$_gt_path' already exists."
|
||||||
|
_gt_dir=$(dirname "$_gt_path")
|
||||||
|
[ -d "$_gt_dir" ] || mkdir -p "$_gt_dir"
|
||||||
|
printf '%s\n' "$DEFAULT_TMPL" > "$_gt_path"
|
||||||
|
echo "Generated template at '$_gt_path'."
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
printf '%s\n' "$DEFAULT_CONF" > "$new_dir/site.conf"
|
||||||
|
printf '%s\n' "$DEFAULT_TMPL" > "$new_dir/template.html"
|
||||||
|
printf "# _kewt_ website\n" > "$new_dir/index.md"
|
||||||
|
|
||||||
|
if [ -n "$new_title" ]; then
|
||||||
|
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'."
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
create_new_post() {
|
||||||
|
post_src_dir="$1"
|
||||||
|
post_user_title="$2"
|
||||||
|
|
||||||
|
target_dir="$post_src_dir"
|
||||||
|
if [ -n "$posts_dir" ]; then
|
||||||
|
target_dir="$post_src_dir/$posts_dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$target_dir"
|
||||||
|
|
||||||
|
base_filename="$(date +%Y-%m-%d-%H-%M)"
|
||||||
|
filename="${base_filename}.md"
|
||||||
|
file_path="$target_dir/$filename"
|
||||||
|
|
||||||
|
counter=1
|
||||||
|
while [ -e "$file_path" ]; do
|
||||||
|
filename="${base_filename}_${counter}.md"
|
||||||
|
file_path="$target_dir/$filename"
|
||||||
|
counter=$((counter + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
post_date_val="$(date "+%Y-%m-%d %H:%M")"
|
||||||
|
if [ -n "$post_user_title" ]; then
|
||||||
|
printf -- '---\ntitle = "%s"\ndate = "%s"\ndraft = %s\n---\n# %s\n' "$post_user_title" "$post_date_val" "$draft_by_default" "$post_user_title" > "$file_path"
|
||||||
|
else
|
||||||
|
printf -- '---\ndate = "%s"\ndraft = %s\n---\n' "$post_date_val" "$draft_by_default" > "$file_path"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Created new post at '$file_path'."
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
update_site() {
|
||||||
|
update_dir="${1:-.}"
|
||||||
|
[ -d "$update_dir" ] || die "Directory '$update_dir' does not exist."
|
||||||
|
|
||||||
|
target_conf="$update_dir/site.conf"
|
||||||
|
target_tmpl="$update_dir/template.html"
|
||||||
|
|
||||||
|
# Generate default site.conf
|
||||||
|
default_conf="$KEWT_TMPDIR/default_site.conf"
|
||||||
|
printf '%s\n' "$DEFAULT_CONF" > "$default_conf"
|
||||||
|
|
||||||
|
# Update site.conf
|
||||||
|
if [ ! -f "$target_conf" ]; then
|
||||||
|
echo "No site.conf found in '$update_dir'; nothing to update."
|
||||||
|
else
|
||||||
|
added=0
|
||||||
|
while IFS= read -r line; do
|
||||||
|
case "$line" in
|
||||||
|
''|'#'*) continue ;;
|
||||||
|
*=*) ;;
|
||||||
|
*) continue ;;
|
||||||
|
esac
|
||||||
|
key=$(printf '%s' "${line%%=*}" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
|
||||||
|
if ! grep -q "^[[:space:]]*${key}[[:space:]]*=" "$target_conf"; then
|
||||||
|
printf '%s\n' "$line" >> "$target_conf"
|
||||||
|
echo " Added: $key"
|
||||||
|
added=$((added + 1))
|
||||||
|
fi
|
||||||
|
done < "$default_conf"
|
||||||
|
if [ "$added" -eq 0 ]; then
|
||||||
|
echo "site.conf is already up to date."
|
||||||
|
else
|
||||||
|
echo "Added $added new key(s) to '$target_conf'."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update template.html
|
||||||
|
if [ -f "$target_tmpl" ]; then
|
||||||
|
default_tmpl="$KEWT_TMPDIR/default_template.html"
|
||||||
|
printf '%s\n' "$DEFAULT_TMPL" > "$default_tmpl"
|
||||||
|
if cmp -s "$default_tmpl" "$target_tmpl" 2>/dev/null; then
|
||||||
|
echo "template.html is already up to date."
|
||||||
|
else
|
||||||
|
cp "$default_tmpl" "${target_tmpl}.default"
|
||||||
|
echo "template.html has local changes; saved latest default as '${target_tmpl}.default'."
|
||||||
|
echo ""
|
||||||
|
diff "$target_tmpl" "${target_tmpl}.default" || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
168
lib/config.sh
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
DEFAULT_CONF='title = "kewt"
|
||||||
|
style = "kewt"
|
||||||
|
lang = "en"
|
||||||
|
draft_by_default = false
|
||||||
|
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
|
||||||
|
enable_header_links = true
|
||||||
|
base_url = ""
|
||||||
|
generate_feed = false
|
||||||
|
feed_file = "rss.xml"
|
||||||
|
posts_dir = ""
|
||||||
|
posts_per_page = 12
|
||||||
|
custom_admonitions = ""
|
||||||
|
cw_hide_url = true
|
||||||
|
generate_tags = false
|
||||||
|
tags_dir = "tags"
|
||||||
|
generate_search = false
|
||||||
|
search_in_footer = false
|
||||||
|
search_in_header = false
|
||||||
|
include_cw_pages_in_search = false'
|
||||||
|
|
||||||
|
DEFAULT_TMPL='<!doctype html>
|
||||||
|
<html lang="{{LANG}}">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>{{TITLE}}</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{{CSS}}{{VERSION}}" type="text/css" />
|
||||||
|
{{HEAD_EXTRA}}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<input type="checkbox" id="nav-toggle" class="nav-toggle" aria-hidden="true" />
|
||||||
|
<header>
|
||||||
|
<h1>{{HEADER_BRAND}}</h1>
|
||||||
|
{{HEADER_SEARCH}}
|
||||||
|
<label for="nav-toggle" class="nav-toggle-label" aria-hidden="true">☰</label>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<nav id="side-bar">{{NAV}}</nav>
|
||||||
|
|
||||||
|
<article>{{CONTENT}}</article>
|
||||||
|
<footer>{{FOOTER}}</footer>
|
||||||
|
</body>
|
||||||
|
</html>'
|
||||||
|
|
||||||
|
reset_config() {
|
||||||
|
title="kewt"
|
||||||
|
style="kewt"
|
||||||
|
lang="en"
|
||||||
|
draft_by_default="false"
|
||||||
|
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"
|
||||||
|
enable_header_links="true"
|
||||||
|
base_url=""
|
||||||
|
generate_feed="false"
|
||||||
|
feed_file="rss.xml"
|
||||||
|
posts_dir=""
|
||||||
|
posts_per_page="12"
|
||||||
|
custom_admonitions=""
|
||||||
|
cw_hide_url="true"
|
||||||
|
generate_tags="false"
|
||||||
|
tags_dir="tags"
|
||||||
|
generate_search="false"
|
||||||
|
search_in_footer="false"
|
||||||
|
search_in_header="false"
|
||||||
|
include_cw_pages_in_search="false"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_config() {
|
||||||
|
[ -f "$1" ] || return
|
||||||
|
while IFS= read -r line || [ -n "$line" ]; do
|
||||||
|
case "$line" in
|
||||||
|
''|'#'*) continue ;;
|
||||||
|
*=*) ;;
|
||||||
|
*) continue ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
key=${line%%=*}
|
||||||
|
val=${line#*=}
|
||||||
|
|
||||||
|
key=$(printf '%s' "$key" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
|
||||||
|
val=$(printf '%s' "$val" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
|
||||||
|
case "$val" in
|
||||||
|
\"*\")
|
||||||
|
val=${val#\"}; val=${val%\"}
|
||||||
|
val=$(printf '%s' "$val" | sed 's/\\"/\"/g; s/\\\\/\\/g')
|
||||||
|
;;
|
||||||
|
\'*\')
|
||||||
|
val=${val#\'}; val=${val%\'}
|
||||||
|
val=$(printf '%s' "$val" | sed "s/\\\\'/'/g; s/\\\\/\\/g")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$key" in
|
||||||
|
title) title="$val" ;;
|
||||||
|
style) style="${val#/}" ;;
|
||||||
|
dir_indexes) dir_indexes="$val" ;;
|
||||||
|
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" ;;
|
||||||
|
display_title) display_title="$val" ;;
|
||||||
|
logo_as_favicon) logo_as_favicon="$val" ;;
|
||||||
|
favicon) favicon="${val#/}" ;;
|
||||||
|
generate_page_title) generate_page_title="$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#/}" ;;
|
||||||
|
posts_per_page) posts_per_page="$val" ;;
|
||||||
|
custom_admonitions) custom_admonitions="$val" ;;
|
||||||
|
cw_hide_url) cw_hide_url="$val" ;;
|
||||||
|
lang) lang="$val" ;;
|
||||||
|
draft_by_default) draft_by_default="$val" ;;
|
||||||
|
generate_tags) generate_tags="$val" ;;
|
||||||
|
tags_dir) tags_dir="${val#/}" ;;
|
||||||
|
generate_search) generate_search="$val" ;;
|
||||||
|
search_in_footer) search_in_footer="$val" ;;
|
||||||
|
search_in_header) search_in_header="$val" ;;
|
||||||
|
include_cw_pages_in_search) include_cw_pages_in_search="$val" ;;
|
||||||
|
esac
|
||||||
|
done < "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_config
|
||||||
315
lib/generator.sh
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
SEARCH_FORM_FOOTER='<form class="kewt-search-footer" action="/search.html" method="get"><input type="text" name="q" placeholder="Search..." required><button type="submit">Go</button></form>'
|
||||||
|
SEARCH_FORM_HEADER='<form class="kewt-search-header" action="/search.html" method="get"><input type="text" name="q" placeholder="Search..." required><button type="submit">Go</button></form>'
|
||||||
|
SEARCH_FORM_NAV='<div class="kewt-search-nav"><form action="/search.html" method="get"><input type="text" name="q" placeholder="Search..." required><button type="submit">Go</button></form></div>'
|
||||||
|
|
||||||
|
generate_nav() {
|
||||||
|
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")
|
||||||
|
nav_input="$KEWT_TMPDIR/nav_input.lst"
|
||||||
|
: > "$nav_input"
|
||||||
|
|
||||||
|
if [ -f "$manifest_visible_list" ]; then
|
||||||
|
while IFS= read -r nav_rel_path; do
|
||||||
|
printf '%s/%s\n' "$1" "$nav_rel_path" >> "$nav_input"
|
||||||
|
done < "$manifest_visible_list"
|
||||||
|
|
||||||
|
if [ -n "$posts_dir" ] && [ -d "$1/$posts_dir" ] && ! manifest_dir_hidden_by_draft_index "$posts_dir"; then
|
||||||
|
has_posts_nav_entry="false"
|
||||||
|
has_posts_index_entry="false"
|
||||||
|
while IFS= read -r nav_rel_path; do
|
||||||
|
case "$nav_rel_path" in
|
||||||
|
"$posts_dir"/index.md) has_posts_index_entry="true" ;;
|
||||||
|
"$posts_dir"/*) has_posts_nav_entry="true" ;;
|
||||||
|
esac
|
||||||
|
done < "$manifest_visible_list"
|
||||||
|
if [ "$has_posts_nav_entry" = "true" ] && [ "$has_posts_index_entry" = "false" ]; then
|
||||||
|
printf '%s/%s/index.md\n' "$1" "$posts_dir" >> "$nav_input"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
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 > "$nav_input"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sort -u "$nav_input" | 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"
|
||||||
|
}
|
||||||
|
escape_html_text() {
|
||||||
|
printf '%s' "$1" | sed \
|
||||||
|
-e 's/&/\&/g' \
|
||||||
|
-e 's/</\</g' \
|
||||||
|
-e 's/>/\>/g'
|
||||||
|
}
|
||||||
|
escape_html_attr() {
|
||||||
|
printf '%s' "$1" | sed \
|
||||||
|
-e 's/&/\&/g' \
|
||||||
|
-e 's/"/\"/g' \
|
||||||
|
-e 's/</\</g' \
|
||||||
|
-e 's/>/\>/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>'
|
||||||
|
}
|
||||||
|
find_closest() {
|
||||||
|
target="$1"
|
||||||
|
start_dir="$2"
|
||||||
|
curr="$start_dir"
|
||||||
|
while [ "$curr" != "$src" ] && [ "$curr" != "." ] && [ "$curr" != "/" ]; do
|
||||||
|
if [ -f "$curr/$target" ]; then
|
||||||
|
echo "$curr/$target"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
curr=$(dirname "$curr")
|
||||||
|
done
|
||||||
|
if [ -f "$src/$target" ]; then
|
||||||
|
echo "$src/$target"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
copy_style_with_resolved_vars() {
|
||||||
|
src_style="$1"
|
||||||
|
out_style="$2"
|
||||||
|
awk -f "$awk_dir/replace_variables.awk" "$src_style" > "$out_style"
|
||||||
|
}
|
||||||
|
merge_root_style() {
|
||||||
|
root_file="$1"
|
||||||
|
base_css="$2"
|
||||||
|
out_file="$3"
|
||||||
|
{
|
||||||
|
cat "$root_file"
|
||||||
|
awk '
|
||||||
|
BEGIN { in_root = 0; brace_depth = 0 }
|
||||||
|
/^:root[[:space:]]*\{/ { in_root = 1; brace_depth = 1; next }
|
||||||
|
in_root {
|
||||||
|
for (i = 1; i <= length($0); i++) {
|
||||||
|
c = substr($0, i, 1)
|
||||||
|
if (c == "{") brace_depth++
|
||||||
|
if (c == "}") { brace_depth--; if (brace_depth == 0) { in_root = 0; next } }
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
' "$base_css"
|
||||||
|
} | awk -f "$awk_dir/replace_variables.awk" > "$out_file"
|
||||||
|
}
|
||||||
|
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=$(markdown_file_url "$rel_path")
|
||||||
|
fi
|
||||||
|
|
||||||
|
content_file="$file"
|
||||||
|
if [ -n "$posts_dir" ] && [ "$file" != "$src/$posts_dir/index.md" ]; then
|
||||||
|
rel_dir_of_url=$(dirname "$current_url")
|
||||||
|
rel_dir_of_url="${rel_dir_of_url#/}"
|
||||||
|
if { [ "$rel_dir_of_url" = "$posts_dir" ] || [ "./$rel_dir_of_url" = "$posts_dir" ]; } && [ "$(basename "$current_url")" != "index.html" ]; then
|
||||||
|
temp_post_with_backlink="$KEWT_TMPDIR/post_with_backlink_$$.md"
|
||||||
|
printf "[< Back](index.html)\n\n" > "$temp_post_with_backlink"
|
||||||
|
awk -f "$awk_dir/frontmatter.awk" "$file" >> "$temp_post_with_backlink"
|
||||||
|
|
||||||
|
post_md_name="$(basename "$current_url" .html).md"
|
||||||
|
prevnext_file="$KEWT_TMPDIR/prevnext/$post_md_name"
|
||||||
|
if [ -f "$prevnext_file" ]; then
|
||||||
|
IFS='|' read -r prev_str next_str < "$prevnext_file"
|
||||||
|
|
||||||
|
printf "\n\n---\n<div class=\"post-nav\">\n" >> "$temp_post_with_backlink"
|
||||||
|
if [ -n "$prev_str" ]; then
|
||||||
|
printf "<span class=\"prev-post\">%s</span>\n" "$prev_str" >> "$temp_post_with_backlink"
|
||||||
|
fi
|
||||||
|
if [ -n "$next_str" ]; then
|
||||||
|
printf "<span class=\"next-post\">%s</span>\n" "$next_str" >> "$temp_post_with_backlink"
|
||||||
|
fi
|
||||||
|
printf "</div>\n" >> "$temp_post_with_backlink"
|
||||||
|
fi
|
||||||
|
content_file="$temp_post_with_backlink"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
local_template=$(find_closest "template.html" "$(dirname "$file")")
|
||||||
|
[ -z "$local_template" ] && local_template="$template"
|
||||||
|
|
||||||
|
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"/}"
|
||||||
|
case "$closest_style_src" in
|
||||||
|
"$src/styles.css") style_rel_to_src="styles.css" ;;
|
||||||
|
"$src/style.css") style_rel_to_src="style.css" ;;
|
||||||
|
esac
|
||||||
|
style_path="/$(encode_url_path "${style_rel_to_src%styles.css}")"
|
||||||
|
style_path="${style_path%style.css}styles.css"
|
||||||
|
else
|
||||||
|
style_path="/styles.css"
|
||||||
|
fi
|
||||||
|
|
||||||
|
logo_html=""
|
||||||
|
if [ "$display_logo" = "true" ] && [ -n "$logo" ]; then
|
||||||
|
logo_html="<img class=\"site-logo\" src=\"$logo\" alt=\"$title\" />"
|
||||||
|
fi
|
||||||
|
|
||||||
|
brand_text=""
|
||||||
|
if [ "$display_title" = "true" ]; then
|
||||||
|
brand_text="$title"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$logo_html" ] && [ -n "$brand_text" ]; then
|
||||||
|
header_brand="<a href=\"/index.html\">$logo_html $brand_text</a>"
|
||||||
|
elif [ -n "$logo_html" ]; then
|
||||||
|
header_brand="<a href=\"/index.html\">$logo_html</a>"
|
||||||
|
elif [ -n "$brand_text" ]; then
|
||||||
|
header_brand="<a href=\"/index.html\">$brand_text</a>"
|
||||||
|
else
|
||||||
|
header_brand="<a href=\"/index.html\">$title</a>"
|
||||||
|
fi
|
||||||
|
|
||||||
|
favicon_src=""
|
||||||
|
if [ "$logo_as_favicon" = "true" ] && [ -n "$logo" ]; then
|
||||||
|
favicon_src="$logo"
|
||||||
|
elif [ -n "$favicon" ]; then
|
||||||
|
favicon_src="$favicon"
|
||||||
|
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 [ -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
|
||||||
|
first_heading=$(first_heading_from_markdown "$file")
|
||||||
|
if [ -n "$first_heading" ]; then
|
||||||
|
first_heading=$(strip_markdown_text "$first_heading")
|
||||||
|
page_title="$first_heading - $title"
|
||||||
|
else
|
||||||
|
basename_no_ext=$(basename "$file" .md)
|
||||||
|
if [ "$basename_no_ext" != "index" ] && [ "$basename_no_ext" != "404_gen" ]; then
|
||||||
|
cap_basename=$(echo "$basename_no_ext" | awk '{print toupper(substr($0,1,1)) substr($0,2)}')
|
||||||
|
page_title="$cap_basename - $title"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
head_extra_og="<meta property=\"og:title\" content=\"$(escape_html_attr "$page_title")\" />"
|
||||||
|
if [ -n "$fm_description" ]; then
|
||||||
|
head_extra_og="$head_extra_og
|
||||||
|
<meta property=\"og:description\" content=\"$(escape_html_attr "$fm_description")\" />"
|
||||||
|
fi
|
||||||
|
og_url="${base_url%/}${current_url}"
|
||||||
|
head_extra_og="$head_extra_og
|
||||||
|
<meta property=\"og:url\" content=\"$(escape_html_attr "$og_url")\" />"
|
||||||
|
|
||||||
|
if [ -n "$head_extra" ]; then
|
||||||
|
head_extra="$head_extra
|
||||||
|
$head_extra_og"
|
||||||
|
else
|
||||||
|
head_extra="$head_extra_og"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$is_cw_content_page" = "true" ] && [ "$cw_hide_url" = "true" ]; then
|
||||||
|
head_extra="$head_extra
|
||||||
|
<script>window.history.replaceState(null, '', '$current_url');</script>"
|
||||||
|
fi
|
||||||
|
|
||||||
|
final_footer="$footer"
|
||||||
|
if [ "$search_in_footer" = "true" ]; then
|
||||||
|
final_footer="$footer $SEARCH_FORM_FOOTER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
final_nav="$nav"
|
||||||
|
final_header_brand="$header_brand"
|
||||||
|
final_header_search=""
|
||||||
|
if [ "$search_in_header" = "true" ]; then
|
||||||
|
final_header_search="$SEARCH_FORM_HEADER"
|
||||||
|
final_nav="$SEARCH_FORM_NAV
|
||||||
|
$nav"
|
||||||
|
fi
|
||||||
|
|
||||||
|
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_LANG="$lang" AWK_CURRENT_URL="$current_url" AWK_TITLE="$page_title" AWK_NAV="$final_nav" AWK_FOOTER="$final_footer" AWK_STYLE_PATH="${style_path}" AWK_HEADER_BRAND="$final_header_brand" AWK_HEADER_SEARCH="$final_header_search" AWK_HEAD_EXTRA="$head_extra" AWK_VERSION="$asset_version" AWK_CONTENT_WARNING="$fm_content_warning" awk -f "$awk_dir/render_template.awk" "$local_template"
|
||||||
|
}
|
||||||
|
generate_content_warning_page() {
|
||||||
|
_fm_title="$1"
|
||||||
|
_fm_content_warning="$2"
|
||||||
|
_content_rel_url="$3"
|
||||||
|
_target_url="$4"
|
||||||
|
_out_file="$5"
|
||||||
|
_is_home="$6"
|
||||||
|
|
||||||
|
_temp_cw="$KEWT_TMPDIR/cw_$$.md"
|
||||||
|
_cw_text="${_fm_content_warning}"
|
||||||
|
[ "$_cw_text" = "true" ] && _cw_text="This content may be sensitive."
|
||||||
|
|
||||||
|
cat <<EOF > "$_temp_cw"
|
||||||
|
---
|
||||||
|
title = "$_fm_title"
|
||||||
|
---
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> **Content Warning:** $_cw_text
|
||||||
|
|
||||||
|
<a href="$(basename "$_content_rel_url")" class="cw-button">Reveal Content</a>
|
||||||
|
EOF
|
||||||
|
render_markdown "$_temp_cw" "$_is_home" "$_target_url" > "$_out_file"
|
||||||
|
rm -f "$_temp_cw"
|
||||||
|
}
|
||||||
193
lib/manifest.sh
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
shell_quote() {
|
||||||
|
printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")"
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest_meta_path() {
|
||||||
|
printf '%s/manifest/meta/%s.meta\n' "$KEWT_TMPDIR" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest_dir_meta_path() {
|
||||||
|
printf '%s/manifest/dir-meta/%s.meta\n' "$KEWT_TMPDIR" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest_dir_hidden_by_draft_index() {
|
||||||
|
_manifest_hidden_dir="${1:-.}"
|
||||||
|
[ -f "$manifest_hidden_dirs_list" ] || return 1
|
||||||
|
|
||||||
|
while :; do
|
||||||
|
awk -v dir="$_manifest_hidden_dir" '$0 == dir { found = 1 } END { exit(found ? 0 : 1) }' "$manifest_hidden_dirs_list" >/dev/null 2>&1 && return 0
|
||||||
|
[ "$_manifest_hidden_dir" = "." ] && return 1
|
||||||
|
_manifest_hidden_parent=$(dirname "$_manifest_hidden_dir")
|
||||||
|
[ "$_manifest_hidden_parent" = "$_manifest_hidden_dir" ] && return 1
|
||||||
|
_manifest_hidden_dir="$_manifest_hidden_parent"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
write_manifest_dir_meta() {
|
||||||
|
_dir_meta_rel="$1"
|
||||||
|
_dir_meta_count="$2"
|
||||||
|
_dir_meta_first="$3"
|
||||||
|
_dir_meta_has_index="$4"
|
||||||
|
_dir_meta_path=$(manifest_dir_meta_path "$_dir_meta_rel")
|
||||||
|
mkdir -p "$(dirname "$_dir_meta_path")"
|
||||||
|
{
|
||||||
|
printf 'dir_manifest_rel=%s\n' "$(shell_quote "$_dir_meta_rel")"
|
||||||
|
printf 'dir_md_count=%s\n' "$(shell_quote "$_dir_meta_count")"
|
||||||
|
printf 'dir_first_md=%s\n' "$(shell_quote "$_dir_meta_first")"
|
||||||
|
printf 'dir_has_index=%s\n' "$(shell_quote "$_dir_meta_has_index")"
|
||||||
|
} > "$_dir_meta_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_manifest_dir_entry() {
|
||||||
|
_dir_manifest_rel="$1"
|
||||||
|
_dir_manifest_meta_path=$(manifest_dir_meta_path "$_dir_manifest_rel")
|
||||||
|
[ -f "$_dir_manifest_meta_path" ] || return 1
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
. "$_dir_manifest_meta_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_manifest_entry() {
|
||||||
|
_manifest_rel_path="$1"
|
||||||
|
_manifest_meta_path=$(manifest_meta_path "$_manifest_rel_path")
|
||||||
|
[ -f "$_manifest_meta_path" ] || return 1
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
. "$_manifest_meta_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
extract_search_content() {
|
||||||
|
_search_file="$1"
|
||||||
|
awk '{
|
||||||
|
if (NR == 1 && $0 == "---") { in_fm = 1; next }
|
||||||
|
if (in_fm && $0 == "---") { in_fm = 0; next }
|
||||||
|
if (in_fm) next
|
||||||
|
if ($0 ~ /^```/) { in_code = !in_code; next }
|
||||||
|
if (in_code) next
|
||||||
|
print
|
||||||
|
}' "$_search_file" | sed \
|
||||||
|
-e 's/^#\{1,6\} //' \
|
||||||
|
-e 's/\*\*\([^*]*\)\*\*/\1/g' \
|
||||||
|
-e 's/\*\([^*]*\)\*/\1/g' \
|
||||||
|
-e 's/__\([^_]*\)__/\1/g' \
|
||||||
|
-e 's/_\([^_]*\)_/\1/g' \
|
||||||
|
-e 's/`\([^`]*\)`/\1/g' \
|
||||||
|
-e 's/\[\([^]]*\)](\([^)]*\))/\1/g' \
|
||||||
|
-e 's/!\[\([^]]*\)](\([^)]*\))//g' \
|
||||||
|
-e 's/^[[:space:]]*[-*+] //' \
|
||||||
|
-e 's/^[[:space:]]*[0-9]\{1,\}\. //' \
|
||||||
|
-e 's/^>[[:space:]]*//' \
|
||||||
|
-e 's/<[^>]*>//g' \
|
||||||
|
-e '/^[[:space:]]*$/d' \
|
||||||
|
-e 's/|//g' \
|
||||||
|
-e 's/^[[:space:]]*---[[:space:]]*$//' |
|
||||||
|
tr '\n' ' ' |
|
||||||
|
sed -e 's/ */ /g' -e 's/\\/\\\\/g' -e 's/"/\\"/g' |
|
||||||
|
awk '{ print substr($0, 1, 500) }'
|
||||||
|
}
|
||||||
|
|
||||||
|
build_markdown_manifest() {
|
||||||
|
manifest_root="$KEWT_TMPDIR/manifest"
|
||||||
|
manifest_meta_root="$manifest_root/meta"
|
||||||
|
manifest_dir_meta_root="$manifest_root/dir-meta"
|
||||||
|
manifest_all_list="$manifest_root/all.lst"
|
||||||
|
manifest_visible_list="$manifest_root/visible.lst"
|
||||||
|
manifest_hidden_dirs_list="$manifest_root/hidden-dirs.lst"
|
||||||
|
|
||||||
|
rm -rf "$manifest_root"
|
||||||
|
mkdir -p "$manifest_meta_root"
|
||||||
|
mkdir -p "$manifest_dir_meta_root"
|
||||||
|
: > "$manifest_all_list"
|
||||||
|
: > "$manifest_visible_list"
|
||||||
|
: > "$manifest_hidden_dirs_list"
|
||||||
|
|
||||||
|
eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -name \"*.md\" -print" | sort | while IFS= read -r manifest_file; do
|
||||||
|
manifest_rel_path="${manifest_file#"$src"/}"
|
||||||
|
manifest_dir_rel=$(dirname "$manifest_rel_path")
|
||||||
|
manifest_filename=$(basename "$manifest_rel_path")
|
||||||
|
manifest_is_index="false"
|
||||||
|
[ "$manifest_filename" = "index.md" ] && manifest_is_index="true"
|
||||||
|
|
||||||
|
parse_frontmatter "$manifest_file"
|
||||||
|
if [ "$manifest_filename" = "index.md" ] && [ "$fm_draft" = "true" ]; then
|
||||||
|
printf '%s\n' "$manifest_dir_rel" >> "$manifest_hidden_dirs_list"
|
||||||
|
fi
|
||||||
|
markdown_title_from_loaded_file "$manifest_file" "$title - Page"
|
||||||
|
manifest_title="$markdown_title"
|
||||||
|
set_post_datetime "$fm_date" "$(basename "$manifest_file" .md)"
|
||||||
|
|
||||||
|
manifest_post_date="$post_date"
|
||||||
|
manifest_post_time="$post_time"
|
||||||
|
manifest_post_slug=$(basename "$manifest_file" .md | 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/^[_\-]//')
|
||||||
|
|
||||||
|
if [ "$manifest_is_index" = "true" ]; then
|
||||||
|
if [ "$manifest_rel_path" = "index.md" ]; then
|
||||||
|
manifest_url="/index.html"
|
||||||
|
else
|
||||||
|
manifest_url=$(directory_index_url "${manifest_rel_path%/index.md}")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
manifest_url=$(markdown_file_url "$manifest_rel_path")
|
||||||
|
fi
|
||||||
|
|
||||||
|
manifest_search_content=""
|
||||||
|
if [ "$generate_search" = "true" ]; then
|
||||||
|
manifest_search_content=$(extract_search_content "$manifest_file")
|
||||||
|
fi
|
||||||
|
|
||||||
|
manifest_meta_file=$(manifest_meta_path "$manifest_rel_path")
|
||||||
|
mkdir -p "$(dirname "$manifest_meta_file")"
|
||||||
|
{
|
||||||
|
printf 'manifest_rel_path=%s\n' "$(shell_quote "$manifest_rel_path")"
|
||||||
|
printf 'manifest_dir_rel=%s\n' "$(shell_quote "$manifest_dir_rel")"
|
||||||
|
printf 'manifest_filename=%s\n' "$(shell_quote "$manifest_filename")"
|
||||||
|
printf 'manifest_is_index=%s\n' "$(shell_quote "$manifest_is_index")"
|
||||||
|
printf 'manifest_title=%s\n' "$(shell_quote "$manifest_title")"
|
||||||
|
printf 'manifest_date=%s\n' "$(shell_quote "$fm_date")"
|
||||||
|
printf 'manifest_draft=%s\n' "$(shell_quote "$fm_draft")"
|
||||||
|
printf 'manifest_description=%s\n' "$(shell_quote "$fm_description")"
|
||||||
|
printf 'manifest_content_warning=%s\n' "$(shell_quote "$fm_content_warning")"
|
||||||
|
printf 'manifest_tags=%s\n' "$(shell_quote "$fm_tags")"
|
||||||
|
printf 'manifest_url=%s\n' "$(shell_quote "$manifest_url")"
|
||||||
|
printf 'manifest_search_content=%s\n' "$(shell_quote "$manifest_search_content")"
|
||||||
|
printf 'manifest_post_date=%s\n' "$(shell_quote "$manifest_post_date")"
|
||||||
|
printf 'manifest_post_time=%s\n' "$(shell_quote "$manifest_post_time")"
|
||||||
|
printf 'manifest_post_slug=%s\n' "$(shell_quote "$manifest_post_slug")"
|
||||||
|
} > "$manifest_meta_file"
|
||||||
|
|
||||||
|
if load_manifest_dir_entry "$manifest_dir_rel"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
dir_md_count=0
|
||||||
|
dir_first_md=""
|
||||||
|
dir_has_index="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
dir_md_count=$((dir_md_count + 1))
|
||||||
|
if [ -z "$dir_first_md" ]; then
|
||||||
|
dir_first_md="$manifest_rel_path"
|
||||||
|
fi
|
||||||
|
if [ "$manifest_filename" = "index.md" ]; then
|
||||||
|
dir_has_index="true"
|
||||||
|
fi
|
||||||
|
write_manifest_dir_meta "$manifest_dir_rel" "$dir_md_count" "$dir_first_md" "$dir_has_index"
|
||||||
|
|
||||||
|
printf '%s\n' "$manifest_rel_path" >> "$manifest_all_list"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -s "$manifest_hidden_dirs_list" ]; then
|
||||||
|
LC_ALL=C sort -u "$manifest_hidden_dirs_list" > "$manifest_hidden_dirs_list.sorted"
|
||||||
|
mv "$manifest_hidden_dirs_list.sorted" "$manifest_hidden_dirs_list"
|
||||||
|
fi
|
||||||
|
|
||||||
|
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
|
||||||
|
printf '%s\n' "$visible_rel_path" >> "$manifest_visible_list"
|
||||||
|
done
|
||||||
|
}
|
||||||
105
lib/metadata.sh
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
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=""
|
||||||
|
fm_description=""
|
||||||
|
fm_content_warning=""
|
||||||
|
fm_tags=""
|
||||||
|
while IFS='=' read -r _fk _fv; do
|
||||||
|
case "$_fk" in
|
||||||
|
title) fm_title="$_fv" ;;
|
||||||
|
date) fm_date="$_fv" ;;
|
||||||
|
draft) fm_draft="$_fv" ;;
|
||||||
|
description) fm_description="$_fv" ;;
|
||||||
|
content_warning) fm_content_warning="$_fv" ;;
|
||||||
|
tags) fm_tags="$_fv" ;;
|
||||||
|
esac
|
||||||
|
done < "$_fm_out"
|
||||||
|
rm -f "$_fm_out"
|
||||||
|
}
|
||||||
|
|
||||||
|
strip_markdown_text() {
|
||||||
|
printf '%s' "$1" | sed \
|
||||||
|
-e 's/\[//g' \
|
||||||
|
-e 's/\]//g' \
|
||||||
|
-e 's/!//g' \
|
||||||
|
-e 's/\*//g' \
|
||||||
|
-e 's/_//g' \
|
||||||
|
-e 's/`//g' \
|
||||||
|
-e 's/([^)]*)//g' \
|
||||||
|
-e 's/\\//g'
|
||||||
|
}
|
||||||
|
|
||||||
|
first_heading_from_markdown() {
|
||||||
|
grep -m 1 '^# ' "$1" | sed 's/^# *//; s/ *$//'
|
||||||
|
}
|
||||||
|
|
||||||
|
markdown_title_from_loaded_file() {
|
||||||
|
_title_file="$1"
|
||||||
|
_title_default="$2"
|
||||||
|
markdown_title="$fm_title"
|
||||||
|
|
||||||
|
if [ -z "$markdown_title" ]; then
|
||||||
|
markdown_title=$(first_heading_from_markdown "$_title_file")
|
||||||
|
if [ -n "$markdown_title" ]; then
|
||||||
|
markdown_title=$(strip_markdown_text "$markdown_title")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$markdown_title" ]; then
|
||||||
|
basename_no_ext=$(basename "$_title_file" .md)
|
||||||
|
if [ "$basename_no_ext" != "index" ] && [ "$basename_no_ext" != "404_gen" ]; then
|
||||||
|
markdown_title=$(echo "$basename_no_ext" | awk '{print toupper(substr($0,1,1)) substr($0,2)}')
|
||||||
|
else
|
||||||
|
markdown_title="$_title_default"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
markdown_title_from_file() {
|
||||||
|
_title_file="$1"
|
||||||
|
_title_default="$2"
|
||||||
|
parse_frontmatter "$_title_file"
|
||||||
|
markdown_title_from_loaded_file "$_title_file" "$_title_default"
|
||||||
|
}
|
||||||
|
|
||||||
|
set_post_datetime() {
|
||||||
|
_raw_date="$1"
|
||||||
|
_fallback_name="$2"
|
||||||
|
|
||||||
|
if [ -n "$_raw_date" ]; then
|
||||||
|
post_date=$(echo "$_raw_date" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/')
|
||||||
|
post_time=""
|
||||||
|
if echo "$_raw_date" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?[0-9]\{2\}[:\-][0-9]\{2\}'; then
|
||||||
|
post_time=$(echo "$_raw_date" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
|
||||||
|
fi
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
post_date=$(echo "$_fallback_name" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/')
|
||||||
|
post_time="00:00"
|
||||||
|
if echo "$_fallback_name" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then
|
||||||
|
post_time=$(echo "$_fallback_name" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
set_post_metadata() {
|
||||||
|
_post_file="$1"
|
||||||
|
_default_title="$2"
|
||||||
|
_basename_no_ext=$(basename "$_post_file" .md)
|
||||||
|
|
||||||
|
markdown_title_from_file "$_post_file" "$_default_title"
|
||||||
|
post_heading="$markdown_title"
|
||||||
|
set_post_datetime "$fm_date" "$_basename_no_ext"
|
||||||
|
|
||||||
|
post_slug=$(echo "$_basename_no_ext" | 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/^[_\-]//')
|
||||||
|
}
|
||||||
193
lib/runtime.sh
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
trim_whitespace() {
|
||||||
|
printf '%s' "$1" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
|
||||||
|
}
|
||||||
|
|
||||||
|
encode_url_path() {
|
||||||
|
printf '%s' "$1" | sed \
|
||||||
|
-e 's/%/%25/g' \
|
||||||
|
-e 's/ /%20/g' \
|
||||||
|
-e 's/#/%23/g' \
|
||||||
|
-e 's/?/%3F/g' \
|
||||||
|
-e 's/"/%22/g' \
|
||||||
|
-e "s/'/%27/g"
|
||||||
|
}
|
||||||
|
|
||||||
|
markdown_file_url() {
|
||||||
|
_rel_path="$1"
|
||||||
|
printf '/%s.html\n' "$(encode_url_path "${_rel_path%.md}")"
|
||||||
|
}
|
||||||
|
|
||||||
|
directory_index_url() {
|
||||||
|
_rel_dir="$1"
|
||||||
|
if [ -z "$_rel_dir" ] || [ "$_rel_dir" = "." ]; then
|
||||||
|
printf '/index.html\n'
|
||||||
|
else
|
||||||
|
printf '/%s/index.html\n' "$(encode_url_path "$_rel_dir")"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
format_rfc2822_utc() {
|
||||||
|
_rfc_date="$1"
|
||||||
|
_rfc_time="${2:-00:00}"
|
||||||
|
[ -n "$_rfc_time" ] || _rfc_time="00:00"
|
||||||
|
awk -v d="$_rfc_date" -v t="$_rfc_time" '
|
||||||
|
function weekday(y, m, day, k, j, h) {
|
||||||
|
if (m < 3) {
|
||||||
|
m += 12
|
||||||
|
y--
|
||||||
|
}
|
||||||
|
k = y % 100
|
||||||
|
j = int(y / 100)
|
||||||
|
h = (day + int((13 * (m + 1)) / 5) + k + int(k / 4) + int(j / 4) + 5 * j) % 7
|
||||||
|
return (h + 6) % 7
|
||||||
|
}
|
||||||
|
BEGIN {
|
||||||
|
split(d, da, "-")
|
||||||
|
split(t, ti, ":")
|
||||||
|
year = da[1] + 0
|
||||||
|
month = da[2] + 0
|
||||||
|
day = da[3] + 0
|
||||||
|
hour = (ti[1] == "" ? 0 : ti[1]) + 0
|
||||||
|
minute = (ti[2] == "" ? 0 : ti[2]) + 0
|
||||||
|
|
||||||
|
months[1] = "Jan"; months[2] = "Feb"; months[3] = "Mar"; months[4] = "Apr"
|
||||||
|
months[5] = "May"; months[6] = "Jun"; months[7] = "Jul"; months[8] = "Aug"
|
||||||
|
months[9] = "Sep"; months[10] = "Oct"; months[11] = "Nov"; months[12] = "Dec"
|
||||||
|
|
||||||
|
days[0] = "Sun"; days[1] = "Mon"; days[2] = "Tue"; days[3] = "Wed"
|
||||||
|
days[4] = "Thu"; days[5] = "Fri"; days[6] = "Sat"
|
||||||
|
|
||||||
|
printf "%s, %02d %s %04d %02d:%02d:00 +0000\n",
|
||||||
|
days[weekday(year, month, day)], day, months[month], year, hour, minute
|
||||||
|
}
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
|
append_find_rule() {
|
||||||
|
_expr="$1"
|
||||||
|
_rule="$2"
|
||||||
|
|
||||||
|
if [ -n "$_expr" ]; then
|
||||||
|
printf '%s -o %s\n' "$_expr" "$_rule"
|
||||||
|
else
|
||||||
|
printf '%s\n' "$_rule"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
append_pattern_rules_from_file() {
|
||||||
|
_expr="$1"
|
||||||
|
_root="$2"
|
||||||
|
_file="$3"
|
||||||
|
|
||||||
|
while IFS= read -r line || [ -n "$line" ]; do
|
||||||
|
case "$line" in
|
||||||
|
''|'#'*) continue ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
pattern=$(trim_whitespace "$line")
|
||||||
|
[ -z "$pattern" ] && continue
|
||||||
|
|
||||||
|
pattern_clean="${pattern#/}"
|
||||||
|
pattern_clean="${pattern_clean%/}"
|
||||||
|
|
||||||
|
if echo "$pattern" | grep -q "/"; then
|
||||||
|
_expr=$(append_find_rule "$_expr" "-path '$_root/$pattern_clean'")
|
||||||
|
_expr=$(append_find_rule "$_expr" "-path '$_root/$pattern_clean/*'")
|
||||||
|
else
|
||||||
|
_expr=$(append_find_rule "$_expr" "-name '$pattern_clean'")
|
||||||
|
fi
|
||||||
|
done < "$_file"
|
||||||
|
|
||||||
|
printf '%s\n' "$_expr"
|
||||||
|
}
|
||||||
|
|
||||||
|
append_nested_marker_rules() {
|
||||||
|
_expr="$1"
|
||||||
|
_root="$2"
|
||||||
|
_marker="$3"
|
||||||
|
_tmp_file="$KEWT_TMPDIR/${_marker#*.}_paths"
|
||||||
|
|
||||||
|
find "$_root" -name "$_marker" > "$_tmp_file"
|
||||||
|
while IFS= read -r marker_path; do
|
||||||
|
marker_dir="${marker_path%/"$marker"}"
|
||||||
|
if [ "$marker_dir" != "$_root" ] && [ "$marker_dir" != "." ]; then
|
||||||
|
_expr=$(append_find_rule "$_expr" "-path '$marker_dir'")
|
||||||
|
_expr=$(append_find_rule "$_expr" "-path '$marker_dir/*'")
|
||||||
|
fi
|
||||||
|
done < "$_tmp_file"
|
||||||
|
rm -f "$_tmp_file"
|
||||||
|
|
||||||
|
printf '%s\n' "$_expr"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_rule_args() {
|
||||||
|
_root="$1"
|
||||||
|
_marker="$2"
|
||||||
|
_base_expr="$3"
|
||||||
|
|
||||||
|
_expr="$_base_expr"
|
||||||
|
if [ -f "$_root/$_marker" ]; then
|
||||||
|
_expr=$(append_pattern_rules_from_file "$_expr" "$_root" "$_root/$_marker")
|
||||||
|
fi
|
||||||
|
_expr=$(append_nested_marker_rules "$_expr" "$_root" "$_marker")
|
||||||
|
printf '%s\n' "$_expr"
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_template_path() {
|
||||||
|
template="$src/template.html"
|
||||||
|
[ -f "$template" ] || template="./template.html"
|
||||||
|
if [ ! -f "$template" ]; then
|
||||||
|
template="$KEWT_TMPDIR/default_template.html"
|
||||||
|
printf '%s\n' "$DEFAULT_TMPL" > "$template"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
build_full_nav() {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh_build_context() {
|
||||||
|
reset_config
|
||||||
|
load_config "./site.conf"
|
||||||
|
load_config "$src/site.conf"
|
||||||
|
|
||||||
|
HIDE_ARGS="$BASE_HIDE_ARGS"
|
||||||
|
if [ -n "$posts_dir" ]; then
|
||||||
|
HIDE_ARGS=$(append_find_rule "$HIDE_ARGS" "-path '$src/$posts_dir/*'")
|
||||||
|
fi
|
||||||
|
|
||||||
|
asset_version=""
|
||||||
|
if [ "$versioning" = "true" ]; then
|
||||||
|
asset_version="?v=$(date '+%Y%m%d%H%M%S')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
resolve_template_path
|
||||||
|
}
|
||||||
|
|
||||||
|
watch_for_changes() {
|
||||||
|
_mark_file="$1"
|
||||||
|
|
||||||
|
changed="$(find "$src" -type f -newer "$_mark_file" 2>/dev/null | head -n 1)"
|
||||||
|
[ -z "$changed" ] && [ -f "site.conf" ] && [ "site.conf" -nt "$_mark_file" ] && changed="site.conf"
|
||||||
|
[ -z "$changed" ] && [ -f "$src/site.conf" ] && [ "$src/site.conf" -nt "$_mark_file" ] && changed="$src/site.conf"
|
||||||
|
[ -z "$changed" ] && [ -f "$template" ] && [ "$template" -nt "$_mark_file" ] && changed="$template"
|
||||||
|
[ -z "$changed" ] && [ -d "$script_dir/styles" ] && changed="$(find "$script_dir/styles" -type f -newer "$_mark_file" 2>/dev/null | head -n 1)"
|
||||||
|
|
||||||
|
printf '%s\n' "$changed"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_posts_directory_rel() {
|
||||||
|
_rel_dir="$1"
|
||||||
|
[ -n "$posts_dir" ] && { [ "$_rel_dir" = "$posts_dir" ] || [ "./$_rel_dir" = "$posts_dir" ]; }
|
||||||
|
}
|
||||||
54
lib/search.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
var params = new URLSearchParams(window.location.search);
|
||||||
|
var query = params.get("q");
|
||||||
|
var box = document.getElementById("search-box");
|
||||||
|
var resultsContainer = document.getElementById("search-results-list");
|
||||||
|
|
||||||
|
if (box && query) box.value = query;
|
||||||
|
|
||||||
|
if (!query) {
|
||||||
|
resultsContainer.innerHTML = "<p>Enter a search term above.</p>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch("/search.json")
|
||||||
|
.then(function (response) {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(function (data) {
|
||||||
|
var q = query.toLowerCase();
|
||||||
|
var results = data.filter(function (item) {
|
||||||
|
return (
|
||||||
|
item.title.toLowerCase().indexOf(q) !== -1 ||
|
||||||
|
item.content.toLowerCase().indexOf(q) !== -1
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
var esc = query.replace(/</g, "<").replace(/>/g, ">");
|
||||||
|
|
||||||
|
if (results.length === 0) {
|
||||||
|
resultsContainer.innerHTML =
|
||||||
|
'<p>No results found for "<strong>' + esc + '</strong>".</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var html =
|
||||||
|
"<p>Found " +
|
||||||
|
results.length +
|
||||||
|
' result(s) for "<strong>' +
|
||||||
|
esc +
|
||||||
|
'</strong>":</p>';
|
||||||
|
results.forEach(function (result) {
|
||||||
|
var snippet = result.content.substring(0, 200);
|
||||||
|
if (result.content.length > 200) snippet += "...";
|
||||||
|
html += '<div class="search-result">';
|
||||||
|
html += '<a href="' + result.url + '">' + result.title + "</a>";
|
||||||
|
if (snippet) html += "<p>" + snippet + "</p>";
|
||||||
|
html += "</div>";
|
||||||
|
});
|
||||||
|
resultsContainer.innerHTML = html;
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
resultsContainer.innerHTML = "<p>Error loading search index.</p>";
|
||||||
|
});
|
||||||
|
});
|
||||||
778
markdown.sh
@@ -1,754 +1,82 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
script_dir=$(CDPATH="" cd -- "$(dirname -- "$0")" && pwd)
|
||||||
awk_dir="$script_dir/awk"
|
awk_dir="$script_dir/awk"
|
||||||
|
|
||||||
sed_ere() {
|
|
||||||
if sed -E '' </dev/null >/dev/null 2>&1; then
|
|
||||||
sed -E "$@"
|
|
||||||
else
|
|
||||||
sed -r "$@"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
sed_ere_inplace() {
|
|
||||||
script="$1"
|
|
||||||
file="$2"
|
|
||||||
tmp="${file}.tmp.$$"
|
|
||||||
sed_ere "$script" "$file" > "$tmp" && mv "$tmp" "$file" || {
|
|
||||||
rm -f "$tmp"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sed_ere_inplace_n() {
|
|
||||||
script="$1"
|
|
||||||
file="$2"
|
|
||||||
tmp="${file}.tmp.$$"
|
|
||||||
sed_ere -n "$script" "$file" > "$tmp" && mv "$tmp" "$file" || {
|
|
||||||
rm -f "$tmp"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sed_inplace() {
|
sed_inplace() {
|
||||||
script="$1"
|
script="$1"
|
||||||
file="$2"
|
file="$2"
|
||||||
tmp="${file}.tmp.$$"
|
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"
|
rm -f "$tmp"
|
||||||
return 1
|
return 1
|
||||||
}
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
temp_file="/tmp/markdown.$$"
|
temp_parent="${KEWT_TMPDIR:-${TMPDIR:-/tmp}}"
|
||||||
|
temp_file="${temp_parent}/markdown.$$.md"
|
||||||
cat "$@" > "$temp_file"
|
cat "$@" > "$temp_file"
|
||||||
|
|
||||||
# backslash escapes for literal characters and inline code masking
|
trap 'rm -f "$temp_file" "$temp_file.tmp" "$temp_file.fm"' EXIT INT TERM
|
||||||
awk '
|
|
||||||
function mask(s, t) {
|
|
||||||
t = s
|
|
||||||
gsub(/\*/, "\034P0\034", t)
|
|
||||||
gsub(/_/, "\034P1\034", t)
|
|
||||||
gsub(/`/, "\034P2\034", t)
|
|
||||||
gsub(/\[/, "\034P3\034", t)
|
|
||||||
gsub(/\]/, "\034P4\034", t)
|
|
||||||
gsub(/\(/, "\034P5\034", t)
|
|
||||||
gsub(/\)/, "\034P6\034", t)
|
|
||||||
gsub(/!/, "\034P7\034", t)
|
|
||||||
gsub(/\$/, "\034P8\034", t)
|
|
||||||
gsub(/#/, "\034P9\034", t)
|
|
||||||
gsub(/\+/, "\034P10\034", t)
|
|
||||||
gsub(/-/, "\034P11\034", t)
|
|
||||||
gsub(/\\/, "\034P12\034", t)
|
|
||||||
gsub(/</, "\034P13\034", t)
|
|
||||||
gsub(/>/, "\034P14\034", t)
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
{
|
|
||||||
# backslash escapes
|
|
||||||
gsub(/\\\*/, "\034P0\034")
|
|
||||||
gsub(/\\_/, "\034P1\034")
|
|
||||||
gsub(/\\`/, "\034P2\034")
|
|
||||||
gsub(/\\\[/, "\034P3\034")
|
|
||||||
gsub(/\\\]/, "\034P4\034")
|
|
||||||
gsub(/\\\(/, "\034P5\034")
|
|
||||||
gsub(/\\\)/, "\034P6\034")
|
|
||||||
gsub(/\\!/, "\034P7\034")
|
|
||||||
gsub(/\\\$/, "\034P8\034")
|
|
||||||
gsub(/\\#/, "\034P9\034")
|
|
||||||
gsub(/\\\+/, "\034P10\034")
|
|
||||||
gsub(/\\\-/, "\034P11\034")
|
|
||||||
gsub(/\\\\/, "\034P12\034")
|
|
||||||
gsub(/\\</, "\034P13\034")
|
|
||||||
gsub(/\\>/, "\034P14\034")
|
|
||||||
|
|
||||||
# inline code (1 or 2 backticks)
|
# Frontmatter
|
||||||
line = $0
|
fm_file="$temp_file.fm"
|
||||||
if (line ~ /^```/) {
|
: > "$fm_file"
|
||||||
print line
|
awk -v fm_out="$fm_file" -f "$awk_dir/frontmatter.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||||
next
|
|
||||||
}
|
|
||||||
out = ""
|
|
||||||
p = 1
|
|
||||||
while (match(substr(line, p), /`+/)) {
|
|
||||||
pstart = p + RSTART - 1
|
|
||||||
plen = RLENGTH
|
|
||||||
if (plen >= 3) {
|
|
||||||
out = out substr(line, p, pstart - p + plen)
|
|
||||||
p = pstart + plen
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
# Found 1 or 2 backticks at pstart
|
# Mask
|
||||||
# Search for closing marker
|
awk -f "$awk_dir/mask_inline_code.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||||
marker = substr(line, pstart, plen)
|
awk -f "$awk_dir/mask_plain.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||||
tail = substr(line, pstart + plen)
|
|
||||||
mpos = index(tail, marker)
|
|
||||||
if (mpos > 0) {
|
|
||||||
# Check if it is followed by more backticks
|
|
||||||
if (substr(tail, mpos + plen, 1) == "`") {
|
|
||||||
# Not a match, treat as literal
|
|
||||||
out = out substr(line, p, pstart - p + plen)
|
|
||||||
p = pstart + plen
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
# Found match!
|
|
||||||
content = substr(tail, 1, mpos - 1)
|
|
||||||
out = out substr(line, p, pstart - p)
|
|
||||||
if (plen == 2 && substr(content, 1, 1) == " " && substr(content, length(content), 1) == " ") {
|
|
||||||
content = substr(content, 2, length(content) - 2)
|
|
||||||
}
|
|
||||||
out = out "<code>" mask(content) "</code>"
|
|
||||||
p = pstart + plen + mpos + plen - 1
|
|
||||||
} else {
|
|
||||||
# No closing marker, treat as literal
|
|
||||||
out = out substr(line, p, pstart - p + plen)
|
|
||||||
p = pstart + plen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out = out substr(line, p)
|
|
||||||
print out
|
|
||||||
}' "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
|
||||||
|
|
||||||
awk '
|
|
||||||
|
|
||||||
function find_unescaped_tag(s, tag, p, off, pos) {
|
|
||||||
p = 1
|
|
||||||
while (1) {
|
|
||||||
off = index(substr(s, p), tag)
|
|
||||||
if (off == 0) return 0
|
|
||||||
pos = p + off - 1
|
|
||||||
if (pos == 1 || substr(s, pos - 1, 1) != "\\") return pos
|
|
||||||
p = pos + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mask_plain(s, t) {
|
|
||||||
t = s
|
|
||||||
gsub(/\*/, "\034P0\034", t)
|
|
||||||
gsub(/_/, "\034P1\034", t)
|
|
||||||
gsub(/`/, "\034P2\034", t)
|
|
||||||
gsub(/\[/, "\034P3\034", t)
|
|
||||||
gsub(/\]/, "\034P4\034", t)
|
|
||||||
gsub(/\(/, "\034P5\034", t)
|
|
||||||
gsub(/\)/, "\034P6\034", t)
|
|
||||||
gsub(/!/, "\034P7\034", t)
|
|
||||||
gsub(/\$/, "\034P8\034", t)
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
BEGIN { in_plain = 0 }
|
|
||||||
{
|
|
||||||
line = $0
|
|
||||||
out = ""
|
|
||||||
while (1) {
|
|
||||||
if (!in_plain) {
|
|
||||||
pos = find_unescaped_tag(line, "<plain>")
|
|
||||||
if (pos == 0) {
|
|
||||||
out = out line
|
|
||||||
break
|
|
||||||
}
|
|
||||||
out = out substr(line, 1, pos - 1) "<mfmplain>"
|
|
||||||
line = substr(line, pos + 7)
|
|
||||||
in_plain = 1
|
|
||||||
} else {
|
|
||||||
pos = find_unescaped_tag(line, "</plain>")
|
|
||||||
if (pos == 0) {
|
|
||||||
out = out mask_plain(line)
|
|
||||||
line = ""
|
|
||||||
break
|
|
||||||
}
|
|
||||||
out = out mask_plain(substr(line, 1, pos - 1)) "</mfmplain>"
|
|
||||||
line = substr(line, pos + 8)
|
|
||||||
in_plain = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
print out
|
|
||||||
}
|
|
||||||
' "$temp_file" > "$temp_file.plain.$$" && mv "$temp_file.plain.$$" "$temp_file"
|
|
||||||
|
|
||||||
|
# Reference links
|
||||||
|
refs=$(awk '/^\[[^\]]+\]: */' "$temp_file")
|
||||||
IFS='
|
IFS='
|
||||||
'
|
'
|
||||||
refs=$(sed_ere -n "/^\[.+\]: +/p" "$@")
|
for ref in $refs; do
|
||||||
for ref in $refs
|
ref_id=$(echo "$ref" | sed 's/^\[\(.*\)\]: .*/\1/')
|
||||||
do
|
ref_url=$(echo "$ref" | sed 's/^\[.*\]: \([^ ]*\).*/\1/')
|
||||||
ref_id=$(printf %s "$ref" | sed_ere -n "s/^\[(.+)\]: .*/\1/p" | tr -d '\n')
|
ref_title=$(echo "$ref" | sed -n 's/^\[.*\]: [^ ]* "\(.*\)"/\1/p' | sed 's@|@!@g')
|
||||||
ref_url=$(printf %s "$ref" | sed_ere -n "s/^\[.+\]: (.+)/\1/p" | cut -d' ' -f1 | tr -d '\n')
|
sed_inplace "s|!\[\([^]]*\)\]\[$ref_id\]|<img src=\"$ref_url\" title=\"$ref_title\" alt=\"\1\" />|g" "$temp_file"
|
||||||
ref_title=$(printf %s "$ref" | sed_ere -n "s/^\[.+\]: (.+) \"(.+)\"/\2/p" | sed 's@|@!@g' | tr -d '\n')
|
sed_inplace "s|\[\([^]]*\)\]\[$ref_id\]|<a href=\"$ref_url\" title=\"$ref_title\">\1</a>|g" "$temp_file"
|
||||||
|
sed_inplace "s|!\[$ref_id\]\[\]|<img src=\"$ref_url\" title=\"$ref_title\" alt=\"$ref_id\" />|g" "$temp_file"
|
||||||
# reference-style image using the label
|
sed_inplace "s|\[$ref_id\]\[\]|<a href=\"$ref_url\" title=\"$ref_title\">$ref_id</a>|g" "$temp_file"
|
||||||
sed_ere_inplace "s|!\[([^]]+)\]\[($ref_id)\]|<img src=\"$ref_url\" title=\"$ref_title\" alt=\"\1\" />|g" "$temp_file"
|
|
||||||
# reference-style link using the label
|
|
||||||
sed_ere_inplace "s|\[([^]]+)\]\[($ref_id)\]|<a href=\"$ref_url\" title=\"$ref_title\">\1</a>|g" "$temp_file"
|
|
||||||
|
|
||||||
# implicit reference-style
|
|
||||||
sed_ere_inplace "s|!\[($ref_id)\]\[\]|<img src=\"$ref_url\" title=\"$ref_title\" alt=\"\1\" />|g" "$temp_file"
|
|
||||||
# implicit reference-style
|
|
||||||
sed_ere_inplace "s|\[($ref_id)\]\[\]|<a href=\"$ref_url\" title=\"$ref_title\">\1</a>|g" "$temp_file"
|
|
||||||
done
|
done
|
||||||
|
sed_inplace "/^\[[^\]]*\]: */d" "$temp_file"
|
||||||
|
|
||||||
# delete the reference lines
|
# Blocks
|
||||||
sed_ere_inplace "/^\[.+\]: +/d" "$temp_file"
|
|
||||||
|
|
||||||
# normalize GitHub admonition shorthand in blockquotes
|
loop_count=0
|
||||||
sed_ere_inplace '
|
max_iterations=100
|
||||||
/^>!\[/s/^>!\[/> [!/
|
while grep '^>' "$temp_file" >/dev/null; do
|
||||||
/^>\[!/s/^>\[!/> [!/
|
awk -f "$awk_dir/blockquote.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||||
s/^>([^[:space:]>])/> \1/
|
loop_count=$((loop_count + 1))
|
||||||
' "$temp_file"
|
if [ "$loop_count" -gt "$max_iterations" ]; then
|
||||||
|
echo "Warning: Blockquote processing exceeded $max_iterations iterations on $1. Breaking to prevent infinite loop." >&2
|
||||||
# blockquotes
|
|
||||||
# use grep to find all the nested blockquotes
|
|
||||||
while grep '^> ' "$temp_file" >/dev/null
|
|
||||||
do
|
|
||||||
sed_ere_inplace_n '
|
|
||||||
/^$/b blockquote
|
|
||||||
|
|
||||||
H
|
|
||||||
$ b blockquote
|
|
||||||
b
|
|
||||||
|
|
||||||
:blockquote
|
|
||||||
x
|
|
||||||
s/(\n+)(> .*)/\1<blockquote>\n\2\n<\/blockquote>/ # wrap the tags in a blockquote
|
|
||||||
p
|
|
||||||
' "$temp_file"
|
|
||||||
|
|
||||||
sed_inplace '1 d' "$temp_file" # cleanup superfluous first line
|
|
||||||
|
|
||||||
# cleanup blank lines and remove subsequent blockquote characters
|
|
||||||
sed_ere_inplace '
|
|
||||||
/^> /s/^> (.*)/\1/
|
|
||||||
' "$temp_file"
|
|
||||||
done
|
|
||||||
|
|
||||||
# convert [!TYPE] blockquotes into admonition blocks
|
|
||||||
awk '
|
|
||||||
function cap(s) { return toupper(substr(s, 1, 1)) tolower(substr(s, 2)) }
|
|
||||||
BEGIN { count = 0 }
|
|
||||||
{ lines[++count] = $0 }
|
|
||||||
END {
|
|
||||||
i = 1
|
|
||||||
while (i <= count) {
|
|
||||||
if (lines[i] == "<blockquote>") {
|
|
||||||
j = i + 1
|
|
||||||
while (j <= count && lines[j] != "</blockquote>") j++
|
|
||||||
if (j <= count) {
|
|
||||||
first = ""
|
|
||||||
first_idx = 0
|
|
||||||
for (k = i + 1; k < j; k++) {
|
|
||||||
if (lines[k] != "") {
|
|
||||||
first = lines[k]
|
|
||||||
first_idx = k
|
|
||||||
break
|
break
|
||||||
}
|
fi
|
||||||
}
|
|
||||||
if (first ~ /^\[![A-Za-z]+\]$/) {
|
|
||||||
kind = first
|
|
||||||
sub(/^\[!/, "", kind)
|
|
||||||
sub(/\]$/, "", kind)
|
|
||||||
lkind = tolower(kind)
|
|
||||||
if (lkind == "note" || lkind == "tip" || lkind == "important" || lkind == "warning" || lkind == "caution") {
|
|
||||||
print "<div class=\"admonition admonition-" lkind "\">"
|
|
||||||
print "<p class=\"admonition-title\">" cap(lkind) "</p>"
|
|
||||||
has_body = 0
|
|
||||||
for (k = first_idx + 1; k < j; k++) {
|
|
||||||
if (lines[k] != "") {
|
|
||||||
print "<p>" lines[k] "</p>"
|
|
||||||
has_body = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!has_body) print "<p></p>"
|
|
||||||
print "</div>"
|
|
||||||
i = j + 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
print lines[i]
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
' "$temp_file" > "$temp_file.admon.$$" && mv "$temp_file.admon.$$" "$temp_file"
|
|
||||||
|
|
||||||
# Setext-style headers
|
|
||||||
sed_ere_inplace_n '
|
|
||||||
# Setext-style headers need to be wrapped around newlines
|
|
||||||
/^$/ b print
|
|
||||||
|
|
||||||
# else, append to holding area
|
|
||||||
H
|
|
||||||
$ b print
|
|
||||||
b
|
|
||||||
|
|
||||||
:print
|
|
||||||
x
|
|
||||||
/=+$/{
|
|
||||||
s/\n(.*)\n=+$/\n<h1>\1<\/h1>/
|
|
||||||
p
|
|
||||||
b
|
|
||||||
}
|
|
||||||
/\-+$/{
|
|
||||||
s/\n(.*)\n\-+$/\n<h2>\1<\/h2>/
|
|
||||||
p
|
|
||||||
b
|
|
||||||
}
|
|
||||||
p
|
|
||||||
' "$temp_file"
|
|
||||||
|
|
||||||
sed_inplace '1 d' "$temp_file" # cleanup superfluous first line
|
|
||||||
|
|
||||||
# atx-style headers and other block styles
|
|
||||||
sed_ere_inplace '
|
|
||||||
/^#+ /s/ #+$// # kill all ending header characters
|
|
||||||
/^# /s/# ([A-Za-z0-9 ]*)(.*)/<h1 id="\1">\1\2<\/h1>/g # H1
|
|
||||||
/^#{2} /s/#{2} ([A-Za-z0-9 ]*)(.*)/<h2 id="\1">\1\2<\/h2>/g # H2
|
|
||||||
/^#{3} /s/#{3} ([A-Za-z0-9 ]*)(.*)/<h3 id="\1">\1\2<\/h3>/g # H3
|
|
||||||
/^#{4} /s/#{4} ([A-Za-z0-9 ]*)(.*)/<h4 id="\1">\1\2<\/h4>/g # H4
|
|
||||||
/^#{5} /s/#{5} ([A-Za-z0-9 ]*)(.*)/<h5 id="\1">\1\2<\/h5>/g # H5
|
|
||||||
/^#{6} /s/#{6} ([A-Za-z0-9 ]*)(.*)/<h6 id="\1">\1\2<\/h6>/g # H6
|
|
||||||
|
|
||||||
/^\*\*\*+$/s/\*\*\*+/<hr \/>/ # hr with *
|
|
||||||
/^---+$/s/---+/<hr \/>/ # hr with -
|
|
||||||
/^___+$/s/___+/<hr \/>/ # hr with _
|
|
||||||
|
|
||||||
' "$temp_file"
|
|
||||||
|
|
||||||
# unordered lists
|
|
||||||
# use grep to find all the nested lists
|
|
||||||
while grep '^[\*\+\-] ' "$temp_file" >/dev/null
|
|
||||||
do
|
|
||||||
sed_ere_inplace_n '
|
|
||||||
# wrap the list
|
|
||||||
/^$/b list
|
|
||||||
|
|
||||||
# wrap the li tags then add to the hold buffer
|
|
||||||
# use uli instead of li to avoid collisions when processing nested lists
|
|
||||||
/^[\*\+\-] /s/[\*\+\-] (.*)/<\/uli>\n<uli>\n\1/
|
|
||||||
|
|
||||||
H
|
|
||||||
$ b list # if at end of file, check for the end of a list
|
|
||||||
b # else, branch to the end of the script
|
|
||||||
|
|
||||||
# this is where a list is checked for the pattern
|
|
||||||
:list
|
|
||||||
# exchange the hold space into the pattern space
|
|
||||||
x
|
|
||||||
# look for the list items, if there wrap the ul tags
|
|
||||||
/<uli>/{
|
|
||||||
s/(.*)/\n<ul>\1\n<\/uli>\n<\/ul>/ # close the ul tags
|
|
||||||
s/\n<\/uli>// # kill the first superfluous closing tag
|
|
||||||
p
|
|
||||||
b
|
|
||||||
}
|
|
||||||
p
|
|
||||||
' "$temp_file"
|
|
||||||
|
|
||||||
sed_inplace '1 d' "$temp_file" # cleanup superfluous first line
|
|
||||||
|
|
||||||
# convert to the proper li to avoid collisions with nested lists
|
|
||||||
sed_inplace 's/uli>/li>/g' "$temp_file"
|
|
||||||
|
|
||||||
# prepare any nested lists
|
|
||||||
sed_ere_inplace '/^[\*\+\-] /s/(.*)/\n\1\n/' "$temp_file"
|
|
||||||
done
|
done
|
||||||
|
|
||||||
# ordered lists
|
awk -v custom_admonitions="$CUSTOM_ADMONITIONS" -f "$awk_dir/blockquote_to_admonition.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||||
# use grep to find all the nested lists
|
awk -f "$awk_dir/fenced_code.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||||
while grep -E '^[1-9]+\. ' "$temp_file" >/dev/null
|
awk -f "$awk_dir/indented_code.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||||
do
|
awk -f "$awk_dir/pipe_tables.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||||
sed_ere_inplace_n '
|
awk -v enable_header_links="$ENABLE_HEADER_LINKS" -f "$awk_dir/headers.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||||
# wrap the list
|
awk -f "$awk_dir/definition_lists.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||||
/^$/b list
|
awk -f "$awk_dir/lists.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||||
|
|
||||||
# wrap the li tags then add to the hold buffer
|
# TOC
|
||||||
# use oli instead of li to avoid collisions when processing nested lists
|
awk -f "$awk_dir/toc.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||||
/^[1-9]+\. /s/[1-9]+\. (.*)/<\/oli>\n<oli>\n\1/
|
# Footnotes
|
||||||
|
awk -f "$awk_dir/footnotes.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||||
|
|
||||||
H
|
# Spacing
|
||||||
$ b list # if at end of file, check for the end of a list
|
awk -f "$awk_dir/breaks.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||||
b # else, branch to the end of the script
|
awk -f "$awk_dir/paragraphs.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||||
|
|
||||||
:list
|
# Inline styles
|
||||||
# exchange the hold space into the pattern space
|
awk -v emoji_file="$awk_dir/emoji.tsv" -f "$awk_dir/emoji.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||||
x
|
awk -f "$awk_dir/markdown_inline.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||||
# look for the list items, if there wrap the ol tags
|
awk -v input_file="$1" -v site_root="$MARKDOWN_SITE_ROOT" -v fallback_file="$MARKDOWN_FALLBACK_FILE" -v script_dir="$script_dir" -f "$awk_dir/markdown_embed.awk" "$temp_file"
|
||||||
/<oli>/{
|
|
||||||
s/(.*)/\n<ol>\1\n<\/oli>\n<\/ol>/ # close the ol tags
|
|
||||||
s/\n<\/oli>// # kill the first superfluous closing tag
|
|
||||||
p
|
|
||||||
b
|
|
||||||
}
|
|
||||||
p
|
|
||||||
' "$temp_file"
|
|
||||||
|
|
||||||
sed_inplace '1 d' "$temp_file" # cleanup superfluous first line
|
|
||||||
|
|
||||||
# convert list items into proper list items to avoid collisions with nested lists
|
|
||||||
sed_inplace 's/oli>/li>/g' "$temp_file"
|
|
||||||
|
|
||||||
# prepare any nested lists
|
|
||||||
sed_ere_inplace '/^[1-9]+\. /s/(.*)/\n\1\n/' "$temp_file"
|
|
||||||
done
|
|
||||||
|
|
||||||
# make escaped periods literal
|
|
||||||
sed_ere_inplace '/^[1-9]+\\. /s/([1-9]+)\\. /\1\. /' "$temp_file"
|
|
||||||
|
|
||||||
# fenced code blocks (triple backticks)
|
|
||||||
awk '
|
|
||||||
BEGIN { in_fence = 0; first_line = 0 }
|
|
||||||
{
|
|
||||||
if (!in_fence && $0 ~ /^```/) {
|
|
||||||
printf "<pre><code>"
|
|
||||||
in_fence = 1
|
|
||||||
first_line = 1
|
|
||||||
next
|
|
||||||
}
|
|
||||||
if (in_fence && $0 ~ /^```[[:space:]]*$/) {
|
|
||||||
print "</code></pre>"
|
|
||||||
in_fence = 0
|
|
||||||
next
|
|
||||||
}
|
|
||||||
if (in_fence) {
|
|
||||||
if (first_line) {
|
|
||||||
first_line = 0
|
|
||||||
if ($0 == "") next
|
|
||||||
}
|
|
||||||
print
|
|
||||||
} else {
|
|
||||||
print
|
|
||||||
}
|
|
||||||
}
|
|
||||||
END {
|
|
||||||
if (in_fence) print "</code></pre>"
|
|
||||||
}
|
|
||||||
' "$temp_file" > "$temp_file.fence.$$" && mv "$temp_file.fence.$$" "$temp_file"
|
|
||||||
|
|
||||||
|
|
||||||
# code blocks
|
|
||||||
sed_ere_inplace_n '
|
|
||||||
# if at end of file, append the current line to the hold buffer and print it
|
|
||||||
${
|
|
||||||
H
|
|
||||||
b code
|
|
||||||
}
|
|
||||||
|
|
||||||
# wrap the code block on any non code block lines
|
|
||||||
/^\t| {4}/!b code
|
|
||||||
|
|
||||||
# else, append to the holding buffer and do nothing
|
|
||||||
H
|
|
||||||
b # else, branch to the end of the script
|
|
||||||
|
|
||||||
:code
|
|
||||||
# exchange the hold space with the pattern space
|
|
||||||
x
|
|
||||||
# look for the code items, if there wrap the pre-code tags
|
|
||||||
/\t| {4}/{
|
|
||||||
s/(\t| {4})(.*)/<pre><code>\n\1\2\n<\/code><\/pre>/ # wrap the ending tags
|
|
||||||
p
|
|
||||||
b
|
|
||||||
}
|
|
||||||
p
|
|
||||||
' "$temp_file"
|
|
||||||
|
|
||||||
sed_inplace '1 d' "$temp_file" # cleanup superfluous first line
|
|
||||||
|
|
||||||
# convert html characters inside pre-code tags into printable representations
|
|
||||||
sed_ere_inplace '
|
|
||||||
# get inside pre-code tags
|
|
||||||
/^<pre><code>/{
|
|
||||||
:inside
|
|
||||||
n
|
|
||||||
# if you found the end tags, branch out
|
|
||||||
/^<\/code><\/pre>/!{
|
|
||||||
s/&/\&/g # ampersand
|
|
||||||
s/</\</g # less than
|
|
||||||
s/>/\>/g # greater than
|
|
||||||
b inside
|
|
||||||
}
|
|
||||||
}
|
|
||||||
' "$temp_file"
|
|
||||||
|
|
||||||
# remove the first tab (or 4 spaces) from the code lines
|
|
||||||
sed_ere_inplace 's/^\t| {4}(.*)/\1/' "$temp_file"
|
|
||||||
|
|
||||||
# markdown pipe tables
|
|
||||||
awk '
|
|
||||||
function trim(s) {
|
|
||||||
sub(/^[[:space:]]+/, "", s)
|
|
||||||
sub(/[[:space:]]+$/, "", s)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
function is_table_row(line, t) {
|
|
||||||
t = line
|
|
||||||
return (t ~ /^[[:space:]]*\|/ && t ~ /\|[[:space:]]*$/)
|
|
||||||
}
|
|
||||||
|
|
||||||
function is_table_sep(line, t) {
|
|
||||||
if (!is_table_row(line)) return 0
|
|
||||||
t = line
|
|
||||||
gsub(/[|:\-[:space:]]/, "", t)
|
|
||||||
return (t == "" && line ~ /-/)
|
|
||||||
}
|
|
||||||
|
|
||||||
function split_row(line, out, n, i, raw) {
|
|
||||||
raw = line
|
|
||||||
sub(/^[[:space:]]*\|/, "", raw)
|
|
||||||
sub(/\|[[:space:]]*$/, "", raw)
|
|
||||||
n = split(raw, out, /\|/)
|
|
||||||
for (i = 1; i <= n; i++) out[i] = trim(out[i])
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
function align_for(sep, t) {
|
|
||||||
t = trim(sep)
|
|
||||||
if (t ~ /^:-+:$/) return "center"
|
|
||||||
if (t ~ /^:-+$/) return "left"
|
|
||||||
if (t ~ /^-+:$/) return "right"
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function render_cell(cell, inner) {
|
|
||||||
inner = trim(cell)
|
|
||||||
if (inner ~ /^```.*```$/) {
|
|
||||||
sub(/^```[[:space:]]*/, "", inner)
|
|
||||||
sub(/[[:space:]]*```$/, "", inner)
|
|
||||||
return "<pre><code>" inner "</code></pre>"
|
|
||||||
}
|
|
||||||
return inner
|
|
||||||
}
|
|
||||||
|
|
||||||
BEGIN { count = 0 }
|
|
||||||
{ lines[++count] = $0 }
|
|
||||||
|
|
||||||
END {
|
|
||||||
in_pre = 0
|
|
||||||
i = 1
|
|
||||||
while (i <= count) {
|
|
||||||
if (lines[i] ~ /^<pre><code>/) {
|
|
||||||
in_pre = 1
|
|
||||||
print lines[i]
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (in_pre) {
|
|
||||||
print lines[i]
|
|
||||||
if (lines[i] ~ /^<\/code><\/pre>/) in_pre = 0
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i < count && is_table_row(lines[i]) && is_table_sep(lines[i + 1])) {
|
|
||||||
n_header = split_row(lines[i], header)
|
|
||||||
n_sep = split_row(lines[i + 1], sep)
|
|
||||||
n_cols = (n_header > n_sep ? n_header : n_sep)
|
|
||||||
|
|
||||||
print "<table>"
|
|
||||||
print "<thead>"
|
|
||||||
print "<tr>"
|
|
||||||
for (c = 1; c <= n_cols; c++) {
|
|
||||||
cell = (c <= n_header ? render_cell(header[c]) : "")
|
|
||||||
a = (c <= n_sep ? align_for(sep[c]) : "")
|
|
||||||
if (a != "") print "<th style=\"text-align: " a ";\">" cell "</th>"
|
|
||||||
else print "<th>" cell "</th>"
|
|
||||||
}
|
|
||||||
print "</tr>"
|
|
||||||
print "</thead>"
|
|
||||||
|
|
||||||
j = i + 2
|
|
||||||
print "<tbody>"
|
|
||||||
while (j <= count && is_table_row(lines[j])) {
|
|
||||||
n_body = split_row(lines[j], body)
|
|
||||||
print "<tr>"
|
|
||||||
for (c = 1; c <= n_cols; c++) {
|
|
||||||
cell = (c <= n_body ? render_cell(body[c]) : "")
|
|
||||||
a = (c <= n_sep ? align_for(sep[c]) : "")
|
|
||||||
if (a != "") print "<td style=\"text-align: " a ";\">" cell "</td>"
|
|
||||||
else print "<td>" cell "</td>"
|
|
||||||
}
|
|
||||||
print "</tr>"
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
print "</tbody>"
|
|
||||||
print "</table>"
|
|
||||||
|
|
||||||
i = j
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_table_sep(lines[i]) && i < count && is_table_row(lines[i + 1])) {
|
|
||||||
n_sep = split_row(lines[i], sep)
|
|
||||||
n_cols = n_sep
|
|
||||||
|
|
||||||
print "<table>"
|
|
||||||
print "<thead>"
|
|
||||||
print "<tr>"
|
|
||||||
for (c = 1; c <= n_cols; c++) {
|
|
||||||
a = align_for(sep[c])
|
|
||||||
if (a != "") print "<th style=\"text-align: " a ";\"></th>"
|
|
||||||
else print "<th></th>"
|
|
||||||
}
|
|
||||||
print "</tr>"
|
|
||||||
print "</thead>"
|
|
||||||
|
|
||||||
j = i + 1
|
|
||||||
print "<tbody>"
|
|
||||||
while (j <= count && is_table_row(lines[j])) {
|
|
||||||
n_body = split_row(lines[j], body)
|
|
||||||
print "<tr>"
|
|
||||||
for (c = 1; c <= n_cols; c++) {
|
|
||||||
cell = (c <= n_body ? render_cell(body[c]) : "")
|
|
||||||
a = align_for(sep[c])
|
|
||||||
if (a != "") print "<td style=\"text-align: " a ";\">" cell "</td>"
|
|
||||||
else print "<td>" cell "</td>"
|
|
||||||
}
|
|
||||||
print "</tr>"
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
print "</tbody>"
|
|
||||||
print "</table>"
|
|
||||||
|
|
||||||
i = j
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
print lines[i]
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
' "$temp_file" > "$temp_file.table.$$" && mv "$temp_file.table.$$" "$temp_file"
|
|
||||||
|
|
||||||
# br tags
|
|
||||||
sed_ere_inplace '
|
|
||||||
# if an empty line, append it to the next line, then check on whether there is two in a row
|
|
||||||
/^$/ {
|
|
||||||
N
|
|
||||||
N
|
|
||||||
/^\n{2}/s/(.*)/\n<br \/>\1/
|
|
||||||
}
|
|
||||||
' "$temp_file"
|
|
||||||
|
|
||||||
# emphasis and strong emphasis and strikethrough
|
|
||||||
sed_ere_inplace_n '
|
|
||||||
# batch up the entire stream of text until a line break in the action
|
|
||||||
/^$/b emphasis
|
|
||||||
|
|
||||||
H
|
|
||||||
$ b emphasis
|
|
||||||
b
|
|
||||||
|
|
||||||
:emphasis
|
|
||||||
x
|
|
||||||
s/\*\*([^\n]+)\*\*/<strong>\1<\/strong>/g
|
|
||||||
s/__([^_\n]+)__/<strong>\1<\/strong>/g
|
|
||||||
s/\*([^\*\n]+)\*/<em>\1<\/em>/g
|
|
||||||
s/([^\\])_([^_\n]+)_/\1<em>\2<\/em>/g
|
|
||||||
s/\~\~([^\n]+)\~\~/<strike>\1<\/strike>/g
|
|
||||||
p
|
|
||||||
' "$temp_file"
|
|
||||||
|
|
||||||
sed_inplace '1 d' "$temp_file" # cleanup superfluous first line
|
|
||||||
|
|
||||||
# paragraphs
|
|
||||||
sed_ere_inplace_n '
|
|
||||||
# if an empty line, check the paragraph
|
|
||||||
/^$/ b para
|
|
||||||
# else append it to the hold buffer
|
|
||||||
H
|
|
||||||
# at end of file, check paragraph
|
|
||||||
$ b para
|
|
||||||
# now branch to end of script
|
|
||||||
b
|
|
||||||
# this is where a paragraph is checked for the pattern
|
|
||||||
:para
|
|
||||||
# return the entire paragraph into the pattern space
|
|
||||||
x
|
|
||||||
# look for non block-level elements, if there - print the p tags
|
|
||||||
/\n<(div|table|pre|p|[ou]l|h[1-6]|[bh]r|blockquote|li)/!{
|
|
||||||
s/(\n+)(.*)/\1<p>\n\2\n<\/p>/
|
|
||||||
p
|
|
||||||
b
|
|
||||||
}
|
|
||||||
p
|
|
||||||
' "$temp_file"
|
|
||||||
|
|
||||||
sed_inplace '1 d' "$temp_file" # cleanup superfluous first line
|
|
||||||
|
|
||||||
# cleanup area where P tags have broken nesting
|
|
||||||
sed_ere_inplace_n '
|
|
||||||
# if the line looks like like an end tag
|
|
||||||
/^<\/(div|table|pre|p|[ou]l|h[1-6]|[bh]r|blockquote)>/{
|
|
||||||
h
|
|
||||||
# if EOF, print the line
|
|
||||||
$ {
|
|
||||||
x
|
|
||||||
b done
|
|
||||||
}
|
|
||||||
# fetch the next line and check on whether or not it is a P tag
|
|
||||||
n
|
|
||||||
/^<\/p>/{
|
|
||||||
G
|
|
||||||
b done
|
|
||||||
}
|
|
||||||
# else, append the line to the previous line and print them both
|
|
||||||
H
|
|
||||||
x
|
|
||||||
}
|
|
||||||
:done
|
|
||||||
p
|
|
||||||
' "$temp_file"
|
|
||||||
|
|
||||||
# inline styles and special characters
|
|
||||||
sed_ere_inplace '
|
|
||||||
/^<pre><code>/,/^<\/code><\/pre>/b
|
|
||||||
|
|
||||||
s/<(http[s]?:\/\/.*)>/<a href=\"\1\">\1<\/a>/g # automatic links
|
|
||||||
s/<(.*@.*\..*)>/<a href=\"mailto:\1\">\1<\/a>/g # automatic email address links
|
|
||||||
|
|
||||||
# force-inline image syntax (double bang)
|
|
||||||
s/!!\[([^]]*)\]\(([^)]*) \"([^\"]*)\"\)/<img data-force-inline=\"1\" alt=\"\1\" src=\"\2\" title=\"\3\" \/>/g
|
|
||||||
s/!!\[([^]]*)\]\(([^)]*)\)/<img data-force-inline=\"1\" alt=\"\1\" src=\"\2\" \/>/g
|
|
||||||
|
|
||||||
s/(^|[^\\])!\[([^]]*)\]\(([^)]*) \"([^\"]*)\"\)/\1<img alt=\"\2\" src=\"\3\" title=\"\4\" \/>/g # inline image with title
|
|
||||||
s/(^|[^\\])!\[([^]]*)\]\(([^)]*)\)/\1<img alt=\"\2\" src=\"\3\" \/>/g # inline image without title
|
|
||||||
|
|
||||||
s/(^|[^\\!])\[([^]]*)\]\(([^)]*) \"([^\"]*)\"\)/\1<a href=\"\3\" title=\"\4\">\2<\/a>/g # inline link with title
|
|
||||||
s/(^|[^\\!])\[([^]]*)\]\(([^)]*)\)/\1<a href=\"\3\">\2<\/a>/g # inline link
|
|
||||||
|
|
||||||
# MFM font syntax
|
|
||||||
s/\$\[font\.serif ([^]]+)\]/<span style=\"font-family: serif;\">\1<\/span>/g
|
|
||||||
s/\$\[font\.monospace ([^]]+)\]/<span style=\"font-family: monospace;\">\1<\/span>/g
|
|
||||||
s/\$\[font\.sans ([^]]+)\]/<span style=\"font-family: sans-serif;\">\1<\/span>/g
|
|
||||||
|
|
||||||
# special characters
|
|
||||||
/&.+;/!s/&/\&/g # ampersand
|
|
||||||
/<[\/a-zA-Z]/!s/</\</g# less than bracket
|
|
||||||
' "$temp_file"
|
|
||||||
|
|
||||||
# display and cleanup
|
|
||||||
awk -v input_file="$1" -v site_root="$MARKDOWN_SITE_ROOT" -v fallback_file="$MARKDOWN_FALLBACK_FILE" -f "$awk_dir/markdown_embed.awk" "$temp_file"
|
|
||||||
rm "$temp_file"
|
rm "$temp_file"
|
||||||
|
|||||||
9
package.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "kewt",
|
||||||
|
"description": "A minimalist static site generator inspired by werc",
|
||||||
|
"global": "true",
|
||||||
|
"install": "make install",
|
||||||
|
"scripts": [
|
||||||
|
"kewt"
|
||||||
|
]
|
||||||
|
}
|
||||||
16
packaging/AUR/.SRCINFO.git
Normal 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
|
||||||
15
packaging/AUR/.SRCINFO.template
Normal 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
|
||||||
31
packaging/AUR/PKGBUILD.git
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Maintainer: n0va <n0va@krzak.org>
|
||||||
|
pkgname=kewt-git
|
||||||
|
pkgver=r0.0000000
|
||||||
|
pkgrel=2
|
||||||
|
pkgdesc="A minimalist, 100% POSIX, static site generator inspired by werc and kew"
|
||||||
|
arch=('any')
|
||||||
|
url="https://kewt.krzak.org"
|
||||||
|
license=('ISC')
|
||||||
|
makedepends=('git')
|
||||||
|
depends=('sh')
|
||||||
|
provides=('kewt')
|
||||||
|
conflicts=('kewt' 'kewt-bin')
|
||||||
|
source=("${pkgname}::git+https://git.krzak.org/N0VA/kewt.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"
|
||||||
|
install -d "${pkgdir}/usr/share/zsh/site-functions"
|
||||||
|
"${pkgdir}/usr/bin/kewt" --dump-zsh-completions > "${pkgdir}/usr/share/zsh/site-functions/_kewt"
|
||||||
|
}
|
||||||
23
packaging/AUR/PKGBUILD.template
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# 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://kewt.krzak.org"
|
||||||
|
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')
|
||||||
|
|
||||||
|
build() {
|
||||||
|
chmod +x "${srcdir}/${pkgname}-${pkgver}.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
17
packaging/homebrew/kewt.rb.template
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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"
|
||||||
|
generate_completions_from_executable(bin/"kewt", "--dump-zsh-completions", shells: [:zsh])
|
||||||
|
end
|
||||||
|
|
||||||
|
test do
|
||||||
|
system "#{bin}/kewt", "--version"
|
||||||
|
end
|
||||||
|
end
|
||||||
25
packaging/zsh/_kewt
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#compdef kewt
|
||||||
|
|
||||||
|
_kewt() {
|
||||||
|
local -a args
|
||||||
|
args=(
|
||||||
|
'--help[Show help message]'
|
||||||
|
'(-h)--help[Show help message]'
|
||||||
|
'(-)--new[Create a new site directory]'
|
||||||
|
'(-)--init[Create a new site directory (alias for --new)]'
|
||||||
|
'(-)--clean[Clean the output directory before building]'
|
||||||
|
'(-)--no-clean[Do not clean the output directory before building]'
|
||||||
|
'(-)--update[Update site.conf and template.html with latest defaults]'
|
||||||
|
'(-)--post[Create a new empty post file in the configured posts_dir]'
|
||||||
|
'(-)--generate-template[Generate a new template file]'
|
||||||
|
'(-v --version)'{-v,--version}'[Show version information]'
|
||||||
|
'--from[Source directory]:directory:_directories'
|
||||||
|
'--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:'
|
||||||
|
)
|
||||||
|
|
||||||
|
_arguments -S -C $args '*: :_directories'
|
||||||
|
}
|
||||||
|
|
||||||
|
_kewt "$@"
|
||||||
11
site.conf
@@ -1,11 +0,0 @@
|
|||||||
title = "kewt"
|
|
||||||
style = "kewt"
|
|
||||||
dir_indexes = true
|
|
||||||
single_file_index = true
|
|
||||||
flatten = false
|
|
||||||
footer = "made with <a href="https://kewt.krzak.org">kewt</a>"
|
|
||||||
logo = ""
|
|
||||||
display_logo = false
|
|
||||||
display_title = true
|
|
||||||
logo_as_favicon = true
|
|
||||||
favicon = ""
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# Heaven
|
|
||||||
|
|
||||||
| --- | --- |
|
|
||||||
| ```![/styles.css]``` | <img style="vertical-align: top;" src="catgirl.jpg"> |
|
|
||||||
BIN
site/button.gif
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus malesuada lacus eu ligula semper pharetra. Cras viverra volutpat massa nec sagittis. Aliquam fringilla quam ut tincidunt ultricies. Aliquam erat volutpat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed id sagittis nisi. Aenean vitae urna justo. Vivamus dictum eros ac mi convallis, blandit hendrerit nunc sagittis. Mauris feugiat neque quis justo molestie, vel pulvinar diam faucibus. Ut tempus magna sit amet ex pharetra mollis. Ut elementum metus metus, id consectetur est tempor eget. Proin sed nibh tincidunt, porttitor elit non, blandit ligula. Ut condimentum accumsan lobortis. Nullam nec tempus leo, sit amet iaculis erat. Donec rutrum, orci in elementum varius, nisl elit rutrum nunc, in lacinia lorem enim non enim.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus malesuada lacus eu ligula semper pharetra. Cras viverra volutpat massa nec sagittis. Aliquam fringilla quam ut tincidunt ultricies. Aliquam erat volutpat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed id sagittis nisi. Aenean vitae urna justo. Vivamus dictum eros ac mi convallis, blandit hendrerit nunc sagittis. Mauris feugiat neque quis justo molestie, vel pulvinar diam faucibus. Ut tempus magna sit amet ex pharetra mollis. Ut elementum metus metus, id consectetur est tempor eget. Proin sed nibh tincidunt, porttitor elit non, blandit ligula. Ut condimentum accumsan lobortis. Nullam nec tempus leo, sit amet iaculis erat. Donec rutrum, orci in elementum varius, nisl elit rutrum nunc, in lacinia lorem enim non enim.
|
||||||
@@ -15,3 +17,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 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.
|
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]
|
||||||
|
|||||||
5
site/depths/index.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Depths
|
||||||
|
|
||||||
|
This is a custom index for a directory
|
||||||
|
|
||||||
|
{{LIST}}
|
||||||
85
site/docs/configuration.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
title = "Configuration"
|
||||||
|
---
|
||||||
|
# Configuration
|
||||||
|
|
||||||
|
## site.conf
|
||||||
|
|
||||||
|
```conf
|
||||||
|
title = "kewt"
|
||||||
|
style = "kewt"
|
||||||
|
lang = "en"
|
||||||
|
draft_by_default = false
|
||||||
|
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
|
||||||
|
enable_header_links = true
|
||||||
|
base_url = ""
|
||||||
|
generate_feed = false
|
||||||
|
feed_file = "rss.xml"
|
||||||
|
posts_dir = ""
|
||||||
|
posts_per_page = 12
|
||||||
|
custom_admonitions = ""
|
||||||
|
cw_hide_url = true
|
||||||
|
generate_tags = false
|
||||||
|
tags_dir = "tags"
|
||||||
|
generate_search = false
|
||||||
|
search_in_footer = false
|
||||||
|
search_in_header = false
|
||||||
|
include_cw_pages_in_search = false
|
||||||
|
```
|
||||||
|
- `title` - site title
|
||||||
|
- `style` - style name from the built-in `styles/` directory. See [Theming](theming.md)
|
||||||
|
- `lang` - document language, used for the `<html lang="...">` attribute (default: "en")
|
||||||
|
- `draft_by_default` - default value for the `draft` frontmatter field in new posts created (default: false)
|
||||||
|
- `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 build-time version query parameter (for example `?v=20260505193210`) to css asset urls to bypass cache (default: false)
|
||||||
|
- `enable_header_links` - turns markdown section headings into clickable anchor links (default: true)
|
||||||
|
- `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.
|
||||||
|
- `posts_per_page` - number of posts per page in paginated post indexes (default: 12). Set to 0 to disable pagination.
|
||||||
|
- `custom_admonitions` - comma separated list of custom admonitions
|
||||||
|
- `cw_hide_url` - embeds non-breaking JS to replace the URL in the browser bar on content warning pages (default: true)
|
||||||
|
- `generate_tags` - generate tag index pages from post frontmatter (requires `posts_dir`)
|
||||||
|
- `tags_dir` - directory name for generated tag pages (default: "tags")
|
||||||
|
- `generate_search` - generate a `search.json` index for client-side search
|
||||||
|
- `search_in_footer` - include a search box in the page footer (requires `generate_search`)
|
||||||
|
- `search_in_header` - include a search box in the page header (requires `generate_search` and a template that includes `{{HEADER_SEARCH}}`)
|
||||||
|
- `include_cw_pages_in_search` - include content warning pages in the search index (default: false)
|
||||||
|
|
||||||
|
## Dot Files
|
||||||
|
|
||||||
|
- `.kewtignore` - files/directories to ignore completely. If the file is empty, the whole directory gets ignored.
|
||||||
|
- `.kewthide` - files/directories to hide from navigation but still process. Same empty-file rules as `.kewtignore`.
|
||||||
|
- `.kewtpreserve` - files/directories to copy as-is without converting markdown to HTML. Same empty-file rules again.
|
||||||
32
site/docs/embeds.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
title = "Embeds"
|
||||||
|
---
|
||||||
|
# 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>`
|
||||||
|
- `\` works the same, with `alt` used for images
|
||||||
|
- `\!![link]` and `\!` force inline local file contents
|
||||||
|
|
||||||
|
If you want to **force** a file to be inlined, use `\!![]` instead of `\![]`
|
||||||
|
|
||||||
|
## Reality-Breaking Embeds
|
||||||
|
|
||||||
|
`\!![link]` and `\!` work even inside inline code blocks. If the content between backticks consists only of `\!![]` embeds, the embed triggers and the content is inlined instead of being rendered as code.
|
||||||
|
|
||||||
|
```
|
||||||
|
`!![/file.sh]`
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
22
site/docs/frontmatter.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
title = "Frontmatter"
|
||||||
|
---
|
||||||
|
# Frontmatter
|
||||||
|
|
||||||
|
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
|
||||||
|
description = "A short page summary"
|
||||||
|
tags = "example, tutorial"
|
||||||
|
---
|
||||||
|
```
|
||||||
|
- `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. If not set, uses the `draft_by_default` config value.
|
||||||
|
- `description` - page description, used for Open Graph `og:description` meta tag.
|
||||||
|
- `tags` - comma separated list of tags. Used for tag index generation when `generate_tags` is enabled in `site.conf`.
|
||||||
|
- `content_warning` - if set, creates an interstitial warning page that the user must click through. If set to `true` uses a generic warning, otherwise uses your string.
|
||||||
6
site/docs/index.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
title = "Documentation"
|
||||||
|
---
|
||||||
|
# Documentation
|
||||||
|
|
||||||
|
{{LIST}}
|
||||||
45
site/docs/installation.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
title = "Installation"
|
||||||
|
---
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
## Standalone
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -L -o kewt https://git.krzak.org/N0VA/kewt/releases/download/latest/kewt
|
||||||
|
chmod +x kewt
|
||||||
|
```
|
||||||
|
## 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
|
||||||
|
```
|
||||||
120
site/docs/markdown.md
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
---
|
||||||
|
title = "Markdown Extensions"
|
||||||
|
---
|
||||||
|
# Markdown Extensions
|
||||||
|
|
||||||
|
## Directory Index Customisation
|
||||||
|
|
||||||
|
By default, directories without an `index.md` get an auto-generated index page listing their contents.
|
||||||
|
|
||||||
|
If you create your own `index.md` in a directory, you can still include the auto-generated file list by using the `{{LIST}}` placeholder:
|
||||||
|
|
||||||
|
```md
|
||||||
|
# Blog
|
||||||
|
|
||||||
|
This is my blog. The posts are below. The top-most one is the most recent.
|
||||||
|
|
||||||
|
{{LIST}}
|
||||||
|
```
|
||||||
|
The `{{LIST}}` placeholder is replaced with the autogenerated file list.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
`{{TOC}}` auto-generates a nested heading list with clickable anchors.
|
||||||
|
|
||||||
|
## Footnotes
|
||||||
|
|
||||||
|
Full support for `[^id]` footnotes and `[^id]: text` definitions. They render as a numbered `<section>` at the bottom of the page.
|
||||||
|
|
||||||
|
## Definition Lists
|
||||||
|
|
||||||
|
Definition lists use the standard syntax:
|
||||||
|
|
||||||
|
```md
|
||||||
|
Term
|
||||||
|
: Definition
|
||||||
|
```
|
||||||
|
This renders as `<dl><dt>Term</dt><dd>Definition</dd></dl>`. Multiple definitions per term are supported.
|
||||||
|
|
||||||
|
## Emoji Shortcodes
|
||||||
|
|
||||||
|
Standard GitHub/MkDocs emoji shortcodes like `:smile:`, `:fire:`, `:rocket:` are automatically replaced with their Unicode emoji equivalents. Shortcodes inside codeblocks are left as-is.
|
||||||
|
|
||||||
|
## Pipe Tables
|
||||||
|
|
||||||
|
Tables use the GitHub-style syntax:
|
||||||
|
|
||||||
|
```md
|
||||||
|
| Header 1 | Header 2 |
|
||||||
|
|---|---|
|
||||||
|
| cell 1 | cell 2 |
|
||||||
|
| cell 3 | cell 4 |
|
||||||
|
```
|
||||||
|
Column alignment is set with colons in the separator:
|
||||||
|
|
||||||
|
```md
|
||||||
|
| Left | Center | Right |
|
||||||
|
|:---|:---:|---:|
|
||||||
|
| a | b | c |
|
||||||
|
```
|
||||||
|
Tables can drop the header row and with a separator:
|
||||||
|
|
||||||
|
```md
|
||||||
|
|---|---|
|
||||||
|
| a | b |
|
||||||
|
```
|
||||||
|
## Blockquotes
|
||||||
|
|
||||||
|
Standard Markdown blockquote syntax using `>`:
|
||||||
|
|
||||||
|
```md
|
||||||
|
> This is a blockquote.
|
||||||
|
> It can span multiple lines.
|
||||||
|
```
|
||||||
|
### Admonitions
|
||||||
|
|
||||||
|
Blockquotes that start with a type tag become styled admonition blocks. Five built-in types are supported: `NOTE`, `TIP`, `IMPORTANT`, `WARNING`, `CAUTION`.
|
||||||
|
|
||||||
|
```md
|
||||||
|
> [!NOTE]
|
||||||
|
> This is a note admonition.
|
||||||
|
```
|
||||||
|
Custom admonition types can be added via the `custom_admonitions` config option in `site.conf`.
|
||||||
|
|
||||||
|
## Task Lists
|
||||||
|
|
||||||
|
GFM-style task lists are supported inside **both** ordered and unordered lists:
|
||||||
|
|
||||||
|
```md
|
||||||
|
- [ ] Unchecked item
|
||||||
|
- [x] Checked item
|
||||||
|
- Normal item
|
||||||
|
|
||||||
|
1. [ ] Unchecked item
|
||||||
|
2. [x] Checked item
|
||||||
|
3. Normal item
|
||||||
|
```
|
||||||
|
## Reference Links
|
||||||
|
|
||||||
|
Markdown reference-style links and images are supported:
|
||||||
|
|
||||||
|
```md
|
||||||
|
[link text][ref]
|
||||||
|
|
||||||
|
[ref]: https://example.com "Optional title"
|
||||||
|
|
||||||
|
![alt text][img-ref]
|
||||||
|
|
||||||
|
[img-ref]: /image.png "Optional title"
|
||||||
|
```
|
||||||
|
## Plain Text Blocks
|
||||||
|
|
||||||
|
Content inside `<plain>...</plain>` tags is rendered without any Markdown processing
|
||||||
|
|
||||||
|
## MFM Font Syntax
|
||||||
|
|
||||||
|
Misskey-style font syntax is supported for inline font family changes:
|
||||||
|
|
||||||
|
- `$[font.serif text]` - serif font
|
||||||
|
- `$[font.mono text]` - monospace font
|
||||||
|
- `$[font.sans text]` - sans-serif font
|
||||||
32
site/docs/quickstart.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
title = "Quickstart"
|
||||||
|
---
|
||||||
|
# Quickstart
|
||||||
|
|
||||||
|
## Creating a site
|
||||||
|
|
||||||
|
```sh
|
||||||
|
kewt --new mysite
|
||||||
|
cd mysite
|
||||||
|
```
|
||||||
|
This creates a directory with a `site.conf`, `template.html`, and `index.md`.
|
||||||
|
|
||||||
|
## Writing content
|
||||||
|
|
||||||
|
Edit `index.md` (or any `.md` file) and just write markdown as usual. Files in subdirectories are added to the navigation automatically.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```sh
|
||||||
|
kewt src out # Replace with the directories you want
|
||||||
|
```
|
||||||
|
Reads from `src` and writes static HTML to `out`.
|
||||||
|
|
||||||
|
## Previewing
|
||||||
|
|
||||||
|
```sh
|
||||||
|
kewt --serve
|
||||||
|
```
|
||||||
|
Builds the site and starts a local HTTP server. Use `--watch` with `--serve` to rebuild automatically on file changes.
|
||||||
|
|
||||||
|
## That's it, if you want to do anything more, look at [the documentation](/docs)
|
||||||
22
site/docs/templates.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
title = "Templates"
|
||||||
|
---
|
||||||
|
# Templates
|
||||||
|
|
||||||
|
When customizing `template.html`, the placeholders available are:
|
||||||
|
- `{{CONTENT}}` - the generated content
|
||||||
|
- `{{TITLE}}` - the generated title
|
||||||
|
- `{{NAV}}` - the generated navigation
|
||||||
|
- `{{FOOTER}}` - the configured footer
|
||||||
|
- `{{VERSION}}` - the cache-busting string from `versioning = true` (e.g. `?v=12345678`). Safe to use even if versioning is **disabled** (it will be empty).
|
||||||
|
- `{{CSS}}` - the configured CSS file path
|
||||||
|
- `{{LANG}}` - the configured document language
|
||||||
|
- `{{HEAD_EXTRA}}` - meta-tags
|
||||||
|
- `{{HEADER_BRAND}}` - header rendering the name and/or logo
|
||||||
|
- `{{HEADER_SEARCH}}` - header search form when `search_in_header = true`
|
||||||
|
|
||||||
|
## Search
|
||||||
|
|
||||||
|
When `generate_search` is enabled, kewt embeds a search bar into pages based on the `search_in_header` and `search_in_footer` config options. The search uses a `search.json` index generated at build time and a client-side JS script. No external dependencies are required.
|
||||||
|
|
||||||
|
If you use a custom template and want header search enabled, make sure your `template.html` includes `{{HEADER_SEARCH}}` somewhere inside `<header>`.
|
||||||
62
site/docs/theming.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
---
|
||||||
|
title = "Theming"
|
||||||
|
---
|
||||||
|
# Theming
|
||||||
|
|
||||||
|
*kewt* has a few colour palettes built-in. Set the `style` option in `site.conf` to a theme name to apply it.
|
||||||
|
|
||||||
|
## Built-in Themes
|
||||||
|
|
||||||
|
| Theme | `style` value | Dark/Light |
|
||||||
|
|---|---|---|
|
||||||
|
| Kewt (default) | `kewt` | Light |
|
||||||
|
| Kewt Light | `kewt-light` | Light |
|
||||||
|
| Nord | `nord` | Dark |
|
||||||
|
| Nord Light | `nord-light` | Light |
|
||||||
|
| Monokai | `monokai` | Dark |
|
||||||
|
| Monokai Light | `monokai-light` | Light |
|
||||||
|
| Grayscale | `grayscale` | Dark |
|
||||||
|
| Grayscale Light | `grayscale-light` | Light |
|
||||||
|
| One Dark | `onedark` | Dark |
|
||||||
|
| One Light | `onelight` | Light |
|
||||||
|
| Rose Pine | `rosepine` | Dark |
|
||||||
|
| Rose Pine Light | `rosepine-light` | Light |
|
||||||
|
| Solarized | `solarized` | Light |
|
||||||
|
| Solarized Dark | `solarized-dark` | Dark |
|
||||||
|
|
||||||
|
```conf
|
||||||
|
style = "kewt-light"
|
||||||
|
```
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
Each theme is a `.root.css` file containing a `:root` block with CSS custom properties. At build time, *kewt* merges the theme's variables with the base `kewt.css` stylesheet. The base `:root` block is stripped out and replaced with the theme's variables.
|
||||||
|
|
||||||
|
## Style Resolution
|
||||||
|
|
||||||
|
*kewt* resolves styles in this priority order (highest wins):
|
||||||
|
|
||||||
|
1. `site/styles.css` - a full custom stylesheet in your site directory. Overrides everything.
|
||||||
|
2. `site/styles.root.css` - custom `:root` variables merged with the built-in `kewt.css` base.
|
||||||
|
3. built-in `<style>.css` - a full stylesheet matching the `style` config value.
|
||||||
|
4. built-in `<style>.root.css` - `:root` variables merged with `kewt.css`.
|
||||||
|
|
||||||
|
If none of these exist, the unmodified `kewt.css` is used
|
||||||
|
|
||||||
|
## Custom Themes
|
||||||
|
|
||||||
|
To create a custom colour theme, place a `styles.root.css` file in your site directory. The file should contain only a `:root` block with the CSS variables you want to override:
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--bg: #1a1b26;
|
||||||
|
--fg: #c0caf5;
|
||||||
|
--fg-link: #7aa2f7;
|
||||||
|
--fg-heading: #c0caf5;
|
||||||
|
--code-bg: #24283b;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Any variables not overridden will fall back to the defaults in `kewt.css`. The `:root` block in the base stylesheet is automatically removed to prevent conflicts.
|
||||||
|
|
||||||
|
## Per-Directory Styles
|
||||||
|
|
||||||
|
Subdirectories can have their own `styles.css` or `styles.root.css` that apply only to pages in that directory. Per-directory styles follow the same priority.
|
||||||
25
site/docs/usage.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title = "Usage"
|
||||||
|
---
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
```sh
|
||||||
|
kewt --help
|
||||||
|
kewt --version
|
||||||
|
kewt --new [title]
|
||||||
|
kewt --post [title]
|
||||||
|
kewt --generate-template [path]
|
||||||
|
kewt --update [dir]
|
||||||
|
kewt --from <src> --to <out>
|
||||||
|
kewt [src] [out]
|
||||||
|
kewt --watch
|
||||||
|
kewt --serve [port]
|
||||||
|
```
|
||||||
|
- `--new [title]` creates a new site directory with a default `site.conf`, `template.html`, and `index.md`.
|
||||||
|
- `--post [title]` creates a new markdown file in the configured `posts_dir` with the current date/time as the filename and default frontmatter.
|
||||||
|
- `--generate-template [path]` writes the default `template.html` to the given path (defaults to `template.html` in the current directory).
|
||||||
|
- `--update [dir]` adds any missing keys to `site.conf` and checks `template.html` against the latest default.
|
||||||
|
- `--watch` (`-w`) watches for file changes in the source directory and rebuilds automatically.
|
||||||
|
- `--clean` cleans the output directory before building (default behavior).
|
||||||
|
- `--no-clean` does not clean the output directory before building. Useful with `--watch` to avoid clearing output on every rebuild.
|
||||||
|
- `--serve` (`-s`) starts a local HTTP server (python3 or busybox) in the output directory after building. Use with the port number to specify the port. The default port is `8000`. Composable with `--watch`.
|
||||||
BIN
site/favicon.ico
Normal file
|
After Width: | Height: | Size: 361 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 149 KiB |
12
site/heaven/index.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
title = "Heaven"
|
||||||
|
content_warning = "This page may contain CSS"
|
||||||
|
---
|
||||||
|
# Heaven
|
||||||
|
|
||||||
|
Told you
|
||||||
|
|
||||||
|
Probably should've mentioned the catgirl too
|
||||||
|
|
||||||
|
| --- | --- |
|
||||||
|
| ```!![/styles.css]``` | <img style="vertical-align: top;" src="catgirl.jpg"> |
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
# _kewt_
|
# _kewt_
|
||||||
### Pronounced "cute"
|
### Pronounced "cute"
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
# [Go to the repo](https://git.krzak.org/N0VA/kewt)
|
# [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)_
|
_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)_)
|
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,69 +14,31 @@ It's meant to be a static site generator, like _[kew](https://github.com/uint23/
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- No dependencies
|
- No dependencies
|
||||||
|
- Frontmatter support (title, date, draft, description)
|
||||||
- Supports many embed types
|
- Supports many embed types
|
||||||
- Automatic css variable replacement for older browsers
|
- Automatic css variable replacement for older browsers
|
||||||
- Automatic inlining and embedding of many filetypes with `\![link]` or `\`
|
- Automatic inlining and embedding of many filetypes with `\![link]` or `\`
|
||||||
|
- Typed embeds: `\!i`, `\!v`, `\!a`, `\!f`, `\!e`
|
||||||
- Inline html support
|
- Inline html support
|
||||||
- MFM `$font` and `\<plain>` tags
|
- 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
|
||||||
|
- Customisable directory index pages with `{{LIST}}`
|
||||||
|
- Open Graph meta tags from frontmatter
|
||||||
|
- Auto-generated Table of Contents via `{{TOC}}`
|
||||||
|
- Footnotes (`[^id]`)
|
||||||
|
- Definition lists
|
||||||
|
- Emoji shortcodes (`:smile:`, `:fire:`, etc.)
|
||||||
|
- Post pagination
|
||||||
|
- `--watch` and `--serve` modes for development
|
||||||
|
|
||||||
If you want to **force** a file to be inlined, use `\!![]` instead of `\![]`
|
***
|
||||||
|
|
||||||
## Usage
|
## [Quickstart guide](/docs/quickstart.html)
|
||||||
|
|
||||||
```sh
|
|
||||||
./kewt.sh --help
|
|
||||||
./kewt.sh --new [title]
|
|
||||||
./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`.
|
|
||||||
|
|
||||||
## site.conf
|
|
||||||
|
|
||||||
```conf
|
|
||||||
title = "kewt"
|
|
||||||
style = "kewt"
|
|
||||||
dir_indexes = true
|
|
||||||
single_file_index = true
|
|
||||||
flatten = false
|
|
||||||
footer = "made with <a href="https://kewt.krzak.org">kewt</a>"
|
|
||||||
logo = ""
|
|
||||||
display_logo = false
|
|
||||||
display_title = true
|
|
||||||
logo_as_favicon = true
|
|
||||||
favicon = ""
|
|
||||||
```
|
|
||||||
|
|
||||||
- `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
|
|
||||||
- `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)
|
|
||||||
|
|
||||||
## 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>`
|
|
||||||
- `\` works the same, with `alt` used for images
|
|
||||||
- `\!![]` and `\!` force inline local file contents
|
|
||||||
|
|
||||||
## Credits
|
|
||||||
|
|
||||||
- Markdown to html conversion based on [markdown.bash](https://github.com/chadbraunduin/markdown.bash) by [chadbraunduin](https://github.com/chadbraunduin)
|
|
||||||
- Default css style and html template based on _[kew](https://github.com/uint23/kew)_ by [uint23](https://github.com/uint23)
|
|
||||||
|
|
||||||
# Warning
|
|
||||||
>![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...
|
|
||||||
|
|||||||
30
site/site.conf
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
title = "kewt"
|
||||||
|
style = "kewt"
|
||||||
|
dir_indexes = true
|
||||||
|
single_file_index = true
|
||||||
|
flatten = false
|
||||||
|
footer = "<a href=\"https://kewt.krzak.org\"><img src=\"/button.gif\" /></a>"
|
||||||
|
logo = ""
|
||||||
|
display_logo = false
|
||||||
|
display_title = true
|
||||||
|
logo_as_favicon = false
|
||||||
|
favicon = "favicon.ico"
|
||||||
|
order = "Home, docs, depths, heaven"
|
||||||
|
home_name = "Home"
|
||||||
|
show_home_in_nav = true
|
||||||
|
nav_links = ""
|
||||||
|
nav_extra = ""
|
||||||
|
generate_page_title = true
|
||||||
|
error_page = "not_found.html"
|
||||||
|
versioning = true
|
||||||
|
enable_header_links = true
|
||||||
|
base_url = "https://kewt.krzak.org"
|
||||||
|
custom_admonitions = ""
|
||||||
|
generate_feed = false
|
||||||
|
feed_file = "rss.xml"
|
||||||
|
posts_dir = ""
|
||||||
|
generate_tags = false
|
||||||
|
generate_search = true
|
||||||
|
search_in_footer = true
|
||||||
|
search_in_header = true
|
||||||
|
include_cw_pages_in_search = false
|
||||||
27
styles/grayscale-light.root.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #f5f5f5;
|
||||||
|
--bg-deep: #e8e8e8;
|
||||||
|
--fg: #1a1a1a;
|
||||||
|
--fg-muted: #808080;
|
||||||
|
--fg-link: #333333;
|
||||||
|
--fg-heading: #000000;
|
||||||
|
--code-bg: #e8e8e8;
|
||||||
|
--code-border: #c0c0c0;
|
||||||
|
--code-fg: #1a1a1a;
|
||||||
|
--code-sel: #555555;
|
||||||
|
--code-prop: #333333;
|
||||||
|
--code-val: #666666;
|
||||||
|
--code-var: #444444;
|
||||||
|
--code-com: #999999;
|
||||||
|
--adm-note-bg: #e0e0ee;
|
||||||
|
--adm-note-border: #8888aa;
|
||||||
|
--adm-tip-bg: #e0eee0;
|
||||||
|
--adm-tip-border: #88aa88;
|
||||||
|
--adm-important-bg: #eee0ee;
|
||||||
|
--adm-important-border: #aa88aa;
|
||||||
|
--adm-warning-bg: #eeeed0;
|
||||||
|
--adm-warning-border: #aaaa78;
|
||||||
|
--adm-caution-bg: #eee0e0;
|
||||||
|
--adm-caution-border: #aa8888;
|
||||||
|
--thead-bg: #e8e8e8;
|
||||||
|
}
|
||||||
27
styles/grayscale.root.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #1a1a1a;
|
||||||
|
--bg-deep: #111111;
|
||||||
|
--fg: #d4d4d4;
|
||||||
|
--fg-muted: #808080;
|
||||||
|
--fg-link: #e0e0e0;
|
||||||
|
--fg-heading: #ffffff;
|
||||||
|
--code-bg: #0d0d0d;
|
||||||
|
--code-border: #404040;
|
||||||
|
--code-fg: #d4d4d4;
|
||||||
|
--code-sel: #b8b8b8;
|
||||||
|
--code-prop: #e0e0e0;
|
||||||
|
--code-val: #a0a0a0;
|
||||||
|
--code-var: #c8c8c8;
|
||||||
|
--code-com: #585858;
|
||||||
|
--adm-note-bg: #1a1a2e;
|
||||||
|
--adm-note-border: #5a5a8a;
|
||||||
|
--adm-tip-bg: #1a2e1a;
|
||||||
|
--adm-tip-border: #5a8a5a;
|
||||||
|
--adm-important-bg: #2e1a2e;
|
||||||
|
--adm-important-border: #8a5a8a;
|
||||||
|
--adm-warning-bg: #2e2e1a;
|
||||||
|
--adm-warning-border: #8a8a5a;
|
||||||
|
--adm-caution-bg: #2e1a1a;
|
||||||
|
--adm-caution-border: #8a5a5a;
|
||||||
|
--thead-bg: #111111;
|
||||||
|
}
|
||||||
27
styles/kewt-light.root.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #f5f0ff;
|
||||||
|
--bg-deep: #e8dffa;
|
||||||
|
--fg: #2d1b4e;
|
||||||
|
--fg-muted: #7a6898;
|
||||||
|
--fg-link: #7b3fba;
|
||||||
|
--fg-heading: #3d2466;
|
||||||
|
--code-bg: #e8dffa;
|
||||||
|
--code-border: #d0c0e8;
|
||||||
|
--code-fg: #2d1b4e;
|
||||||
|
--code-sel: #b8860b;
|
||||||
|
--code-prop: #6f42c1;
|
||||||
|
--code-val: #0366d6;
|
||||||
|
--code-var: #22863a;
|
||||||
|
--code-com: #7a6898;
|
||||||
|
--adm-note-bg: #e0e0f8;
|
||||||
|
--adm-note-border: #5b6fc4;
|
||||||
|
--adm-tip-bg: #ddf0dd;
|
||||||
|
--adm-tip-border: #46a758;
|
||||||
|
--adm-important-bg: #eeddf5;
|
||||||
|
--adm-important-border: #8b5fc7;
|
||||||
|
--adm-warning-bg: #f5f0dd;
|
||||||
|
--adm-warning-border: #b8860b;
|
||||||
|
--adm-caution-bg: #f5dddd;
|
||||||
|
--adm-caution-border: #c44569;
|
||||||
|
--thead-bg: #e8dffa;
|
||||||
|
}
|
||||||
417
styles/kewt.css
@@ -1,25 +1,29 @@
|
|||||||
:root {
|
:root {
|
||||||
--bg: #646c7f;
|
--bg: #4a3b69;
|
||||||
--fg: #fffde0;
|
--bg-deep: #352654;
|
||||||
--fg-link: #fff18f;
|
--fg: #fbf5ff;
|
||||||
--code-bg: #32394a;
|
--fg-muted: #c8b9df;
|
||||||
--code-border: #8f95a4;
|
--fg-link: #dfaeff;
|
||||||
--code-fg: #fffde0;
|
--fg-heading: #debfff;
|
||||||
--code-sel: #fff18f;
|
--code-bg: #31234c;
|
||||||
--code-prop: #ffd27f;
|
--code-border: #8060af;
|
||||||
--code-val: #cde7ff;
|
--code-fg: #fbf5ff;
|
||||||
--code-var: #b9ffbe;
|
--code-sel: #ffef99;
|
||||||
--code-com: #d0d0d0;
|
--code-prop: #ffdfba;
|
||||||
--adm-note-bg: #3f5666;
|
--code-val: #cae2ff;
|
||||||
--adm-note-border: #a8d8ff;
|
--code-var: #caffc2;
|
||||||
--adm-tip-bg: #3f664c;
|
--code-com: #b8aac8;
|
||||||
--adm-tip-border: #b9ffbe;
|
--adm-note-bg: #353866;
|
||||||
--adm-important-bg: #5a4a6c;
|
--adm-note-border: #b8c5ff;
|
||||||
--adm-important-border: #e4c7ff;
|
--adm-tip-bg: #295246;
|
||||||
--adm-warning-bg: #6b5539;
|
--adm-tip-border: #aeffda;
|
||||||
--adm-warning-border: #ffe0a8;
|
--adm-important-bg: #533076;
|
||||||
--adm-caution-bg: #6f3f3f;
|
--adm-important-border: #f4d9ff;
|
||||||
--adm-caution-border: #ffb4b4;
|
--adm-warning-bg: #634631;
|
||||||
|
--adm-warning-border: #ffe2bd;
|
||||||
|
--adm-caution-bg: #662d43;
|
||||||
|
--adm-caution-border: #ffc4d5;
|
||||||
|
--thead-bg: #3d2d5c;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -29,18 +33,34 @@ body {
|
|||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
font-family: serif;
|
font-family: serif;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 1.2;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
padding-bottom: 0;
|
||||||
|
border-bottom: 1px solid var(--code-border);
|
||||||
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
header h1 {
|
header h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 35px;
|
font-size: 35px;
|
||||||
font-weight: normal;
|
font-weight: bold;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
color: var(--fg-heading);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-logo {
|
||||||
|
vertical-align: middle;
|
||||||
|
height: 1.1em;
|
||||||
|
width: auto;
|
||||||
|
border: none;
|
||||||
|
margin-right: 0.2em;
|
||||||
|
margin-top: -0.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
header a {
|
header a {
|
||||||
@@ -48,18 +68,26 @@ header a {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header a:hover {
|
||||||
|
color: var(--bg-deep);
|
||||||
|
background: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
#side-bar {
|
#side-bar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 80px;
|
top: 80px;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
|
margin-right: 14px;
|
||||||
|
border-right: 1px solid var(--code-border);
|
||||||
|
padding-right: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-title {
|
.side-title {
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
margin: 20px 0 8px 0;
|
margin: 20px 0 8px 0;
|
||||||
color: var(--fg);
|
color: var(--fg-heading);
|
||||||
}
|
}
|
||||||
|
|
||||||
#side-bar ul {
|
#side-bar ul {
|
||||||
@@ -78,6 +106,14 @@ a {
|
|||||||
padding: 1px 2px;
|
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 {
|
a:hover {
|
||||||
background: var(--fg);
|
background: var(--fg);
|
||||||
color: var(--bg);
|
color: var(--bg);
|
||||||
@@ -91,7 +127,7 @@ article {
|
|||||||
h3 {
|
h3 {
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
color: var(--fg);
|
color: var(--fg-heading);
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,11 +223,28 @@ pre code {
|
|||||||
border-color: var(--adm-caution-border);
|
border-color: var(--adm-caution-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cw-button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: var(--bg-deep);
|
||||||
|
border: 1px solid var(--code-border);
|
||||||
|
color: var(--fg);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cw-button:hover {
|
||||||
|
background: var(--fg);
|
||||||
|
color: var(--bg);
|
||||||
|
border-color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
padding-top: 80px;
|
padding-top: 60px;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
color: var(--fg-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
article,
|
article,
|
||||||
@@ -199,3 +252,315 @@ footer {
|
|||||||
margin-left: 240px;
|
margin-left: 240px;
|
||||||
margin-top: 0px;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
height: 0;
|
||||||
|
margin: 24px 0;
|
||||||
|
border: 0;
|
||||||
|
border-top: 1px solid var(--code-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 20px 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
border-bottom: 2px solid var(--code-border);
|
||||||
|
background: var(--thead-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-top: 1px solid var(--code-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background: var(--bg-deep);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-toggle,
|
||||||
|
.nav-toggle-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-toggle-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--fg-heading);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#side-bar {
|
||||||
|
display: none;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-toggle:checked ~ #side-bar {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-page {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-page input[type="text"] {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: inherit;
|
||||||
|
background: var(--code-bg);
|
||||||
|
color: var(--fg);
|
||||||
|
border: 1px solid var(--code-border);
|
||||||
|
border-radius: 3px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-page input[type="text"]:focus {
|
||||||
|
border-color: var(--fg-link);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-page button {
|
||||||
|
padding: 8px 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: inherit;
|
||||||
|
background: var(--bg-deep);
|
||||||
|
color: var(--fg);
|
||||||
|
border: 1px solid var(--code-border);
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-page button:hover {
|
||||||
|
background: var(--fg);
|
||||||
|
color: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result {
|
||||||
|
margin: 16px 0;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--code-bg);
|
||||||
|
border: 1px solid var(--code-border);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result a {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--fg-link);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result p {
|
||||||
|
margin: 6px 0 0 0;
|
||||||
|
color: var(--fg-muted);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer search */
|
||||||
|
.kewt-search-footer {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 4px;
|
||||||
|
margin-left: 12px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-footer input[type="text"] {
|
||||||
|
padding: 3px 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: inherit;
|
||||||
|
background: var(--code-bg);
|
||||||
|
color: var(--fg);
|
||||||
|
border: 1px solid var(--code-border);
|
||||||
|
border-radius: 3px;
|
||||||
|
outline: none;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-footer input[type="text"]:focus {
|
||||||
|
border-color: var(--fg-link);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-footer button {
|
||||||
|
padding: 3px 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: inherit;
|
||||||
|
background: var(--bg-deep);
|
||||||
|
color: var(--fg);
|
||||||
|
border: 1px solid var(--code-border);
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-footer button:hover {
|
||||||
|
background: var(--fg);
|
||||||
|
color: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-header {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 4px;
|
||||||
|
margin-left: auto;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-header input[type="text"] {
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: inherit;
|
||||||
|
background: var(--code-bg);
|
||||||
|
color: var(--fg);
|
||||||
|
border: 1px solid var(--code-border);
|
||||||
|
border-radius: 3px;
|
||||||
|
outline: none;
|
||||||
|
width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-header input[type="text"]:focus {
|
||||||
|
border-color: var(--fg-link);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-header button {
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: inherit;
|
||||||
|
background: var(--bg-deep);
|
||||||
|
color: var(--fg);
|
||||||
|
border: 1px solid var(--code-border);
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-header button:hover {
|
||||||
|
background: var(--fg);
|
||||||
|
color: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-nav {
|
||||||
|
display: none;
|
||||||
|
padding: 8px 0 12px 0;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
border-bottom: 1px solid var(--code-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-nav form {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-nav input[type="text"] {
|
||||||
|
flex: 1;
|
||||||
|
padding: 6px 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: inherit;
|
||||||
|
background: var(--code-bg);
|
||||||
|
color: var(--fg);
|
||||||
|
border: 1px solid var(--code-border);
|
||||||
|
border-radius: 3px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-nav input[type="text"]:focus {
|
||||||
|
border-color: var(--fg-link);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-nav button {
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: inherit;
|
||||||
|
background: var(--bg-deep);
|
||||||
|
color: var(--fg);
|
||||||
|
border: 1px solid var(--code-border);
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-nav button:hover {
|
||||||
|
background: var(--fg);
|
||||||
|
color: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
.kewt-search-header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-nav {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-page {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-page button {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-footer {
|
||||||
|
display: flex;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kewt-search-footer input[type="text"] {
|
||||||
|
flex: 1;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
27
styles/monokai-light.root.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #fafaf8;
|
||||||
|
--bg-deep: #f0efe8;
|
||||||
|
--fg: #272822;
|
||||||
|
--fg-muted: #8f8e84;
|
||||||
|
--fg-link: #5c8a1e;
|
||||||
|
--fg-heading: #272822;
|
||||||
|
--code-bg: #f0efe8;
|
||||||
|
--code-border: #cccbc2;
|
||||||
|
--code-fg: #272822;
|
||||||
|
--code-sel: #5c8a1e;
|
||||||
|
--code-prop: #c41e6a;
|
||||||
|
--code-val: #7a3ee0;
|
||||||
|
--code-var: #2e8faf;
|
||||||
|
--code-com: #8f8e84;
|
||||||
|
--adm-note-bg: #edf4f7;
|
||||||
|
--adm-note-border: #2e8faf;
|
||||||
|
--adm-tip-bg: #eef7ed;
|
||||||
|
--adm-tip-border: #5c8a1e;
|
||||||
|
--adm-important-bg: #f2eef7;
|
||||||
|
--adm-important-border: #7a3ee0;
|
||||||
|
--adm-warning-bg: #f7f5ed;
|
||||||
|
--adm-warning-border: #b0a01e;
|
||||||
|
--adm-caution-bg: #f7edee;
|
||||||
|
--adm-caution-border: #c41e6a;
|
||||||
|
--thead-bg: #f0efe8;
|
||||||
|
}
|
||||||
27
styles/monokai.root.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #272822;
|
||||||
|
--bg-deep: #1e1f1c;
|
||||||
|
--fg: #f8f8f2;
|
||||||
|
--fg-muted: #75715e;
|
||||||
|
--fg-link: #a6e22e;
|
||||||
|
--fg-heading: #f8f8f2;
|
||||||
|
--code-bg: #1e1f1c;
|
||||||
|
--code-border: #49483e;
|
||||||
|
--code-fg: #f8f8f2;
|
||||||
|
--code-sel: #a6e22e;
|
||||||
|
--code-prop: #f92672;
|
||||||
|
--code-val: #ae81ff;
|
||||||
|
--code-var: #66d9ef;
|
||||||
|
--code-com: #75715e;
|
||||||
|
--adm-note-bg: #272822;
|
||||||
|
--adm-note-border: #66d9ef;
|
||||||
|
--adm-tip-bg: #272822;
|
||||||
|
--adm-tip-border: #a6e22e;
|
||||||
|
--adm-important-bg: #272822;
|
||||||
|
--adm-important-border: #ae81ff;
|
||||||
|
--adm-warning-bg: #272822;
|
||||||
|
--adm-warning-border: #e6db74;
|
||||||
|
--adm-caution-bg: #272822;
|
||||||
|
--adm-caution-border: #f92672;
|
||||||
|
--thead-bg: #1e1f1c;
|
||||||
|
}
|
||||||
27
styles/nord-light.root.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #eceff4;
|
||||||
|
--bg-deep: #d8dee9;
|
||||||
|
--fg: #2e3440;
|
||||||
|
--fg-muted: #4c566a;
|
||||||
|
--fg-link: #5e81ac;
|
||||||
|
--fg-heading: #3b4252;
|
||||||
|
--code-bg: #d8dee9;
|
||||||
|
--code-border: #c5cdd9;
|
||||||
|
--code-fg: #2e3440;
|
||||||
|
--code-sel: #d08770;
|
||||||
|
--code-prop: #5e81ac;
|
||||||
|
--code-val: #8fbcbb;
|
||||||
|
--code-var: #a3be8c;
|
||||||
|
--code-com: #4c566a;
|
||||||
|
--adm-note-bg: #d8dee9;
|
||||||
|
--adm-note-border: #5e81ac;
|
||||||
|
--adm-tip-bg: #e0ebd8;
|
||||||
|
--adm-tip-border: #a3be8c;
|
||||||
|
--adm-important-bg: #e5dbe8;
|
||||||
|
--adm-important-border: #b48ead;
|
||||||
|
--adm-warning-bg: #ede5d6;
|
||||||
|
--adm-warning-border: #ebcb8b;
|
||||||
|
--adm-caution-bg: #eddcdc;
|
||||||
|
--adm-caution-border: #bf616a;
|
||||||
|
--thead-bg: #d8dee9;
|
||||||
|
}
|
||||||
27
styles/nord.root.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #2e3440;
|
||||||
|
--bg-deep: #242933;
|
||||||
|
--fg: #d8dee9;
|
||||||
|
--fg-muted: #a5b0c1;
|
||||||
|
--fg-link: #88c0d0;
|
||||||
|
--fg-heading: #eceff4;
|
||||||
|
--code-bg: #3b4252;
|
||||||
|
--code-border: #4c566a;
|
||||||
|
--code-fg: #d8dee9;
|
||||||
|
--code-sel: #ebcb8b;
|
||||||
|
--code-prop: #8fbcbb;
|
||||||
|
--code-val: #81a1c1;
|
||||||
|
--code-var: #a3be8c;
|
||||||
|
--code-com: #616e88;
|
||||||
|
--adm-note-bg: #3b4252;
|
||||||
|
--adm-note-border: #88c0d0;
|
||||||
|
--adm-tip-bg: #3b4340;
|
||||||
|
--adm-tip-border: #a3be8c;
|
||||||
|
--adm-important-bg: #3b4044;
|
||||||
|
--adm-important-border: #b48ead;
|
||||||
|
--adm-warning-bg: #3b4038;
|
||||||
|
--adm-warning-border: #ebcb8b;
|
||||||
|
--adm-caution-bg: #3b3840;
|
||||||
|
--adm-caution-border: #bf616a;
|
||||||
|
--thead-bg: #3b4252;
|
||||||
|
}
|
||||||
27
styles/onedark.root.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #282c34;
|
||||||
|
--bg-deep: #21252b;
|
||||||
|
--fg: #abb2bf;
|
||||||
|
--fg-muted: #636d83;
|
||||||
|
--fg-link: #61afef;
|
||||||
|
--fg-heading: #c8ccd4;
|
||||||
|
--code-bg: #21252b;
|
||||||
|
--code-border: #3e4451;
|
||||||
|
--code-fg: #abb2bf;
|
||||||
|
--code-sel: #e5c07b;
|
||||||
|
--code-prop: #e06c75;
|
||||||
|
--code-val: #56b6c2;
|
||||||
|
--code-var: #98c379;
|
||||||
|
--code-com: #5c6370;
|
||||||
|
--adm-note-bg: #2c313c;
|
||||||
|
--adm-note-border: #61afef;
|
||||||
|
--adm-tip-bg: #2c3a30;
|
||||||
|
--adm-tip-border: #98c379;
|
||||||
|
--adm-important-bg: #33303c;
|
||||||
|
--adm-important-border: #c678dd;
|
||||||
|
--adm-warning-bg: #3a352c;
|
||||||
|
--adm-warning-border: #e5c07b;
|
||||||
|
--adm-caution-bg: #3a2c2e;
|
||||||
|
--adm-caution-border: #e06c75;
|
||||||
|
--thead-bg: #21252b;
|
||||||
|
}
|
||||||
27
styles/onelight.root.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #fafafa;
|
||||||
|
--bg-deep: #eaeaeb;
|
||||||
|
--fg: #383a42;
|
||||||
|
--fg-muted: #a0a1a7;
|
||||||
|
--fg-link: #4078f2;
|
||||||
|
--fg-heading: #383a42;
|
||||||
|
--code-bg: #eaeaeb;
|
||||||
|
--code-border: #d4d4d5;
|
||||||
|
--code-fg: #383a42;
|
||||||
|
--code-sel: #986801;
|
||||||
|
--code-prop: #e45649;
|
||||||
|
--code-val: #0184bc;
|
||||||
|
--code-var: #50a14f;
|
||||||
|
--code-com: #a0a1a7;
|
||||||
|
--adm-note-bg: #e8eefa;
|
||||||
|
--adm-note-border: #4078f2;
|
||||||
|
--adm-tip-bg: #e8f5e8;
|
||||||
|
--adm-tip-border: #50a14f;
|
||||||
|
--adm-important-bg: #f2e8f5;
|
||||||
|
--adm-important-border: #a626a4;
|
||||||
|
--adm-warning-bg: #f5f0e0;
|
||||||
|
--adm-warning-border: #986801;
|
||||||
|
--adm-caution-bg: #fae8e8;
|
||||||
|
--adm-caution-border: #e45649;
|
||||||
|
--thead-bg: #eaeaeb;
|
||||||
|
}
|
||||||
27
styles/rosepine-light.root.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #faf4ed;
|
||||||
|
--bg-deep: #f2e9e1;
|
||||||
|
--fg: #575279;
|
||||||
|
--fg-muted: #9893a5;
|
||||||
|
--fg-link: #907aa9;
|
||||||
|
--fg-heading: #286983;
|
||||||
|
--code-bg: #f2e9e1;
|
||||||
|
--code-border: #dfdad9;
|
||||||
|
--code-fg: #575279;
|
||||||
|
--code-sel: #ea9d34;
|
||||||
|
--code-prop: #b4637a;
|
||||||
|
--code-val: #56949f;
|
||||||
|
--code-var: #286983;
|
||||||
|
--code-com: #9893a5;
|
||||||
|
--adm-note-bg: #f0e8f5;
|
||||||
|
--adm-note-border: #907aa9;
|
||||||
|
--adm-tip-bg: #e8f0ee;
|
||||||
|
--adm-tip-border: #56949f;
|
||||||
|
--adm-important-bg: #f0e8f0;
|
||||||
|
--adm-important-border: #907aa9;
|
||||||
|
--adm-warning-bg: #f5f0e0;
|
||||||
|
--adm-warning-border: #ea9d34;
|
||||||
|
--adm-caution-bg: #f5e5e8;
|
||||||
|
--adm-caution-border: #b4637a;
|
||||||
|
--thead-bg: #f2e9e1;
|
||||||
|
}
|
||||||
27
styles/rosepine.root.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #191724;
|
||||||
|
--bg-deep: #13111e;
|
||||||
|
--fg: #e0def4;
|
||||||
|
--fg-muted: #908caa;
|
||||||
|
--fg-link: #c4a7e7;
|
||||||
|
--fg-heading: #ebbcba;
|
||||||
|
--code-bg: #1f1d2e;
|
||||||
|
--code-border: #26233a;
|
||||||
|
--code-fg: #e0def4;
|
||||||
|
--code-sel: #f6c177;
|
||||||
|
--code-prop: #eb6f92;
|
||||||
|
--code-val: #9ccfd8;
|
||||||
|
--code-var: #31748f;
|
||||||
|
--code-com: #6e6a86;
|
||||||
|
--adm-note-bg: #1f1d2e;
|
||||||
|
--adm-note-border: #c4a7e7;
|
||||||
|
--adm-tip-bg: #1a2332;
|
||||||
|
--adm-tip-border: #9ccfd8;
|
||||||
|
--adm-important-bg: #2a1f2e;
|
||||||
|
--adm-important-border: #c4a7e7;
|
||||||
|
--adm-warning-bg: #2a251f;
|
||||||
|
--adm-warning-border: #f6c177;
|
||||||
|
--adm-caution-bg: #2a1f22;
|
||||||
|
--adm-caution-border: #eb6f92;
|
||||||
|
--thead-bg: #1f1d2e;
|
||||||
|
}
|
||||||
27
styles/solarized-dark.root.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #002b36;
|
||||||
|
--bg-deep: #001e26;
|
||||||
|
--fg: #839496;
|
||||||
|
--fg-muted: #586e75;
|
||||||
|
--fg-link: #268bd2;
|
||||||
|
--fg-heading: #93a1a1;
|
||||||
|
--code-bg: #073642;
|
||||||
|
--code-border: #094959;
|
||||||
|
--code-fg: #839496;
|
||||||
|
--code-sel: #d33682;
|
||||||
|
--code-prop: #268bd2;
|
||||||
|
--code-val: #2aa198;
|
||||||
|
--code-var: #859900;
|
||||||
|
--code-com: #586e75;
|
||||||
|
--adm-note-bg: #073642;
|
||||||
|
--adm-note-border: #268bd2;
|
||||||
|
--adm-tip-bg: #07382e;
|
||||||
|
--adm-tip-border: #2aa198;
|
||||||
|
--adm-important-bg: #2a0736;
|
||||||
|
--adm-important-border: #d33682;
|
||||||
|
--adm-warning-bg: #363007;
|
||||||
|
--adm-warning-border: #b58900;
|
||||||
|
--adm-caution-bg: #360a07;
|
||||||
|
--adm-caution-border: #cb4b16;
|
||||||
|
--thead-bg: #073642;
|
||||||
|
}
|
||||||
27
styles/solarized.root.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #fdf6e3;
|
||||||
|
--bg-deep: #eee8d5;
|
||||||
|
--fg: #657b83;
|
||||||
|
--fg-muted: #93a1a1;
|
||||||
|
--fg-link: #268bd2;
|
||||||
|
--fg-heading: #586e75;
|
||||||
|
--code-bg: #eee8d5;
|
||||||
|
--code-border: #d3cbb7;
|
||||||
|
--code-fg: #657b83;
|
||||||
|
--code-sel: #d33682;
|
||||||
|
--code-prop: #268bd2;
|
||||||
|
--code-val: #2aa198;
|
||||||
|
--code-var: #859900;
|
||||||
|
--code-com: #93a1a1;
|
||||||
|
--adm-note-bg: #eee8d5;
|
||||||
|
--adm-note-border: #268bd2;
|
||||||
|
--adm-tip-bg: #e8f5e0;
|
||||||
|
--adm-tip-border: #859900;
|
||||||
|
--adm-important-bg: #f0e8f5;
|
||||||
|
--adm-important-border: #6c71c4;
|
||||||
|
--adm-warning-bg: #fdf0e3;
|
||||||
|
--adm-warning-border: #b58900;
|
||||||
|
--adm-caution-bg: #fde8e8;
|
||||||
|
--adm-caution-border: #dc322f;
|
||||||
|
--thead-bg: #eee8d5;
|
||||||
|
}
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<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>
|
|
||||||
39
tools/build-standalone.sh
Executable file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
|
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" "$REPO_ROOT/lib" "$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 lib >> "$OUT_FILE"
|
||||||
|
rm -rf "$tmpbuild"
|
||||||
|
|
||||||
|
chmod +x "$OUT_FILE"
|
||||||
|
|
||||||
|
echo "Generated standalone executable at $OUT_FILE"
|
||||||