simpleblog

Generador estatico minimalista de blog
git clone https://git.fedi.ovh/simpleblog
Log | Files | Refs

commit 5eb390fee04012cadb2331b4521ccc04ed576dca
parent 2a432784ce5d375187df177d50f6d0b97c05c634
Author: Juan <juan@fedi.ovh>
Date:   Mon, 18 May 2026 13:59:35 +0200

refactor: empaquetable (script global + sitio en cwd)

cambios mayores para poder distribuir simpleblog como paquete .apk:

- el script trabaja sobre cwd (SITE_DIR), no sobre el dir donde reside
- plantillas con override: ./templates/<name> -> $DEFAULTS/templates/<name>
  (DEFAULTS = /usr/share/simpleblog por defecto, configurable via env)
- nuevo comando `simpleblog init`: copia skel/ al directorio actual
- nuevo comando `simpleblog serve`: detecta darkhttpd/busybox/python
- sitio de ejemplo movido a example/
- skel/ con esqueleto minimo (config, post hola-mundo, css, favicon)
- alpine/APKBUILD para empaquetar el .apk (depende de smu)
- Makefile: target dist (tarball para abuild), install respeta DESTDIR/PREFIX

smu deja de ser vendored: se asume instalable como paquete aparte.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Diffstat:
M.gitignore | 9+++++----
MMakefile | 70++++++++++++++++++++++++++++++----------------------------------------
Aalpine/APKBUILD | 26++++++++++++++++++++++++++
Dconfig | 29-----------------------------
Aexample/config | 29+++++++++++++++++++++++++++++
Rimages/ejemplo.jpg -> example/images/ejemplo.jpg | 0
Aexample/pages/about.md | 11+++++++++++
Aexample/pages/contacto.md | 7+++++++
Rposts/2026-05-18-hola-mundo.md -> example/posts/2026-05-18-hola-mundo.md | 0
Rposts/2026-05-18-pagina-en-html.html -> example/posts/2026-05-18-pagina-en-html.html | 0
Rposts/2026-05-19-borrador.md -> example/posts/2026-05-19-borrador.md | 0
Rposts/2026-05-20-con-slug.md -> example/posts/2026-05-20-con-slug.md | 0
Rposts/2026-05-21-imagenes.md -> example/posts/2026-05-21-imagenes.md | 0
Rstatic/favicon.svg -> example/static/favicon.svg | 0
Rstatic/logo.svg -> example/static/logo.svg | 0
Rstatic/style.css -> example/static/style.css | 0
Mobjetivo.txt | 77++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Dpages/about.md | 12------------
Dpages/contacto.md | 8--------
Msimpleblog | 103++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Askel/.gitignore | 1+
Askel/config | 24++++++++++++++++++++++++
Askel/images/.keep | 0
Askel/pages/about.md | 10++++++++++
Askel/posts/hola-mundo.md | 27+++++++++++++++++++++++++++
Rstatic/favicon.svg -> skel/static/favicon.svg | 0
Rstatic/logo.svg -> skel/static/logo.svg | 0
Askel/static/style.css | 44++++++++++++++++++++++++++++++++++++++++++++
28 files changed, 339 insertions(+), 148 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,10 +1,11 @@ # salida generada public/ +example/public/ -# dependencias vendored (se clonan + compilan con `make`) -vendor/ - -# basura comun +# build artifacts *.o *.swp .DS_Store + +# vendored opcional (compatibilidad con versiones antiguas) +vendor/ diff --git a/Makefile b/Makefile @@ -1,56 +1,46 @@ .POSIX: -PREFIX = /usr/local -BIN = simpleblog -OUT = public -PORT = 8080 +PKGNAME = simpleblog +VERSION = 0.1 +PREFIX = /usr/local +BIN = simpleblog +SHAREDIR = $(PREFIX)/share/simpleblog +EXAMPLE = example +PORT = 8080 +DIST = $(PKGNAME)-$(VERSION).tar.gz all: build -build: vendor/smu/smu - ./simpleblog build +# build local del sitio de ejemplo, usando este repo como defaults +# (templates/ y skel/ del repo, sin necesidad de instalar el paquete) +build: + cd $(EXAMPLE) && SIMPLEBLOG_DEFAULTS=$$PWD/.. ../simpleblog build clean: - ./simpleblog clean + cd $(EXAMPLE) && SIMPLEBLOG_DEFAULTS=$$PWD/.. ../simpleblog clean stats: - ./simpleblog stats - -vendor/smu/smu: vendor/smu - cd vendor/smu && $(MAKE) - -vendor/smu: - mkdir -p vendor - git clone --depth=1 https://github.com/Gottox/smu.git vendor/smu - -# httpd minimo: probamos en orden darkhttpd, httpd (busybox-extras o standalone), -# busybox httpd como applet, y python como fallback universal. -serve: build - @echo "serving $(OUT)/ on http://localhost:$(PORT)"; \ - if command -v darkhttpd >/dev/null 2>&1; then \ - darkhttpd $(OUT) --port $(PORT); \ - elif command -v httpd >/dev/null 2>&1; then \ - httpd -f -p $(PORT) -h $(OUT); \ - elif command -v busybox-extras >/dev/null 2>&1; then \ - busybox-extras httpd -f -p $(PORT) -h $(OUT); \ - elif command -v busybox >/dev/null 2>&1 && busybox --list 2>/dev/null | grep -qx httpd; then \ - busybox httpd -f -p $(PORT) -h $(OUT); \ - elif command -v python3 >/dev/null 2>&1; then \ - cd $(OUT) && python3 -m http.server $(PORT); \ - else \ - echo "no httpd encontrado. instala darkhttpd, busybox-extras o python3" >&2; \ - exit 1; \ - fi - -install: build + cd $(EXAMPLE) && SIMPLEBLOG_DEFAULTS=$$PWD/.. ../simpleblog stats + +serve: + cd $(EXAMPLE) && SIMPLEBLOG_PORT=$(PORT) SIMPLEBLOG_DEFAULTS=$$PWD/.. ../simpleblog serve + +# instala el script y los defaults globalmente +install: mkdir -p $(DESTDIR)$(PREFIX)/bin + mkdir -p $(DESTDIR)$(SHAREDIR) cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin/$(BIN) chmod 755 $(DESTDIR)$(PREFIX)/bin/$(BIN) + cp -r templates $(DESTDIR)$(SHAREDIR)/ + cp -r skel $(DESTDIR)$(SHAREDIR)/ uninstall: - rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN) + rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN) + rm -rf $(DESTDIR)$(SHAREDIR) -distclean: clean - cd vendor/smu && $(MAKE) clean +# tarball para empaquetar (lo consume el APKBUILD) +dist: + git archive --format=tar.gz --prefix=$(PKGNAME)-$(VERSION)/ HEAD -o $(DIST) + @echo "creado $(DIST)" -.PHONY: all build clean serve stats install uninstall distclean +.PHONY: all build clean stats serve install uninstall dist diff --git a/alpine/APKBUILD b/alpine/APKBUILD @@ -0,0 +1,26 @@ +# Maintainer: Juan <juan@fedi.ovh> +pkgname=simpleblog +pkgver=0.1 +pkgrel=0 +pkgdesc="Minimalist static site generator (sh + smu)" +url="https://git.fedi.ovh/simpleblog/" +arch="noarch" +license="MIT" +depends="smu" +# darkhttpd/imagemagick son opcionales (serve / process_images) +makedepends="" +subpackages="$pkgname-doc" +source="$pkgname-$pkgver.tar.gz" +options="!check" + +package() { + cd "$builddir" + make install \ + PREFIX=/usr \ + DESTDIR="$pkgdir" + + install -Dm644 objetivo.txt \ + "$pkgdir/usr/share/doc/$pkgname/objetivo.txt" +} + +sha512sums="" diff --git a/config b/config @@ -1,29 +0,0 @@ -# simpleblog config — key: value, lineas con # son comentarios -title: simpleblog -description: Un blog minimalista -author: Juan -# baseurl: path para enlaces relativos (acaba en /) -baseurl: / -# siteurl: URL absoluta, sin / final. Usado para el feed atom. -siteurl: https://example.com -lang: es -# nombre del feed (vacio para no generarlo) -feed: atom.xml - -# paginacion del index. 0 = sin paginar (todos los posts en index.html) -posts_per_page: 2 - -# anchos (px) a generar para cada imagen en images/. coma-separados. -# vacio = no procesar imagenes (solo copiar al output). -# en posts referencias /img/<nombre>-<ancho>.<ext> -image_sizes: 320, 640, 1280 -# calidad jpeg/webp (1-100). 85 = compromiso bueno. -image_quality: 85 -# formatos extra a generar ademas del original (coma-separados, sin punto). -# webp pesa ~30% menos que jpeg. avif aun menos pero mas lento de generar. -# vacio = no generar extras. -image_extra_formats: webp - -# comando para convertir markdown -> html (lee stdin, escribe stdout) -# rutas que empiezan por ./ se resuelven respecto al directorio del proyecto -markdown_cmd: ./vendor/smu/smu diff --git a/example/config b/example/config @@ -0,0 +1,29 @@ +# simpleblog config — key: value, lineas con # son comentarios +title: simpleblog +description: Un blog minimalista +author: Juan +# baseurl: path para enlaces relativos (acaba en /) +baseurl: / +# siteurl: URL absoluta, sin / final. Usado para el feed atom. +siteurl: https://example.com +lang: es +# nombre del feed (vacio para no generarlo) +feed: atom.xml + +# paginacion del index. 0 = sin paginar (todos los posts en index.html) +posts_per_page: 2 + +# anchos (px) a generar para cada imagen en images/. coma-separados. +# vacio = no procesar imagenes (solo copiar al output). +# en posts referencias /img/<nombre>-<ancho>.<ext> +image_sizes: 320, 640, 1280 +# calidad jpeg/webp (1-100). 85 = compromiso bueno. +image_quality: 85 +# formatos extra a generar ademas del original (coma-separados, sin punto). +# webp pesa ~30% menos que jpeg. avif aun menos pero mas lento de generar. +# vacio = no generar extras. +image_extra_formats: webp + +# comando para convertir markdown -> html (lee stdin, escribe stdout) +# rutas que empiezan por ./ se resuelven respecto al directorio del sitio +markdown_cmd: smu diff --git a/images/ejemplo.jpg b/example/images/ejemplo.jpg Binary files differ. diff --git a/example/pages/about.md b/example/pages/about.md @@ -0,0 +1,11 @@ +title: Sobre el sitio +description: Quien soy y de que va este blog. +menu: true +menu_order: 10 + + +Esta es una pagina estatica fuera de `posts/`. No aparece en el listado +del blog ni en el feed, pero se enlaza desde el menu de la cabecera. + +Si el frontmatter tiene `menu: true`, la pagina aparece en el menu del +sitio. El orden viene del campo `menu_order` (numerico). diff --git a/example/pages/contacto.md b/example/pages/contacto.md @@ -0,0 +1,7 @@ +title: Contacto +description: Como ponerse en contacto. +menu: true +menu_order: 20 + + +Aqui iria un correo, una direccion de mastodon, lo que sea. diff --git a/posts/2026-05-18-hola-mundo.md b/example/posts/2026-05-18-hola-mundo.md diff --git a/posts/2026-05-18-pagina-en-html.html b/example/posts/2026-05-18-pagina-en-html.html diff --git a/posts/2026-05-19-borrador.md b/example/posts/2026-05-19-borrador.md diff --git a/posts/2026-05-20-con-slug.md b/example/posts/2026-05-20-con-slug.md diff --git a/posts/2026-05-21-imagenes.md b/example/posts/2026-05-21-imagenes.md diff --git a/static/favicon.svg b/example/static/favicon.svg diff --git a/static/logo.svg b/example/static/logo.svg diff --git a/static/style.css b/example/static/style.css diff --git a/objetivo.txt b/objetivo.txt @@ -17,9 +17,10 @@ DECISIONES (2026-05-18) - Lenguaje: shell script POSIX (sh + awk + sed). Razon: minimo absoluto, sin compilar, facil de iterar. Si se queda corto, migramos a C reusando saait. -- Conversor markdown: smu (suckless), clonado y compilado en vendor/smu/. - El comando se configura en `config` (variable `markdown_cmd`); rutas que - empiezan por `./` se resuelven respecto al directorio del proyecto. +- Conversor markdown: smu (suckless), instalado como paquete aparte + (apk add smu). El comando se configura en `config` (variable + `markdown_cmd`); rutas que empiezan por `./` se resuelven respecto al + directorio del sitio. Nota: smu solo entiende markdown clasico de Gruber, NO triple backtick. Bloques de codigo = 4 espacios de indentacion. - Metadatos: frontmatter dentro del propio .md, formato `key: value`, separado @@ -29,38 +30,47 @@ DECISIONES (2026-05-18) - Estilo: estructura semantica + css minimal. -ESTRUCTURA ----------- -simpleblog/ - simpleblog script principal (sh) - Makefile build / clean / serve / install +ESTRUCTURA (tras refactor 2026-05-18, repo empaquetable) +-------------------------------------------------------- +repo/ + simpleblog script principal (sh) -> /usr/bin/simpleblog + Makefile build / install / dist + templates/ plantillas DEFAULT -> /usr/share/simpleblog/templates/ + header.html, footer.html, post.html, page.html, + index.html, index-item.html, tags.html, + atom-{header,item,footer}.xml + skel/ esqueleto para `simpleblog init` + -> /usr/share/simpleblog/skel/ + example/ sitio funcional de demo (posts, pages, config...) + alpine/APKBUILD receta para empaquetar a .apk + objetivo.txt este fichero + +sitio del usuario (creado con `simpleblog init`): config config global (key: value) posts/ entradas .md|.html con frontmatter - pages/ paginas estaticas (about, contacto...) - images/ imagenes originales (alta resolucion) - templates/ - header.html <head>, <header>, nav del menu - footer.html <footer> - post.html entrada individual (con tags y fecha) - page.html pagina estatica individual - index.html listado de posts (reusado para tag pages) - index-item.html item de la lista - tags.html indice global de tags - atom-header.xml cabecera del feed atom - atom-item.xml entry del feed atom - atom-footer.xml pie del feed atom - static/ se copia tal cual a public/ (css, favicon, logo, imgs) - vendor/smu/ conversor markdown vendored - public/ salida generada (gitignorable) + pages/ paginas estaticas + images/ imagenes originales + static/ se copia tal cual a public/ (css, favicon, etc) + templates/ (opcional) overrides de las plantillas default + public/ salida generada (gitignored) USO --- -make build genera public/ (compila smu la primera vez) -make clean borra public/ -make serve sirve public/ con busybox httpd en :8080 (fallback python) -make stats resumen de tamanos (originales, salida, variantes...) -make distclean borra public/ y limpia smu +# desarrollo local del repo (sin instalar) +make build construye example/public/ con templates/ del repo +make serve sirve example/public/ (darkhttpd / busybox / python) +make stats resumen de tamanos +make clean borra example/public/ +make install instala globalmente (script + templates + skel) +make dist crea tarball para empaquetar + +# como usuario final (despues de instalar) +simpleblog init inicializa un sitio nuevo en el cwd (copia skel) +simpleblog build genera public/ +simpleblog serve sirve public/ en :8080 +simpleblog stats resumen de tamanos +simpleblog clean borra public/ HECHO (2026-05-18) @@ -112,11 +122,16 @@ HECHO (2026-05-18) extra. WebP comprime ~70% menos que JPEG en pruebas reales. [x] Comando `make stats`: resumen de tamanos (originales, salida total, variantes de imagen) y conteos (posts, pages, tags). +[x] .gitignore + commit inicial. vendor/ excluido (smu se clona via + `make`). public/ excluido (salida generada). +[x] Refactor para empaquetar (2026-05-18): script trabaja sobre cwd, + plantillas con override (./templates -> /usr/share/simpleblog/templates), + nuevo comando `init` que copia skel al cwd. Skel minimo en skel/. + Sitio de demo en example/. APKBUILD en alpine/. smu deja de ser + vendored y pasa a paquete aparte (apk add smu). POR HACER (pendiente, priorizado) -[ ] .gitignore para public/ y vendor/smu/*.o, smu binario -[ ] Repo git inicial + commit [ ] Ocultar <nav class="pagination"> cuando esta vacia (en tag pages y cuando hay una sola pagina) — ahora queda un nav vacio en el HTML [ ] Validacion: avisar si un slug colisiona entre posts/pages/borradores diff --git a/pages/about.md b/pages/about.md @@ -1,12 +0,0 @@ -title: Sobre el sitio -description: Quien soy y de que va este blog. -menu: true -menu_order: 10 - -# Sobre el sitio - -Esta es una pagina estatica fuera de `posts/`. No aparece en el listado -del blog ni en el feed, pero se enlaza desde el menu de la cabecera. - -Si el frontmatter tiene `menu: true`, la pagina aparece en el menu del -sitio. El orden viene del campo `menu_order` (numerico). diff --git a/pages/contacto.md b/pages/contacto.md @@ -1,8 +0,0 @@ -title: Contacto -description: Como ponerse en contacto. -menu: true -menu_order: 20 - -# Contacto - -Aqui iria un correo, una direccion de mastodon, lo que sea. diff --git a/simpleblog b/simpleblog @@ -1,16 +1,31 @@ #!/bin/sh # simpleblog — generador estatico minimalista de blog -# uso: ./simpleblog [build|clean] +# uso: simpleblog [init|build|clean|stats|serve] +# +# trabaja sobre el directorio actual (el sitio). las plantillas y el skel +# por defecto viven en $SIMPLEBLOG_DEFAULTS (default /usr/share/simpleblog). +# si el sitio tiene su propia ./templates/<name>, esa gana sobre el default. set -eu -SRC="${SIMPLEBLOG_SRC:-$(cd "$(dirname "$0")" && pwd)}" -OUT="${SIMPLEBLOG_OUT:-$SRC/public}" -POSTS="$SRC/posts" -PAGES="$SRC/pages" -TPL="$SRC/templates" -STATIC="$SRC/static" -IMAGES="$SRC/images" -CONF="$SRC/config" +SITE_DIR="${SIMPLEBLOG_SITE:-$(pwd)}" +DEFAULTS="${SIMPLEBLOG_DEFAULTS:-/usr/share/simpleblog}" + +OUT="${SIMPLEBLOG_OUT:-$SITE_DIR/public}" +POSTS="$SITE_DIR/posts" +PAGES="$SITE_DIR/pages" +STATIC="$SITE_DIR/static" +IMAGES="$SITE_DIR/images" +CONF="$SITE_DIR/config" + +# devuelve la ruta a una plantilla con override: si existe en el sitio, esa; +# si no, la del directorio de defaults. +tpl() { + if [ -f "$SITE_DIR/templates/$1" ]; then + printf '%s\n' "$SITE_DIR/templates/$1" + else + printf '%s\n' "$DEFAULTS/templates/$1" + fi +} # --- utilidades ---------------------------------------------------------- @@ -62,8 +77,8 @@ sub_file() { # aplica todas las variables de $2 (key<TAB>valor) a la plantilla $1 -> $3 render() { - tpl="$1"; vars="$2"; out="$3" - cp "$tpl" "$out" + template="$1"; vars="$2"; out="$3" + cp "$template" "$out" while IFS=' ' read -r k v; do [ -z "$k" ] && continue sub_var "$k" "$v" "$out" @@ -228,6 +243,43 @@ cmd_clean() { mkdir -p "$OUT" } +# init: copia $DEFAULTS/skel/ al directorio actual. +cmd_init() { + if [ ! -d "$DEFAULTS/skel" ]; then + echo "no encuentro el skel en $DEFAULTS/skel" >&2 + echo "define SIMPLEBLOG_DEFAULTS o instala simpleblog" >&2 + exit 1 + fi + # avisamos si el directorio no esta vacio (ignoramos .git) + if [ -n "$(ls -A "$SITE_DIR" 2>/dev/null | grep -v '^\.git$' || true)" ]; then + echo "el directorio $SITE_DIR no esta vacio. usa otro o limpia primero." >&2 + exit 1 + fi + cp -r "$DEFAULTS/skel"/. "$SITE_DIR"/ + echo "sitio inicializado en $SITE_DIR" + echo "siguiente paso: edita config y posts/, luego 'simpleblog build'" +} + +cmd_serve() { + port="${SIMPLEBLOG_PORT:-8080}" + [ -d "$OUT" ] || cmd_build + echo "serving $OUT/ on http://localhost:$port" + if command -v darkhttpd >/dev/null 2>&1; then + exec darkhttpd "$OUT" --port "$port" + elif command -v httpd >/dev/null 2>&1; then + exec httpd -f -p "$port" -h "$OUT" + elif command -v busybox-extras >/dev/null 2>&1; then + exec busybox-extras httpd -f -p "$port" -h "$OUT" + elif command -v busybox >/dev/null 2>&1 && busybox --list 2>/dev/null | grep -qx httpd; then + exec busybox httpd -f -p "$port" -h "$OUT" + elif command -v python3 >/dev/null 2>&1; then + cd "$OUT" && exec python3 -m http.server "$port" + else + echo "no httpd encontrado. instala darkhttpd o busybox-extras." >&2 + exit 1 + fi +} + # escribe en $tmp/common.vars las variables que comparten todos los layouts # (excepto page_title/page_description, que dependen de cada pagina). write_common_vars() { @@ -253,8 +305,8 @@ render_layout() { printf 'page_title\t%s\n' "$(sed_escape "$pt")" printf 'page_description\t%s\n' "$(sed_escape "$pd")" } >> "$vars" - render "$TPL/header.html" "$vars" "$tmp/_layout.header" - render "$TPL/footer.html" "$vars" "$tmp/_layout.footer" + render "$(tpl header.html)" "$vars" "$tmp/_layout.header" + render "$(tpl footer.html)" "$vars" "$tmp/_layout.footer" # {{menu}} en el header se inserta como bloque (HTML, contiene &/<>) sub_file menu "$tmp/menu.html" "$tmp/_layout.header" cat "$tmp/_layout.header" "$bodyf" "$tmp/_layout.footer" > "$outf" @@ -273,9 +325,10 @@ cmd_build() { posts_per_page=$(conf posts_per_page) [ -z "$posts_per_page" ] && posts_per_page=0 md_cmd=$(conf markdown_cmd) - [ -z "$md_cmd" ] && md_cmd=markdown + [ -z "$md_cmd" ] && md_cmd=smu + # rutas relativas (./foo) se resuelven respecto al sitio case "$md_cmd" in - ./*) md_cmd="$SRC/${md_cmd#./}" ;; + ./*) md_cmd="$SITE_DIR/${md_cmd#./}" ;; esac tmp=$(mktemp -d) @@ -343,7 +396,7 @@ cmd_build() { while IFS=' ' read -r slug title desc; do pvars="$tmp/p.$slug.vars" { printf 'title\t%s\n' "$(sed_escape "$title")"; } > "$pvars" - cp "$TPL/page.html" "$tmp/p.$slug.body" + cp "$(tpl page.html)" "$tmp/p.$slug.body" while IFS=' ' read -r k v; do sub_var "$k" "$v" "$tmp/p.$slug.body" done < "$pvars" @@ -400,7 +453,7 @@ cmd_build() { printf 'date\t%s\n' "$(sed_escape "$date")" printf 'tags\t%s\n' "$(sed_escape "$tags_html")" } > "$pvars" - cp "$TPL/post.html" "$tmp/$slug.body" + cp "$(tpl post.html)" "$tmp/$slug.body" while IFS=' ' read -r k v; do sub_var "$k" "$v" "$tmp/$slug.body" done < "$pvars" @@ -474,7 +527,7 @@ cmd_build() { printf ' <li><a href="%stags/%s.html">%s</a> <small>(%s)</small></li>\n' \ "$site_baseurl" "$tag" "$tag" "$count" >> "$tmp/tags_index.html" done - cp "$TPL/tags.html" "$tmp/tags.body" + cp "$(tpl tags.html)" "$tmp/tags.body" sub_file items "$tmp/tags_index.html" "$tmp/tags.body" render_layout "$tmp/tags.body" "$OUT/tags.html" \ "Tags — $site_title" "Indice de tags" @@ -500,7 +553,7 @@ cmd_build() { printf 'feed\t%s\n' "$(sed_escape "$site_feed")" printf 'feed_updated\t%s\n' "$(sed_escape "$feed_updated")" } > "$fvars" - render "$TPL/atom-header.xml" "$fvars" "$tmp/feed.header" + render "$(tpl atom-header.xml)" "$fvars" "$tmp/feed.header" : > "$tmp/feed.items" while IFS=' ' read -r d s u t desc; do @@ -515,7 +568,7 @@ cmd_build() { printf 'updated\t%s\n' "$(sed_escape "$updated")" printf 'description\t%s\n' "$(sed_escape "$(xml_escape "$desc")")" } > "$evars" - cp "$TPL/atom-item.xml" "$tmp/e.out" + cp "$(tpl atom-item.xml)" "$tmp/e.out" while IFS=' ' read -r k v; do sub_var "$k" "$v" "$tmp/e.out" done < "$evars" @@ -527,7 +580,7 @@ cmd_build() { cat "$tmp/e.out" >> "$tmp/feed.items" done < "$tmp/index_items.sorted" - render "$TPL/atom-footer.xml" "$fvars" "$tmp/feed.footer" + render "$(tpl atom-footer.xml)" "$fvars" "$tmp/feed.footer" cat "$tmp/feed.header" "$tmp/feed.items" "$tmp/feed.footer" > "$OUT/$site_feed" fi @@ -561,11 +614,11 @@ render_index_page() { printf 'title\t%s\n' "$(sed_escape "$t")" printf 'description\t%s\n' "$(sed_escape "$desc")" } > "$ivars" - render "$TPL/index-item.html" "$ivars" "$tmp/_i.out" + render "$(tpl index-item.html)" "$ivars" "$tmp/_i.out" cat "$tmp/_i.out" >> "$tmp/_items_html" done < "$items" - cp "$TPL/index.html" "$tmp/_index.body" + cp "$(tpl index.html)" "$tmp/_index.body" sub_var list_title "$(sed_escape "$ltitle")" "$tmp/_index.body" pag="$prev $next" sub_var pagination "$(sed_escape "$pag")" "$tmp/_index.body" @@ -577,8 +630,10 @@ render_index_page() { # --- entrada ------------------------------------------------------------- case "${1:-build}" in + init) cmd_init ;; build) cmd_build ;; clean) cmd_clean ;; stats) cmd_stats ;; - *) echo "uso: $0 [build|clean|stats]" >&2; exit 1 ;; + serve) cmd_serve ;; + *) echo "uso: $0 [init|build|clean|stats|serve]" >&2; exit 1 ;; esac diff --git a/skel/.gitignore b/skel/.gitignore @@ -0,0 +1 @@ +public/ diff --git a/skel/config b/skel/config @@ -0,0 +1,24 @@ +# simpleblog config — key: value, # para comentarios +title: Mi blog +description: Un blog hecho con simpleblog +author: anon +# baseurl: path para enlaces relativos (acaba en /) +baseurl: / +# siteurl: URL absoluta, sin / final. Usado para el feed atom. +siteurl: https://example.com +lang: es + +# nombre del feed (vacio para no generarlo) +feed: atom.xml + +# paginacion del index. 0 = sin paginar +posts_per_page: 10 + +# anchos (px) a generar para cada imagen en images/ +image_sizes: 320, 640, 1280 +image_quality: 85 +# formatos extra de imagen (webp pesa ~30% menos que jpeg) +image_extra_formats: webp + +# conversor markdown (debe leer stdin y escribir stdout) +markdown_cmd: smu diff --git a/skel/images/.keep b/skel/images/.keep diff --git a/skel/pages/about.md b/skel/pages/about.md @@ -0,0 +1,10 @@ +title: Sobre +description: Sobre este blog. +menu: true +menu_order: 10 + +# Sobre + +Pagina estatica de ejemplo. Las paginas en `pages/` no aparecen en el +listado del blog ni en el feed. Si tienen `menu: true` salen en el menu +del sitio. diff --git a/skel/posts/hola-mundo.md b/skel/posts/hola-mundo.md @@ -0,0 +1,27 @@ +title: Hola mundo +date: 2026-01-01 +description: Primera entrada del blog. +tags: meta + +# Hola mundo + +Tu primera entrada. El frontmatter (las lineas `key: value` arriba) y el +cuerpo se separan con una linea en blanco. + +Edita este fichero, anade mas en `posts/`, y vuelve a ejecutar +`simpleblog build`. Para previsualizar, `simpleblog serve`. + +## Ejemplo de bloque de codigo + +Indenta con cuatro espacios (smu solo entiende markdown clasico, no +triple backtick): + + print("hola mundo") + +## Mas opciones + +* `draft: true` en el frontmatter -> no se publica +* `slug: foo` -> la URL sera /foo.html +* `tags: a, b, c` -> aparece en /tags/<tag>.html + +Mira la [documentacion](https://git.fedi.ovh/simpleblog/) para mas. diff --git a/static/favicon.svg b/skel/static/favicon.svg diff --git a/static/logo.svg b/skel/static/logo.svg diff --git a/skel/static/style.css b/skel/static/style.css @@ -0,0 +1,44 @@ +/* simpleblog — css minimal */ +* { box-sizing: border-box; } +body { + font-family: system-ui, sans-serif; + max-width: 40rem; + margin: 2rem auto; + padding: 0 1rem; + line-height: 1.5; + color: #222; + background: #fafafa; +} +a { color: #0366d6; } +a:hover { text-decoration: underline; } + +.site-header { border-bottom: 1px solid #ddd; padding-bottom: 1rem; margin-bottom: 2rem; } +.site-brand { display: inline-flex; align-items: center; gap: 0.5rem; text-decoration: none; color: #222; } +.site-logo { width: 1.75rem; height: 1.75rem; } +.site-title { font-size: 1.5rem; font-weight: bold; } +.site-description { color: #666; margin: 0.25rem 0 0; } +.site-nav { margin-top: 0.75rem; } +.site-nav a { margin-right: 0.75rem; } + +.site-footer { border-top: 1px solid #ddd; margin-top: 3rem; padding-top: 1rem; color: #666; font-size: 0.9rem; } + +.post-list ul { list-style: none; padding: 0; } +.post-item { margin-bottom: 1.25rem; } +.post-item time { display: inline-block; color: #888; font-family: monospace; margin-right: 0.5rem; } +.post-summary { color: #555; margin: 0.25rem 0 0; } + +.post-header h1 { margin-bottom: 0.25rem; } +.post-meta { color: #888; font-family: monospace; margin-top: 0; } +.post-content { margin-top: 2rem; } +.post-content pre { background: #f0f0f0; padding: 0.75rem; overflow-x: auto; } +.post-content code { background: #f0f0f0; padding: 0.1rem 0.3rem; } +.post-content pre code { padding: 0; background: none; } + +.pagination { margin-top: 2rem; } +.pagination a { margin-right: 1rem; } + +.post-tags { margin-left: 0.5rem; } +.post-tags a { background: #eef; padding: 0.05rem 0.4rem; border-radius: 3px; font-size: 0.85rem; } + +.tag-cloud ul { list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: 0.5rem; } +.tag-cloud li a { background: #eef; padding: 0.2rem 0.6rem; border-radius: 3px; }