Compare commits
52 Commits
9095f0db44
...
v1.3.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
16
.gitea/workflows/lint.yml
Normal file
16
.gitea/workflows/lint.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
shellcheck:
|
||||
runs-on: local
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Shellcheck
|
||||
run: sudo apt-get update && sudo apt-get install -y shellcheck
|
||||
- name: Run Shellcheck
|
||||
run: shellcheck -s sh kewt.sh markdown.sh tools/build-standalone.sh || true
|
||||
35
.gitea/workflows/publish-aur-git.yml
Normal file
35
.gitea/workflows/publish-aur-git.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Publish kewt-git to AUR
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'packaging/AUR/PKGBUILD.git'
|
||||
- 'packaging/AUR/.SRCINFO.git'
|
||||
|
||||
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 }}"
|
||||
105
.gitea/workflows/release.yml
Normal file
105
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,105 @@
|
||||
name: Release Standalone Builder
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: local
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build standalone executable
|
||||
run: |
|
||||
chmod +x tools/build-standalone.sh
|
||||
./tools/build-standalone.sh
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.21'
|
||||
|
||||
- name: Upload Release Asset
|
||||
uses: https://gitea.com/actions/release-action@main
|
||||
with:
|
||||
files: |-
|
||||
kewt
|
||||
api_key: '${{secrets.GITEA_TOKEN}}'
|
||||
|
||||
- name: Push to GitHub Release
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
|
||||
# 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 }}"
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,4 @@
|
||||
out/
|
||||
kewt
|
||||
site.conf
|
||||
template.html
|
||||
|
||||
34
LICENSE
Normal file
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.
|
||||
75
README.md
75
README.md
@@ -1,78 +1,31 @@
|
||||
# _kewt_
|
||||
# 
|
||||
### Pronounced "cute"
|
||||
|
||||
***
|
||||
|
||||
# [Go to the website](https://kewt.krzak.org)
|
||||
|
||||
***
|
||||
|
||||
_kewt_ is a minimalist ssg inspired by _[werc](http://werc.cat-v.org/)_ and _[kew](https://github.com/uint23/kew)_
|
||||
|
||||
It's meant to be a static site generator, like _[kew](https://github.com/uint23/kew)_ but use only default (POSIX) tooling, like _[werc](http://werc.cat-v.org/)_ (and definitely unlike _[kew](https://github.com/uint23/kew)_)
|
||||
|
||||
## Features
|
||||
|
||||
- No dependencies
|
||||
- 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 `\![]`
|
||||
|
||||
## Usage
|
||||
## Quick Install
|
||||
|
||||
```sh
|
||||
./kewt.sh --help
|
||||
./kewt.sh --new [title]
|
||||
./kewt.sh --from <src> --to <out>
|
||||
./kewt.sh [src] [out]
|
||||
curl -L -o kewt https://git.krzak.org/N0VA/kewt/releases/download/latest/kewt
|
||||
chmod +x kewt
|
||||
```
|
||||
|
||||
`--new [title]` creates a new site directory with a copied `site.conf` and a default `index.md`.
|
||||
On Arch Linux, _kewt_ is available on the AUR:
|
||||
|
||||
## site.conf
|
||||
- [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
|
||||
|
||||
```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 = ""
|
||||
```
|
||||
## Contributing
|
||||
|
||||
- `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
|
||||
Either through a pull request to the **home** repository ([N0VA/kewt](https://git.krzak.org/N0VA/kewt)) or by sending a patch to my email address ([n0va@krzak.org](mailto:n0va@krzak.org))
|
||||
|
||||
## 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...
|
||||
- _kew_ css style adapted from _[kew](https://github.com/uint23/kew)_ by [uint23](https://github.com/uint23)
|
||||
|
||||
14
awk/blockquote.awk
Normal file
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>"
|
||||
}
|
||||
46
awk/blockquote_to_admonition.awk
Normal file
46
awk/blockquote_to_admonition.awk
Normal file
@@ -0,0 +1,46 @@
|
||||
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)
|
||||
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++
|
||||
}
|
||||
}
|
||||
5
awk/breaks.awk
Normal file
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 {
|
||||
src = ENVIRON["AWK_SRC"]
|
||||
slen = length(src)
|
||||
}
|
||||
|
||||
|
||||
44
awk/fenced_code.awk
Normal file
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>"
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,42 @@ function title_from_name(name) {
|
||||
return name
|
||||
}
|
||||
|
||||
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 {
|
||||
src = ENVIRON["AWK_SRC"]
|
||||
single_file_index = ENVIRON["AWK_SINGLE_FILE_INDEX"]
|
||||
flatten = ENVIRON["AWK_FLATTEN"]
|
||||
order = ENVIRON["AWK_ORDER"]
|
||||
home_name = ENVIRON["AWK_HOME_NAME"]
|
||||
show_home_in_nav = ENVIRON["AWK_SHOW_HOME_IN_NAV"]
|
||||
dinfo = ENVIRON["AWK_DINFO"]
|
||||
n_dlines = split(dinfo, dlines, "\n")
|
||||
for (i = 1; i <= n_dlines; i++) {
|
||||
if (split(dlines[i], dparts, "|") == 3) {
|
||||
@@ -12,6 +47,16 @@ BEGIN {
|
||||
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 +76,20 @@ BEGIN {
|
||||
}
|
||||
|
||||
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>"
|
||||
if ("index.md" in all_paths) {
|
||||
print "<li><a href=\"/index.html\">Home</a></li>"
|
||||
if (show_home_in_nav == "true" && "index.md" in all_paths) {
|
||||
if (home_name == "") home_name = "Home"
|
||||
print "<li><a href=\"/index.html\">" home_name "</a></li>"
|
||||
}
|
||||
|
||||
depth = 0
|
||||
|
||||
82
awk/headers.awk
Normal file
82
awk/headers.awk
Normal file
@@ -0,0 +1,82 @@
|
||||
function strip_markdown(s) {
|
||||
gsub(/<[^>]+>/, "", s)
|
||||
gsub(/[*_`~]/, "", s)
|
||||
gsub(/[\[\]]/, "", s)
|
||||
gsub(/\([^\)]*\)/, "", s)
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/, "", s)
|
||||
gsub(/[[:space:]]+/, "-", s)
|
||||
return s
|
||||
}
|
||||
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 != "") {
|
||||
id = strip_markdown(line)
|
||||
if (enable_header_links == "true") {
|
||||
print "<" tag " id=\"" id "\"><a href=\"#" id "\" class=\"header-anchor\">" line "</a></" tag ">"
|
||||
} else {
|
||||
print "<" tag " id=\"" id "\">" line "</" tag ">"
|
||||
}
|
||||
} 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 "<h1 id=\"" strip_markdown(prev) "\">" prev "</h1>"
|
||||
has_prev = 0
|
||||
} else {
|
||||
if (has_prev) print_header(prev)
|
||||
print $0
|
||||
has_prev = 0
|
||||
}
|
||||
} else if ($0 ~ /^-+$/) {
|
||||
if (has_prev && prev != "" && prev !~ /^<[a-z]/) {
|
||||
print "<h2 id=\"" strip_markdown(prev) "\">" prev "</h2>"
|
||||
has_prev = 0
|
||||
} else {
|
||||
if (has_prev) print_header(prev)
|
||||
if (length($0) >= 3) print "<hr />"
|
||||
else print $0
|
||||
has_prev = 0
|
||||
}
|
||||
} else if ($0 ~ /^[*_]+$/ && length($0) >= 3) {
|
||||
if (has_prev) print_header(prev)
|
||||
print "<hr />"
|
||||
has_prev = 0
|
||||
} else {
|
||||
if (has_prev) {
|
||||
print_header(prev)
|
||||
}
|
||||
prev = $0
|
||||
has_prev = 1
|
||||
}
|
||||
}
|
||||
END {
|
||||
if (has_prev) {
|
||||
print_header(prev)
|
||||
}
|
||||
}
|
||||
20
awk/indented_code.awk
Normal file
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>" }
|
||||
66
awk/lists.awk
Normal file
66
awk/lists.awk
Normal file
@@ -0,0 +1,66 @@
|
||||
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 ">"
|
||||
}
|
||||
}
|
||||
|
||||
print "<li>" content "</li>"
|
||||
} else {
|
||||
while (depth > 0) {
|
||||
print "</" cur_type[depth] ">"
|
||||
depth--
|
||||
}
|
||||
print line
|
||||
}
|
||||
}
|
||||
|
||||
END {
|
||||
while (depth > 0) {
|
||||
print "</" cur_type[depth] ">"
|
||||
depth--
|
||||
}
|
||||
}
|
||||
@@ -119,11 +119,35 @@ function css_highlight_line(line, m, prop, val) {
|
||||
return "<span class=\"tok-punc\">}</span>"
|
||||
}
|
||||
|
||||
if (match(line, /^([[:space:]]*)(--?[A-Za-z0-9_-]+)([[:space:]]*:[[:space:]]*)([^;]*)(;?[[:space:]]*)$/, m)) {
|
||||
prop = "<span class=\"tok-prop\">" m[2] "</span>"
|
||||
gsub(/var\(--[A-Za-z0-9_-]+\)/, "<span class=\"tok-var\">&</span>", m[4])
|
||||
val = "<span class=\"tok-val\">" m[4] "</span>"
|
||||
return m[1] prop m[3] val m[5]
|
||||
if (line ~ /^[[:space:]]*--?[A-Za-z0-9_-]+[[:space:]]*:[[:space:]]*[^;]*;?[[:space:]]*$/) {
|
||||
match(line, /:[[:space:]]*/)
|
||||
sep_pos = RSTART
|
||||
sep_len = RLENGTH
|
||||
|
||||
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
|
||||
|
||||
202
awk/markdown_inline.awk
Normal file
202
awk/markdown_inline.awk
Normal file
@@ -0,0 +1,202 @@
|
||||
BEGIN {
|
||||
in_pre = 0
|
||||
}
|
||||
|
||||
function mask_html_tags(s, out, rest, start, len, tag, token) {
|
||||
out = ""
|
||||
rest = s
|
||||
html_tag_count = 0
|
||||
while (match(rest, /<[^>]+>/)) {
|
||||
out = out substr(rest, 1, RSTART - 1)
|
||||
start = RSTART
|
||||
len = RLENGTH
|
||||
tag = substr(rest, start, len)
|
||||
html_tag_count++
|
||||
html_tag_token[html_tag_count] = "\034HT" html_tag_count "\034"
|
||||
html_tag_value[html_tag_count] = tag
|
||||
out = out html_tag_token[html_tag_count]
|
||||
rest = substr(rest, start + len)
|
||||
}
|
||||
return out rest
|
||||
}
|
||||
|
||||
function restore_html_tags(s, i) {
|
||||
for (i = 1; i <= html_tag_count; i++) {
|
||||
gsub(html_tag_token[i], html_tag_value[i], s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
{
|
||||
if ($0 ~ /<pre>/) {
|
||||
in_pre = 1
|
||||
}
|
||||
|
||||
if (in_pre) {
|
||||
print
|
||||
if ($0 ~ /<\/pre>/) {
|
||||
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)
|
||||
}
|
||||
|
||||
# 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
|
||||
}
|
||||
}
|
||||
|
||||
print line
|
||||
}
|
||||
85
awk/mask_inline_code.awk
Normal file
85
awk/mask_inline_code.awk
Normal file
@@ -0,0 +1,85 @@
|
||||
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
|
||||
if (plen >= 3) {
|
||||
out = out substr(line, p, pstart - p + plen)
|
||||
p = pstart + plen
|
||||
continue
|
||||
}
|
||||
|
||||
# Found 1 or 2 backticks at pstart
|
||||
# Search for closing marker
|
||||
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)
|
||||
}
|
||||
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
|
||||
}
|
||||
52
awk/mask_plain.awk
Normal file
52
awk/mask_plain.awk
Normal file
@@ -0,0 +1,52 @@
|
||||
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
|
||||
}
|
||||
43
awk/paragraphs.awk
Normal file
43
awk/paragraphs.awk
Normal file
@@ -0,0 +1,43 @@
|
||||
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)/) {
|
||||
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
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,9 +1,24 @@
|
||||
function replace_all(text, token, value, pos, token_len) {
|
||||
function replace_all(text, token, value, pos, token_len, res) {
|
||||
token_len = length(token)
|
||||
res = ""
|
||||
while ((pos = index(text, token)) > 0) {
|
||||
text = substr(text, 1, pos - 1) value substr(text, pos + token_len)
|
||||
res = res substr(text, 1, pos - 1) value
|
||||
text = substr(text, pos + token_len)
|
||||
}
|
||||
return res text
|
||||
}
|
||||
|
||||
BEGIN {
|
||||
current_url = ENVIRON["AWK_CURRENT_URL"]
|
||||
nav = ENVIRON["AWK_NAV"]
|
||||
title = ENVIRON["AWK_TITLE"]
|
||||
footer = ENVIRON["AWK_FOOTER"]
|
||||
style_path = ENVIRON["AWK_STYLE_PATH"]
|
||||
head_extra = ENVIRON["AWK_HEAD_EXTRA"]
|
||||
header_brand = ENVIRON["AWK_HEADER_BRAND"]
|
||||
if (current_url != "") {
|
||||
nav = replace_all(nav, "href=\"" current_url "\"", "href=\"" current_url "\" class=\"current-page\"")
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
13
awk/update_site_conf.awk
Normal file
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
BIN
button.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
21
icon.svg
Normal file
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 |
680
kewt.sh
680
kewt.sh
@@ -6,23 +6,33 @@ die() {
|
||||
}
|
||||
|
||||
usage() {
|
||||
invoked_as=$(basename "${KEWT_INVOKED_AS:-$0}")
|
||||
cat <<EOF
|
||||
Usage: $0 [--from <src>] [--to <out>]
|
||||
$0 [src] [out]
|
||||
$0 --new [title]
|
||||
$0 --help
|
||||
Usage: $invoked_as [--from <src>] [--to <out>]
|
||||
$invoked_as [src] [out]
|
||||
$invoked_as --new [title]
|
||||
$invoked_as --update [dir]
|
||||
$invoked_as --post
|
||||
$invoked_as --version
|
||||
$invoked_as --help
|
||||
|
||||
Options:
|
||||
--help Show this help message.
|
||||
--new [title] Create a new site directory (default: site)
|
||||
--update [dir] Update site.conf and template.html with latest defaults (defaults to current directory)
|
||||
--post Create a new empty post file in the configured posts_dir with current date and time as name
|
||||
--version Show version information.
|
||||
--from <src> Source directory (default: site)
|
||||
--to <out> Output directory (default: out)
|
||||
EOF
|
||||
}
|
||||
|
||||
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
script_dir=$(CDPATH="" cd -- "$(dirname -- "$0")" && pwd)
|
||||
awk_dir="$script_dir/awk"
|
||||
|
||||
KEWT_TMPDIR=$(mktemp -d "/tmp/kewt_run.XXXXXX")
|
||||
trap 'rm -rf "$KEWT_TMPDIR"' EXIT HUP INT TERM
|
||||
|
||||
ensure_root_defaults() {
|
||||
if [ ! -f "./site.conf" ]; then
|
||||
cat > "./site.conf" <<'EOF'
|
||||
@@ -31,12 +41,25 @@ style = "kewt"
|
||||
dir_indexes = true
|
||||
single_file_index = true
|
||||
flatten = false
|
||||
footer = "made with <a href="https://kewt.krzak.org">kewt</a>"
|
||||
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 = ""
|
||||
EOF
|
||||
fi
|
||||
|
||||
@@ -46,6 +69,7 @@ EOF
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{{TITLE}}</title>
|
||||
|
||||
<link rel="stylesheet" href="{{CSS}}" type="text/css" />
|
||||
@@ -81,33 +105,149 @@ create_new_site() {
|
||||
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"
|
||||
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
|
||||
}
|
||||
|
||||
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"
|
||||
create_new_post() {
|
||||
post_src_dir="$1"
|
||||
|
||||
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
|
||||
|
||||
touch "$file_path"
|
||||
|
||||
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"
|
||||
cat > "$default_conf" <<'CONFEOF'
|
||||
title = "kewt"
|
||||
style = "kewt"
|
||||
dir_indexes = true
|
||||
single_file_index = true
|
||||
flatten = false
|
||||
order = ""
|
||||
home_name = "Home"
|
||||
show_home_in_nav = true
|
||||
nav_links = ""
|
||||
nav_extra = ""
|
||||
footer = "made with <a href=\"https://kewt.krzak.org\">kewt</a>"
|
||||
logo = ""
|
||||
display_logo = false
|
||||
display_title = true
|
||||
logo_as_favicon = true
|
||||
favicon = ""
|
||||
generate_page_title = true
|
||||
error_page = "not_found.html"
|
||||
versioning = false
|
||||
enable_header_links = true
|
||||
base_url = ""
|
||||
generate_feed = false
|
||||
feed_file = "rss.xml"
|
||||
posts_dir = ""
|
||||
CONFEOF
|
||||
|
||||
# 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"
|
||||
cat > "$default_tmpl" <<'TMPLEOF'
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{{TITLE}}</title>
|
||||
|
||||
<link rel="stylesheet" href="{{CSS}}" type="text/css" />
|
||||
{{HEAD_EXTRA}}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h1>{{HEADER_BRAND}}</h1>
|
||||
</header>
|
||||
|
||||
<nav id="side-bar">{{NAV}}</nav>
|
||||
|
||||
<article>{{CONTENT}}</article>
|
||||
<footer>{{FOOTER}}</footer>
|
||||
</body>
|
||||
</html>
|
||||
TMPLEOF
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
||||
src=""
|
||||
out=""
|
||||
new_mode="false"
|
||||
new_title=""
|
||||
post_mode="false"
|
||||
post_title=""
|
||||
positional_count=0
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
@@ -123,6 +263,21 @@ while [ $# -gt 0 ]; do
|
||||
shift
|
||||
fi
|
||||
;;
|
||||
--version|-v)
|
||||
echo "kewt version git"
|
||||
exit 0
|
||||
;;
|
||||
--post)
|
||||
post_mode="true"
|
||||
;;
|
||||
--update)
|
||||
update_dir="."
|
||||
if [ $# -gt 1 ] && [ "${2#-}" = "$2" ]; then
|
||||
update_dir="$2"
|
||||
shift
|
||||
fi
|
||||
update_site "$update_dir"
|
||||
;;
|
||||
--from)
|
||||
[ $# -lt 2 ] && die "--from requires a value."
|
||||
src="$2"
|
||||
@@ -139,9 +294,9 @@ while [ $# -gt 0 ]; do
|
||||
*)
|
||||
positional_count=$((positional_count + 1))
|
||||
if [ "$positional_count" -eq 1 ]; then
|
||||
[ -z "$src" ] && src="$1" || die "Source already set (use either positional or --from)."
|
||||
if [ -z "$src" ]; then src="$1"; else die "Source already set (use either positional or --from)."; fi
|
||||
elif [ "$positional_count" -eq 2 ]; then
|
||||
[ -z "$out" ] && out="$1" || die "Output already set (use either positional or --to)."
|
||||
if [ -z "$out" ]; then out="$1"; else die "Output already set (use either positional or --to)."; fi
|
||||
else
|
||||
die "Too many positional arguments."
|
||||
fi
|
||||
@@ -154,10 +309,125 @@ done
|
||||
|
||||
ensure_root_defaults
|
||||
|
||||
[ -z "$src" ] && src="site"
|
||||
if [ -z "$src" ]; then
|
||||
if [ "$post_mode" = "true" ] && [ -f "./site.conf" ]; then
|
||||
src="."
|
||||
else
|
||||
src="site"
|
||||
fi
|
||||
fi
|
||||
[ -z "$out" ] && out="out"
|
||||
|
||||
[ -d "$src" ] || die "Source directory '$src' does not exist."
|
||||
src="${src%/}"
|
||||
out="${out%/}"
|
||||
|
||||
if [ ! -d "$src" ]; then
|
||||
if [ "$src" = "site" ]; then
|
||||
usage
|
||||
exit 1
|
||||
else
|
||||
die "Source directory '$src' does not exist."
|
||||
fi
|
||||
fi
|
||||
|
||||
IGNORE_ARGS="-name '.kewtignore' -o -path '$src/.*'"
|
||||
|
||||
if [ -f "$src/.kewtignore" ]; then
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
case "$line" in
|
||||
''|'#'*) continue ;;
|
||||
esac
|
||||
pattern=$(echo "$line" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
|
||||
[ -z "$pattern" ] && continue
|
||||
|
||||
pattern_clean="${pattern#/}"
|
||||
pattern_clean="${pattern_clean%/}"
|
||||
|
||||
if echo "$pattern" | grep -q "/"; then
|
||||
IGNORE_ARGS="$IGNORE_ARGS -o -path '$src/$pattern_clean' -o -path '$src/$pattern_clean/*'"
|
||||
else
|
||||
IGNORE_ARGS="$IGNORE_ARGS -o -name '$pattern_clean'"
|
||||
fi
|
||||
done < "$src/.kewtignore"
|
||||
fi
|
||||
|
||||
find "$src" -name .kewtignore > "$KEWT_TMPDIR/kewt_ignore"
|
||||
while read -r ki; do
|
||||
d="${ki%/.kewtignore}"
|
||||
if [ "$d" != "$src" ] && [ "$d" != "." ]; then
|
||||
IGNORE_ARGS="$IGNORE_ARGS -o -path '$d' -o -path '$d/*'"
|
||||
fi
|
||||
done < "$KEWT_TMPDIR/kewt_ignore"
|
||||
rm -f "$KEWT_TMPDIR/kewt_ignore"
|
||||
|
||||
HIDE_ARGS="-name '.kewtignore' -o -name '.kewthide' -o -name '.kewtpreserve' -o -path '$src/.*'"
|
||||
|
||||
if [ -f "$src/.kewthide" ]; then
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
case "$line" in
|
||||
''|'#'*) continue ;;
|
||||
esac
|
||||
pattern=$(echo "$line" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
|
||||
[ -z "$pattern" ] && continue
|
||||
|
||||
pattern_clean="${pattern#/}"
|
||||
pattern_clean="${pattern_clean%/}"
|
||||
|
||||
if echo "$pattern" | grep -q "/"; then
|
||||
HIDE_ARGS="$HIDE_ARGS -o -path '$src/$pattern_clean' -o -path '$src/$pattern_clean/*'"
|
||||
else
|
||||
HIDE_ARGS="$HIDE_ARGS -o -name '$pattern_clean'"
|
||||
fi
|
||||
done < "$src/.kewthide"
|
||||
fi
|
||||
|
||||
find "$src" -name .kewthide > "$KEWT_TMPDIR/kewt_hide"
|
||||
while read -r kh; do
|
||||
d="${kh%/.kewthide}"
|
||||
if [ "$d" != "$src" ] && [ "$d" != "." ]; then
|
||||
HIDE_ARGS="$HIDE_ARGS -o -path '$d' -o -path '$d/*'"
|
||||
fi
|
||||
done < "$KEWT_TMPDIR/kewt_hide"
|
||||
rm -f "$KEWT_TMPDIR/kewt_hide"
|
||||
|
||||
PRESERVE_ARGS="-false"
|
||||
|
||||
if [ -f "$src/.kewtpreserve" ]; then
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
case "$line" in
|
||||
''|'#'*) continue ;;
|
||||
esac
|
||||
pattern=$(echo "$line" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
|
||||
[ -z "$pattern" ] && continue
|
||||
|
||||
pattern_clean="${pattern#/}"
|
||||
pattern_clean="${pattern_clean%/}"
|
||||
|
||||
if echo "$pattern" | grep -q "/"; then
|
||||
PRESERVE_ARGS="$PRESERVE_ARGS -o -path '$src/$pattern_clean' -o -path '$src/$pattern_clean/*'"
|
||||
else
|
||||
PRESERVE_ARGS="$PRESERVE_ARGS -o -name '$pattern_clean'"
|
||||
fi
|
||||
done < "$src/.kewtpreserve"
|
||||
fi
|
||||
|
||||
find "$src" -name .kewtpreserve > "$KEWT_TMPDIR/kewt_preserve"
|
||||
while read -r kp; do
|
||||
d="${kp%/.kewtpreserve}"
|
||||
if [ "$d" != "$src" ] && [ "$d" != "." ]; then
|
||||
PRESERVE_ARGS="$PRESERVE_ARGS -o -path '$d' -o -path '$d/*'"
|
||||
fi
|
||||
done < "$KEWT_TMPDIR/kewt_preserve"
|
||||
rm -f "$KEWT_TMPDIR/kewt_preserve"
|
||||
|
||||
generate_nav() {
|
||||
dinfo=$(eval "find \"$1\" \( $IGNORE_ARGS -o $HIDE_ARGS -o $PRESERVE_ARGS \) -prune -o -print" | sort | AWK_SRC="$1" awk -f "$awk_dir/collect_dir_info.awk")
|
||||
find_cmd="find \"$1\" \( $IGNORE_ARGS -o $HIDE_ARGS -o $PRESERVE_ARGS \) -prune -o -name \"*.md\" -print"
|
||||
if [ -n "$posts_dir" ] && [ -d "$1/$posts_dir" ]; then
|
||||
find_cmd="$find_cmd && echo \"$1/$posts_dir/index.md\""
|
||||
fi
|
||||
eval "$find_cmd" | sort -u | AWK_SRC="$1" AWK_SINGLE_FILE_INDEX="$single_file_index" AWK_FLATTEN="$flatten" AWK_ORDER="$order" AWK_HOME_NAME="$home_name" AWK_SHOW_HOME_IN_NAV="$show_home_in_nav" AWK_DINFO="$dinfo" awk -f "$awk_dir/generate_sidebar.awk"
|
||||
}
|
||||
|
||||
title="kewt"
|
||||
style="kewt"
|
||||
@@ -165,11 +435,25 @@ footer="made with <a href=\"https://kewt.krzak.org\">kewt</a>"
|
||||
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=""
|
||||
|
||||
load_config() {
|
||||
[ -f "$1" ] || return
|
||||
@@ -186,22 +470,41 @@ load_config() {
|
||||
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=${val#\'}; val=${val%\'} ;;
|
||||
\"*\")
|
||||
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" ;;
|
||||
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" ;;
|
||||
logo) logo="${val#/}" ;;
|
||||
display_logo) display_logo="$val" ;;
|
||||
display_title) display_title="$val" ;;
|
||||
logo_as_favicon) logo_as_favicon="$val" ;;
|
||||
favicon) favicon="$val" ;;
|
||||
favicon) favicon="${val#/}" ;;
|
||||
generate_page_title) generate_page_title="$val" ;;
|
||||
error_page) error_page="${val#/}" ;;
|
||||
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#/}" ;;
|
||||
esac
|
||||
done < "$1"
|
||||
}
|
||||
@@ -209,6 +512,76 @@ load_config() {
|
||||
load_config "./site.conf"
|
||||
load_config "$src/site.conf"
|
||||
|
||||
if [ -n "$posts_dir" ]; then
|
||||
HIDE_ARGS="$HIDE_ARGS -o -path '$src/$posts_dir/*'"
|
||||
fi
|
||||
|
||||
[ "$post_mode" = "true" ] && create_new_post "$src"
|
||||
|
||||
asset_version=""
|
||||
if [ "$versioning" = "true" ]; then
|
||||
asset_version="?v=$(date +%s)"
|
||||
fi
|
||||
|
||||
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>'
|
||||
}
|
||||
|
||||
template="$src/template.html"
|
||||
[ -f "$template" ] || template="./template.html"
|
||||
[ -f "$template" ] || die "Template '$template' not found."
|
||||
@@ -217,6 +590,15 @@ template="$src/template.html"
|
||||
mkdir -p "$out"
|
||||
|
||||
nav=$(generate_nav "$src")
|
||||
extra_links=$(nav_links_html)
|
||||
if [ -n "$extra_links" ]; then
|
||||
nav="$nav
|
||||
$extra_links"
|
||||
fi
|
||||
if [ -n "$nav_extra" ]; then
|
||||
nav="$nav
|
||||
$nav_extra"
|
||||
fi
|
||||
|
||||
find_closest() {
|
||||
target="$1"
|
||||
@@ -242,13 +624,37 @@ copy_style_with_resolved_vars() {
|
||||
|
||||
render_markdown() {
|
||||
file="$1"
|
||||
is_home="$2"
|
||||
url_override="$3"
|
||||
|
||||
if [ -n "$url_override" ]; then
|
||||
current_url="$url_override"
|
||||
else
|
||||
rel_path="${file#"$src"}"
|
||||
rel_path="${rel_path#/}"
|
||||
current_url="/${rel_path%.md}.html"
|
||||
fi
|
||||
|
||||
content_file="$file"
|
||||
if [ -n "$posts_dir" ] && [ "$file" != "$src/$posts_dir/index.md" ]; then
|
||||
dir_of_file=$(dirname "$file")
|
||||
rel_dir_of_file="${dir_of_file#"$src"}"
|
||||
rel_dir_of_file="${rel_dir_of_file#/}"
|
||||
if [ "$rel_dir_of_file" = "$posts_dir" ]; then
|
||||
temp_post_with_backlink="$KEWT_TMPDIR/post_with_backlink.md"
|
||||
printf "[< Back](index.html)\n\n" > "$temp_post_with_backlink"
|
||||
cat "$file" >> "$temp_post_with_backlink"
|
||||
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/}"
|
||||
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" ;;
|
||||
@@ -290,14 +696,34 @@ render_markdown() {
|
||||
head_extra="<link rel=\"icon\" href=\"$favicon_src\" />"
|
||||
fi
|
||||
|
||||
MARKDOWN_SITE_ROOT="$src" MARKDOWN_FALLBACK_FILE="styles/$style.css" sh "$script_dir/markdown.sh" "$file" | awk -v title="$title" -v nav="$nav" -v footer="$footer" -v style_path="$style_path" -v header_brand="$header_brand" -v head_extra="$head_extra" -f "$awk_dir/render_template.awk" "$local_template"
|
||||
page_title="$title"
|
||||
if [ "$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=$(grep -m 1 '^# ' "$file" | sed 's/^# *//; s/ *$//')
|
||||
if [ -n "$first_heading" ]; then
|
||||
first_heading=$(echo "$first_heading" | sed -e 's/\[//g' -e 's/\]//g' -e 's/!//g' -e 's/\*//g' -e 's/_//g' -e 's/`//g' -e 's/([^)]*)//g' | sed 's/\\//g')
|
||||
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
|
||||
|
||||
ENABLE_HEADER_LINKS="$enable_header_links" MARKDOWN_SITE_ROOT="$src" MARKDOWN_FALLBACK_FILE="$script_dir/styles/$style.css" sh "$script_dir/markdown.sh" "$content_file" | AWK_CURRENT_URL="$current_url" AWK_TITLE="$page_title" AWK_NAV="$nav" AWK_FOOTER="$footer" AWK_STYLE_PATH="${style_path}${asset_version}" AWK_HEADER_BRAND="$header_brand" AWK_HEAD_EXTRA="$head_extra" awk -f "$awk_dir/render_template.awk" "$local_template"
|
||||
}
|
||||
|
||||
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="."
|
||||
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"
|
||||
|
||||
@@ -310,21 +736,35 @@ find "$src" -type d | sort | while read -r dir; do
|
||||
[ "$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)
|
||||
is_posts_dir="false"
|
||||
if [ -n "$posts_dir" ] && { [ "$rel_dir" = "$posts_dir" ] || [ "./$rel_dir" = "$posts_dir" ]; }; then
|
||||
is_posts_dir="true"
|
||||
fi
|
||||
if [ "$single_file_index" = "true" ] && [ "$is_posts_dir" = "false" ]; then
|
||||
md_count=$(find "$dir" ! -name "$(basename "$dir")" -prune -name "*.md" | wc -l)
|
||||
if [ "$md_count" -eq 1 ]; then
|
||||
md_file=$(find "$dir" -maxdepth 1 -name "*.md")
|
||||
render_markdown "$md_file" > "$out_dir/index.html"
|
||||
md_file=$(find "$dir" ! -name "$(basename "$dir")" -prune -name "*.md")
|
||||
is_home="false"; [ "$dir" = "$src" ] && is_home="true"
|
||||
target_url="/$rel_dir/index.html"
|
||||
[ "$rel_dir" = "." ] && target_url="/index.html"
|
||||
render_markdown "$md_file" "$is_home" "$target_url" > "$out_dir/index.html"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
temp_index="/tmp/kewt_index_$$.md"
|
||||
temp_index="$KEWT_TMPDIR/index.md"
|
||||
display_dir="${rel_dir#.}"
|
||||
[ -z "$display_dir" ] && display_dir="/"
|
||||
echo "# Index of $display_dir" > "$temp_index"
|
||||
echo "" >> "$temp_index"
|
||||
find "$dir" -maxdepth 1 -not -path '*/.*' -not -path "$dir" | sort | while read -r entry; do
|
||||
|
||||
sort_args=""
|
||||
# If this is the posts dir reverse
|
||||
if [ "$rel_dir" = "$posts_dir" ] || [ "./$rel_dir" = "$posts_dir" ]; then
|
||||
sort_args="-r"
|
||||
fi
|
||||
|
||||
find "$dir" ! -name "$(basename "$dir")" -prune ! -name ".*" -print | LC_ALL=C sort $sort_args | while read -r entry; do
|
||||
name="${entry##*/}"
|
||||
case "$name" in
|
||||
template.html|site.conf|style.css|index.md) continue ;;
|
||||
@@ -332,22 +772,53 @@ find "$src" -type d | sort | while read -r dir; do
|
||||
if [ -d "$entry" ]; then
|
||||
echo "- [${name}/](${name}/index.html)" >> "$temp_index"
|
||||
elif [ "${entry%.md}" != "$entry" ]; then
|
||||
echo "- [${name%.md}](${name%.md}.html)" >> "$temp_index"
|
||||
label="${name%.md}"
|
||||
|
||||
# Try to get first heading
|
||||
post_h=$(grep -m 1 '^# ' "$entry" | sed 's/^# *//')
|
||||
if [ -n "$post_h" ]; then
|
||||
post_h=$(echo "$post_h" | sed -e 's/\[//g' -e 's/\]//g' -e 's/!//g' -e 's/\*//g' -e 's/_//g' -e 's/`//g' -e 's/([^)]*)//g' | sed 's/\\//g')
|
||||
|
||||
if [ "$rel_dir" = "$posts_dir" ] || [ "./$rel_dir" = "$posts_dir" ]; then
|
||||
# For posts add date and time
|
||||
p_date=$(echo "${name%.md}" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/')
|
||||
p_time="00:00"
|
||||
if echo "${name%.md}" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then
|
||||
p_time=$(echo "${name%.md}" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
|
||||
fi
|
||||
label="$post_h - $p_date $p_time"
|
||||
else
|
||||
label="$post_h"
|
||||
fi
|
||||
elif [ "$rel_dir" = "$posts_dir" ] || [ "./$rel_dir" = "$posts_dir" ]; then
|
||||
# No heading and date and time for posts
|
||||
p_date=$(echo "${name%.md}" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/')
|
||||
p_time="00:00"
|
||||
if echo "${name%.md}" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then
|
||||
p_time=$(echo "${name%.md}" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
|
||||
fi
|
||||
label="$p_date $p_time"
|
||||
fi
|
||||
echo "- [$label](${name%.md}.html)" >> "$temp_index"
|
||||
else
|
||||
echo "- [$name]($name)" >> "$temp_index"
|
||||
fi
|
||||
done
|
||||
render_markdown "$temp_index" > "$out_dir/index.html"
|
||||
is_home="false"; [ "$dir" = "$src" ] && is_home="true"
|
||||
target_url="/$rel_dir/index.html"
|
||||
[ "$rel_dir" = "." ] && target_url="/index.html"
|
||||
render_markdown "$temp_index" "$is_home" "$target_url" > "$out_dir/index.html"
|
||||
rm "$temp_index"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ! -f "$out/styles.css" ] && [ -f "styles/$style.css" ]; then
|
||||
copy_style_with_resolved_vars "styles/$style.css" "$out/styles.css"
|
||||
if [ ! -f "$out/styles.css" ] && [ -f "$script_dir/styles/$style.css" ]; then
|
||||
copy_style_with_resolved_vars "$script_dir/styles/$style.css" "$out/styles.css"
|
||||
fi
|
||||
|
||||
find "$src" -type f | sort | while IFS= read -r file; do
|
||||
rel_path="${file#$src/}"
|
||||
eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type f -print" | sort | while IFS= read -r file; do
|
||||
rel_path="${file#"$src"}"
|
||||
rel_path="${rel_path#/}"
|
||||
dir_rel=$(dirname "$rel_path")
|
||||
out_dir="$out/$dir_rel"
|
||||
|
||||
@@ -355,17 +826,124 @@ find "$src" -type f | sort | while IFS= read -r file; do
|
||||
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)
|
||||
is_preserved=0
|
||||
if [ -n "$(eval "find \"$file\" \( $PRESERVE_ARGS \) -print")" ]; then
|
||||
is_preserved=1
|
||||
fi
|
||||
|
||||
is_posts_dir_2="false"
|
||||
if [ -n "$posts_dir" ] && { [ "$dir_rel" = "$posts_dir" ] || [ "./$dir_rel" = "$posts_dir" ]; }; then
|
||||
is_posts_dir_2="true"
|
||||
fi
|
||||
|
||||
if [ "$single_file_index" = "true" ] && [ "${file%.md}" != "$file" ] && [ "$is_preserved" -eq 0 ] && [ ! -f "$(dirname "$file")/index.md" ] && [ "$is_posts_dir_2" = "false" ]; then
|
||||
md_count=$(find "$(dirname "$file")" ! -name "$(basename "$(dirname "$file")")" -prune -name "*.md" | wc -l)
|
||||
[ "$md_count" -eq 1 ] && continue
|
||||
fi
|
||||
|
||||
if [ "${file%.md}" != "$file" ]; then
|
||||
if [ "${file%.md}" != "$file" ] && [ "$is_preserved" -eq 0 ]; then
|
||||
is_home="false"; [ "$file" = "$src/index.md" ] && is_home="true"
|
||||
out_file="$out/${rel_path%.md}.html"
|
||||
render_markdown "$file" > "$out_file"
|
||||
render_markdown "$file" "$is_home" > "$out_file"
|
||||
else
|
||||
cp "$file" "$out/$rel_path"
|
||||
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' >> "$sitemap_file"
|
||||
printf ' <loc>%s%s</loc>\n' "$base_url" "$rel_url" >> "$sitemap_file"
|
||||
printf ' <lastmod>%s</lastmod>\n' "$today" >> "$sitemap_file"
|
||||
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' >> "$feed_path"
|
||||
printf ' <channel>\n' >> "$feed_path"
|
||||
printf ' <title>%s</title>\n' "$title" >> "$feed_path"
|
||||
printf ' <link>%s</link>\n' "$base_url_feed" >> "$feed_path"
|
||||
printf ' <description>%s</description>\n' "$title" >> "$feed_path"
|
||||
printf ' <lastBuildDate>%s</lastBuildDate>\n' "$build_date" >> "$feed_path"
|
||||
|
||||
find "$src" -type f -name '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]*.md' -print | LC_ALL=C sort -r | while IFS= read -r post_file; do
|
||||
post_basename=$(basename "$post_file" .md)
|
||||
# Extract YYYY-MM-DD
|
||||
post_date=$(echo "$post_basename" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/')
|
||||
|
||||
# Extract HH:MM if present (e.g., 2026-03-17-10:30 or 2026-03-17-10:30_1)
|
||||
post_time="00:00"
|
||||
if echo "$post_basename" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then
|
||||
post_time=$(echo "$post_basename" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
|
||||
fi
|
||||
|
||||
post_slug=$(echo "$post_basename" | sed -e 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}//' -e 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}//' -e 's/^[_\-]//')
|
||||
|
||||
post_heading=$(grep -m 1 '^# ' "$post_file" | sed 's/^# *//')
|
||||
if [ -z "$post_heading" ]; then
|
||||
if [ -n "$post_slug" ] && ! echo "$post_slug" | grep -q '^[0-9]\+$'; then
|
||||
post_heading=$(echo "$post_slug" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)}1')
|
||||
else
|
||||
post_heading="Post"
|
||||
fi
|
||||
fi
|
||||
post_heading=$(echo "$post_heading" | sed -e 's/\[//g' -e 's/\]//g' -e 's/!//g' -e 's/\*//g' -e 's/_//g' -e 's/`//g' -e 's/([^)]*)//g' | sed 's/\\//g')
|
||||
post_title="$post_heading - $post_date $post_time"
|
||||
|
||||
rel_path="${post_file#"$src"}"
|
||||
rel_path="${rel_path#/}"
|
||||
post_url="$base_url_feed/${rel_path%.md}.html"
|
||||
|
||||
pub_year=$(echo "$post_date" | cut -d- -f1)
|
||||
pub_month=$(echo "$post_date" | cut -d- -f2)
|
||||
pub_day=$(echo "$post_date" | cut -d- -f3)
|
||||
case "$pub_month" in
|
||||
01) pub_mon="Jan" ;; 02) pub_mon="Feb" ;; 03) pub_mon="Mar" ;;
|
||||
04) pub_mon="Apr" ;; 05) pub_mon="May" ;; 06) pub_mon="Jun" ;;
|
||||
07) pub_mon="Jul" ;; 08) pub_mon="Aug" ;; 09) pub_mon="Sep" ;;
|
||||
10) pub_mon="Oct" ;; 11) pub_mon="Nov" ;; 12) pub_mon="Dec" ;;
|
||||
esac
|
||||
pub_date="${pub_day} ${pub_mon} ${pub_year} ${post_time}:00 +0000"
|
||||
|
||||
printf ' <item>\n' >> "$feed_path"
|
||||
printf ' <title>%s</title>\n' "$post_title" >> "$feed_path"
|
||||
printf ' <link>%s</link>\n' "$post_url" >> "$feed_path"
|
||||
printf ' <guid>%s</guid>\n' "$post_url" >> "$feed_path"
|
||||
printf ' <pubDate>%s</pubDate>\n' "$pub_date" >> "$feed_path"
|
||||
printf ' </item>\n' >> "$feed_path"
|
||||
done
|
||||
|
||||
printf ' </channel>\n' >> "$feed_path"
|
||||
printf '</rss>\n' >> "$feed_path"
|
||||
fi
|
||||
|
||||
echo "Build complete."
|
||||
|
||||
770
markdown.sh
770
markdown.sh
@@ -1,754 +1,64 @@
|
||||
#!/bin/sh
|
||||
|
||||
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
script_dir=$(CDPATH="" cd -- "$(dirname -- "$0")" && pwd)
|
||||
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() {
|
||||
script="$1"
|
||||
file="$2"
|
||||
tmp="${file}.tmp.$$"
|
||||
sed "$script" "$file" > "$tmp" && mv "$tmp" "$file" || {
|
||||
if sed "$script" "$file" > "$tmp" && mv "$tmp" "$file"; then
|
||||
return 0
|
||||
else
|
||||
rm -f "$tmp"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
}
|
||||
|
||||
temp_file="/tmp/markdown.$$"
|
||||
temp_file="/tmp/markdown.$$.md"
|
||||
cat "$@" > "$temp_file"
|
||||
|
||||
# backslash escapes for literal characters and inline code masking
|
||||
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")
|
||||
trap 'rm -f "$temp_file" "$temp_file.tmp"' EXIT INT TERM
|
||||
|
||||
# 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
|
||||
if (plen >= 3) {
|
||||
out = out substr(line, p, pstart - p + plen)
|
||||
p = pstart + plen
|
||||
continue
|
||||
}
|
||||
|
||||
# Found 1 or 2 backticks at pstart
|
||||
# Search for closing marker
|
||||
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)
|
||||
}
|
||||
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"
|
||||
# Mask
|
||||
awk -f "$awk_dir/mask_inline_code.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||
awk -f "$awk_dir/mask_plain.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||
|
||||
# Reference links
|
||||
refs=$(cat "$@" | awk '/^\[[^\]]+\]: */')
|
||||
IFS='
|
||||
'
|
||||
refs=$(sed_ere -n "/^\[.+\]: +/p" "$@")
|
||||
for ref in $refs
|
||||
do
|
||||
ref_id=$(printf %s "$ref" | sed_ere -n "s/^\[(.+)\]: .*/\1/p" | tr -d '\n')
|
||||
ref_url=$(printf %s "$ref" | sed_ere -n "s/^\[.+\]: (.+)/\1/p" | cut -d' ' -f1 | tr -d '\n')
|
||||
ref_title=$(printf %s "$ref" | sed_ere -n "s/^\[.+\]: (.+) \"(.+)\"/\2/p" | sed 's@|@!@g' | tr -d '\n')
|
||||
for ref in $refs; do
|
||||
ref_id=$(echo "$ref" | sed 's/^\[\(.*\)\]: .*/\1/')
|
||||
ref_url=$(echo "$ref" | sed 's/^\[.*\]: \([^ ]*\).*/\1/')
|
||||
ref_title=$(echo "$ref" | sed -n 's/^\[.*\]: [^ ]* "\(.*\)"/\1/p' | sed 's@|@!@g')
|
||||
sed_inplace "s|!\[\([^]]*\)\]\[$ref_id\]|<img src=\"$ref_url\" title=\"$ref_title\" alt=\"\1\" />|g" "$temp_file"
|
||||
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"
|
||||
sed_inplace "s|\[$ref_id\]\[\]|<a href=\"$ref_url\" title=\"$ref_title\">$ref_id</a>|g" "$temp_file"
|
||||
done
|
||||
sed_inplace "/^\[[^\]]*\]: */d" "$temp_file"
|
||||
|
||||
# reference-style image using the label
|
||||
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"
|
||||
# Blocks
|
||||
sed_inplace "s/^>!\[/> [!/g" "$temp_file"
|
||||
sed_inplace "s/^>\[!/> [!/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"
|
||||
while grep '^>' "$temp_file" >/dev/null; do
|
||||
awk -f "$awk_dir/blockquote.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||
done
|
||||
|
||||
# delete the reference lines
|
||||
sed_ere_inplace "/^\[.+\]: +/d" "$temp_file"
|
||||
|
||||
# normalize GitHub admonition shorthand in blockquotes
|
||||
sed_ere_inplace '
|
||||
/^>!\[/s/^>!\[/> [!/
|
||||
/^>\[!/s/^>\[!/> [!/
|
||||
s/^>([^[:space:]>])/> \1/
|
||||
' "$temp_file"
|
||||
|
||||
# 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
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
# ordered lists
|
||||
# use grep to find all the nested lists
|
||||
while grep -E '^[1-9]+\. ' "$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 oli instead of li to avoid collisions when processing nested lists
|
||||
/^[1-9]+\. /s/[1-9]+\. (.*)/<\/oli>\n<oli>\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
|
||||
|
||||
:list
|
||||
# exchange the hold space into the pattern space
|
||||
x
|
||||
# look for the list items, if there wrap the ol tags
|
||||
/<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 -f "$awk_dir/blockquote_to_admonition.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||
awk -f "$awk_dir/fenced_code.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||
awk -f "$awk_dir/indented_code.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||
awk -f "$awk_dir/pipe_tables.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||
awk -v enable_header_links="$ENABLE_HEADER_LINKS" -f "$awk_dir/headers.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||
awk -f "$awk_dir/lists.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||
|
||||
# Spacing
|
||||
awk -f "$awk_dir/breaks.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||
awk -f "$awk_dir/paragraphs.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||
|
||||
# Inline styles
|
||||
awk -f "$awk_dir/markdown_inline.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
|
||||
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"
|
||||
|
||||
16
packaging/AUR/.SRCINFO.git
Normal file
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 = 2
|
||||
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
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 = 1
|
||||
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
|
||||
29
packaging/AUR/PKGBUILD.git
Normal file
29
packaging/AUR/PKGBUILD.git
Normal file
@@ -0,0 +1,29 @@
|
||||
# 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+${url}.git")
|
||||
sha256sums=('SKIP')
|
||||
|
||||
pkgver() {
|
||||
cd "${pkgname}"
|
||||
printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
|
||||
}
|
||||
|
||||
build() {
|
||||
cd "${pkgname}"
|
||||
sh tools/build-standalone.sh
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "${pkgname}"
|
||||
install -Dm755 kewt "${pkgdir}/usr/bin/kewt"
|
||||
}
|
||||
21
packaging/AUR/PKGBUILD.template
Normal file
21
packaging/AUR/PKGBUILD.template
Normal file
@@ -0,0 +1,21 @@
|
||||
# Maintainer: n0va <n0va@krzak.org>
|
||||
pkgname=kewt-bin
|
||||
pkgver=VERSION_PLACEHOLDER
|
||||
pkgrel=1
|
||||
pkgdesc="A minimalist, 100% POSIX, static site generator inspired by werc and kew"
|
||||
arch=('any')
|
||||
url="https://kewt.krzak.org"
|
||||
license=('ISC')
|
||||
depends=('sh')
|
||||
provides=('kewt')
|
||||
conflicts=('kewt' 'kewt-git')
|
||||
source=("${pkgname}-${pkgver}.sh::${url}/releases/download/v${pkgver}/kewt")
|
||||
sha256sums=('SHA256SUM_PLACEHOLDER')
|
||||
|
||||
build() {
|
||||
chmod +x "${srcdir}/${pkgname}-${pkgver}.sh"
|
||||
}
|
||||
|
||||
package() {
|
||||
install -Dm755 "${srcdir}/${pkgname}-${pkgver}.sh" "${pkgdir}/usr/bin/kewt"
|
||||
}
|
||||
11
site.conf
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 = ""
|
||||
BIN
site/button.gif
Normal file
BIN
site/button.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
@@ -15,3 +15,5 @@ Sed dictum tortor at interdum dignissim. Nunc hendrerit sollicitudin elementum.
|
||||
Cras vitae sapien egestas, blandit libero et, volutpat augue. Ut augue quam, sollicitudin quis libero laoreet, bibendum imperdiet massa. Duis sed venenatis risus. Praesent a est mollis, viverra erat quis, faucibus elit. Donec at sagittis est, non posuere nisi. Integer posuere pharetra dui in aliquam. Morbi vehicula eros in hendrerit aliquam. Duis in turpis vel mauris mattis convallis in id tortor. Cras et aliquam augue.
|
||||
|
||||
Cras quis consectetur dolor, a sodales tortor. Vestibulum aliquam lacinia metus, sed viverra erat egestas in. Morbi interdum sapien sed bibendum maximus. Aenean accumsan pharetra libero dapibus aliquam. Etiam sodales purus posuere gravida ullamcorper. Vestibulum tincidunt, nibh a pulvinar aliquet, leo tortor pulvinar diam, ut viverra nunc elit bibendum nulla. Praesent vel pulvinar erat, eu efficitur magna. Mauris at consequat purus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce in dui quis nisi elementum aliquam. Proin eget justo sed est commodo accumsan. Suspendisse a feugiat tellus, eget gravida tellus.
|
||||
|
||||
![https://www.youtube.com/embed/NvQD9E5Cq8A]
|
||||
|
||||
BIN
site/favicon.ico
Normal file
BIN
site/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 361 KiB |
@@ -1,8 +1,12 @@
|
||||
# _kewt_
|
||||
### Pronounced "cute"
|
||||
|
||||
***
|
||||
|
||||
# [Go to the repo](https://git.krzak.org/N0VA/kewt)
|
||||
|
||||
***
|
||||
|
||||
_kewt_ is a minimalist ssg inspired by _[werc](http://werc.cat-v.org/)_ and _[kew](https://github.com/uint23/kew)_
|
||||
|
||||
It's meant to be a static site generator, like _[kew](https://github.com/uint23/kew)_ but use only default (POSIX) tooling, like _[werc](http://werc.cat-v.org/)_ (and definitely unlike _[kew](https://github.com/uint23/kew)_)
|
||||
@@ -16,20 +20,45 @@ It's meant to be a static site generator, like _[kew](https://github.com/uint23/
|
||||
- Inline html support
|
||||
- MFM `$font` and `\<plain>` tags
|
||||
- Admonition support (that's what the blocks like the warning block below are called)
|
||||
- RSS/Feed generation and Sitemap support
|
||||
- Post creation via `--post`
|
||||
- Automatic 404 page generation
|
||||
- `?v=n` support for cache busting
|
||||
- Code block classes for use with external libraries like highlight.js or prism.js (both tested)
|
||||
- Clickable markdown header anchors
|
||||
- Mobile responsive layout
|
||||
|
||||
If you want to **force** a file to be inlined, use `\!![]` instead of `\![]`
|
||||
|
||||
## Installation
|
||||
|
||||
You can clone the repository to use `kewt.sh` directly, or you can download the standalone executable, which bundles all dependencies into a single file:
|
||||
|
||||
```sh
|
||||
curl -L -o kewt https://git.krzak.org/N0VA/kewt/releases/download/latest/kewt
|
||||
chmod +x kewt
|
||||
```
|
||||
|
||||
On Arch Linux, _kewt_ is available on the AUR:
|
||||
|
||||
- [kewt-bin](https://aur.archlinux.org/packages/kewt-bin) — prebuilt standalone binary from the latest release
|
||||
- [kewt-git](https://aur.archlinux.org/packages/kewt-git) — built from the latest git source
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
./kewt.sh --help
|
||||
./kewt.sh --version
|
||||
./kewt.sh --new [title]
|
||||
./kewt.sh --post
|
||||
./kewt.sh --from <src> --to <out>
|
||||
./kewt.sh [src] [out]
|
||||
```
|
||||
|
||||
`--new [title]` creates a new site directory with a copied `site.conf` and a default `index.md`.
|
||||
|
||||
`--post` creates a new empty markdown file in the configured `posts_dir` with the current date and time as the name.
|
||||
|
||||
## site.conf
|
||||
|
||||
```conf
|
||||
@@ -38,12 +67,25 @@ style = "kewt"
|
||||
dir_indexes = true
|
||||
single_file_index = true
|
||||
flatten = false
|
||||
footer = "made with <a href="https://kewt.krzak.org">kewt</a>"
|
||||
order = ""
|
||||
home_name = "Home"
|
||||
show_home_in_nav = true
|
||||
nav_links = ""
|
||||
nav_extra = ""
|
||||
footer = "made with <a href=\"https://kewt.krzak.org\">kewt</a>"
|
||||
logo = ""
|
||||
display_logo = false
|
||||
display_title = true
|
||||
logo_as_favicon = true
|
||||
favicon = ""
|
||||
generate_page_title = true
|
||||
error_page = "not_found.html"
|
||||
versioning = false
|
||||
base_url = ""
|
||||
generate_feed = false
|
||||
feed_file = "rss.xml"
|
||||
posts_dir = ""
|
||||
enable_header_links = true
|
||||
```
|
||||
|
||||
- `title` site title
|
||||
@@ -51,12 +93,31 @@ favicon = ""
|
||||
- `dir_indexes` generate directory index pages when missing `index.md`
|
||||
- `single_file_index` if a directory has one markdown file and no `index.md`, use that file as `index.html`
|
||||
- `flatten` flatten sidebar directory levels
|
||||
- `order` comma separated file/directory name list to order the sidebar (alphabetical by default)
|
||||
- `home_name` text for the home link in navigation (default: "Home")
|
||||
- `show_home_in_nav` show home link in navigation (default: true)
|
||||
- `nav_links` comma separated extra nav links, as bare URLs or Markdown links like `[Label](https://example.com)`
|
||||
- `nav_extra` raw HTML appended inside the `<nav>` after the generated link list
|
||||
- `footer` footer html/text shown at the bottom of pages
|
||||
- `logo` logo image path (used in header if enabled)
|
||||
- `display_logo` show logo in header
|
||||
- `display_title` show title text in header
|
||||
- `logo_as_favicon` use `logo` as favicon
|
||||
- `favicon` explicit favicon path (used when `logo_as_favicon` is false or no logo is set)
|
||||
- `generate_page_title` automatically generate title text from the first markdown heading or filename (default: true)
|
||||
- `error_page` filename for the generated 404 error page (default: "not_found.html", empty to disable)
|
||||
- `versioning` append a version query parameter (`?v=timestamp`) to css asset urls to bypass cache (default: false)
|
||||
- `base_url` absolute URL of the site, used for sitemap and RSS feed generation
|
||||
- `generate_feed` enable RSS feed generation (requires `base_url`)
|
||||
- `feed_file` filename for the generated RSS feed (default: "rss.xml")
|
||||
- `posts_dir` directory name containing posts (e.g., "posts"). Enables reverse-chronological sorting, title headings in indexes, and automatic backlinks.
|
||||
- `enable_header_links` turns markdown section headings into clickable anchor links (default: true)
|
||||
|
||||
## Ignores
|
||||
|
||||
- `.kewtignore`: Files/directories to ignore. If empty, the whole directory gets ignored
|
||||
- `.kewthide`: Files/directories to hide from navigation but still process. Same empty rules as with ignore
|
||||
- `.kewtpreserve`: Files/directories to copy but not convert markdown to html. Same empty rules again
|
||||
|
||||
## Embeds
|
||||
|
||||
@@ -70,9 +131,7 @@ favicon = ""
|
||||
|
||||
## 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)
|
||||
- _kew_ css style adapted from _[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...
|
||||
>[!WARNING]
|
||||
>The base that all of this is built upon was coded at night, while sleepy and a bit sick, and after walking for about 4 hours around a forest, so...
|
||||
|
||||
21
site/site.conf
Normal file
21
site/site.conf
Normal file
@@ -0,0 +1,21 @@
|
||||
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_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"
|
||||
127
styles/kewt.css
127
styles/kewt.css
@@ -1,25 +1,28 @@
|
||||
:root {
|
||||
--bg: #646c7f;
|
||||
--fg: #fffde0;
|
||||
--fg-link: #fff18f;
|
||||
--code-bg: #32394a;
|
||||
--code-border: #8f95a4;
|
||||
--code-fg: #fffde0;
|
||||
--code-sel: #fff18f;
|
||||
--code-prop: #ffd27f;
|
||||
--code-val: #cde7ff;
|
||||
--code-var: #b9ffbe;
|
||||
--code-com: #d0d0d0;
|
||||
--adm-note-bg: #3f5666;
|
||||
--adm-note-border: #a8d8ff;
|
||||
--adm-tip-bg: #3f664c;
|
||||
--adm-tip-border: #b9ffbe;
|
||||
--adm-important-bg: #5a4a6c;
|
||||
--adm-important-border: #e4c7ff;
|
||||
--adm-warning-bg: #6b5539;
|
||||
--adm-warning-border: #ffe0a8;
|
||||
--adm-caution-bg: #6f3f3f;
|
||||
--adm-caution-border: #ffb4b4;
|
||||
--bg: #4a3b69;
|
||||
--bg-deep: #352654;
|
||||
--fg: #fbf5ff;
|
||||
--fg-muted: #c8b9df;
|
||||
--fg-link: #dfaeff;
|
||||
--fg-heading: #debfff;
|
||||
--code-bg: #31234c;
|
||||
--code-border: #8060af;
|
||||
--code-fg: #fbf5ff;
|
||||
--code-sel: #ffef99;
|
||||
--code-prop: #ffdfba;
|
||||
--code-val: #cae2ff;
|
||||
--code-var: #caffc2;
|
||||
--code-com: #b8aac8;
|
||||
--adm-note-bg: #353866;
|
||||
--adm-note-border: #b8c5ff;
|
||||
--adm-tip-bg: #295246;
|
||||
--adm-tip-border: #aeffda;
|
||||
--adm-important-bg: #533076;
|
||||
--adm-important-border: #f4d9ff;
|
||||
--adm-warning-bg: #634631;
|
||||
--adm-warning-border: #ffe2bd;
|
||||
--adm-caution-bg: #662d43;
|
||||
--adm-caution-border: #ffc4d5;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -29,18 +32,31 @@ body {
|
||||
color: var(--fg);
|
||||
font-family: serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.2;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 20px;
|
||||
padding-bottom: 0;
|
||||
border-bottom: 1px solid var(--code-border);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
margin: 0;
|
||||
font-size: 35px;
|
||||
font-weight: normal;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
color: var(--fg-heading);
|
||||
}
|
||||
|
||||
.site-logo {
|
||||
vertical-align: middle;
|
||||
height: 1.1em;
|
||||
width: auto;
|
||||
border: none;
|
||||
margin-right: 0.2em;
|
||||
margin-top: -0.1em;
|
||||
}
|
||||
|
||||
header a {
|
||||
@@ -48,18 +64,26 @@ header a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
header a:hover {
|
||||
color: var(--bg-deep);
|
||||
background: var(--fg);
|
||||
}
|
||||
|
||||
#side-bar {
|
||||
position: absolute;
|
||||
top: 80px;
|
||||
left: 0;
|
||||
width: 200px;
|
||||
padding-left: 20px;
|
||||
margin-right: 14px;
|
||||
border-right: 1px solid var(--code-border);
|
||||
padding-right: 7px;
|
||||
}
|
||||
|
||||
.side-title {
|
||||
font-size: 25px;
|
||||
margin: 20px 0 8px 0;
|
||||
color: var(--fg);
|
||||
color: var(--fg-heading);
|
||||
}
|
||||
|
||||
#side-bar ul {
|
||||
@@ -78,6 +102,14 @@ a {
|
||||
padding: 1px 2px;
|
||||
}
|
||||
|
||||
#side-bar a.current-page {
|
||||
font-weight: bold;
|
||||
color: var(--fg);
|
||||
border-left: 3px solid var(--fg-link);
|
||||
padding-left: 7px;
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background: var(--fg);
|
||||
color: var(--bg);
|
||||
@@ -91,7 +123,7 @@ article {
|
||||
h3 {
|
||||
margin-top: 30px;
|
||||
font-size: 25px;
|
||||
color: var(--fg);
|
||||
color: var(--fg-heading);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
@@ -188,10 +220,11 @@ pre code {
|
||||
}
|
||||
|
||||
footer {
|
||||
padding-top: 80px;
|
||||
padding-top: 60px;
|
||||
font-style: italic;
|
||||
font-size: 17px;
|
||||
margin-bottom: 20px;
|
||||
color: var(--fg-muted);
|
||||
}
|
||||
|
||||
article,
|
||||
@@ -199,3 +232,45 @@ footer {
|
||||
margin-left: 240px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
img {
|
||||
image-rendering: auto;
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
}
|
||||
|
||||
footer img {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 0;
|
||||
margin: 24px 0;
|
||||
border: 0;
|
||||
border-top: 1px solid var(--code-border);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
#side-bar {
|
||||
position: relative;
|
||||
top: auto;
|
||||
left: auto;
|
||||
width: auto;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--code-border);
|
||||
padding: 0 0 20px 0;
|
||||
margin: 0 20px 20px 20px;
|
||||
}
|
||||
|
||||
article {
|
||||
margin: 0 20px 0 20px;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
padding-top: 30px;
|
||||
}
|
||||
}
|
||||
@@ -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
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 --abbrev=0 2>/dev/null || echo "standalone")
|
||||
tmpbuild=$(mktemp -d)
|
||||
cp -r "$REPO_ROOT/kewt.sh" "$REPO_ROOT/markdown.sh" "$REPO_ROOT/awk" "$REPO_ROOT/styles" "$tmpbuild/"
|
||||
sed -e "s/kewt version git/kewt version $VERSION/" "$tmpbuild/kewt.sh" > "$tmpbuild/kewt.sh.tmp" && mv "$tmpbuild/kewt.sh.tmp" "$tmpbuild/kewt.sh"
|
||||
chmod +x "$tmpbuild/kewt.sh" "$tmpbuild/markdown.sh"
|
||||
tar -cz -C "$tmpbuild" kewt.sh markdown.sh awk styles >> "$OUT_FILE"
|
||||
rm -rf "$tmpbuild"
|
||||
|
||||
chmod +x "$OUT_FILE"
|
||||
|
||||
echo "Generated standalone executable at $OUT_FILE"
|
||||
Reference in New Issue
Block a user