custom configuration support

- custom extensions
- custom filters
- ignore files matching given patterns
This commit is contained in:
nuex 2011-10-20 14:29:30 -04:00
parent f7affcaca7
commit ad7a86f1c5
10 changed files with 196 additions and 108 deletions

View File

@ -7,20 +7,20 @@ all:
@echo Compiled
install: all
@echo Installing zod executables to ${PREFIX}/bin
@echo Installing zod executable to ${PREFIX}/bin
@mkdir -p ${PREFIX}/bin
@cp bin/zod ${PREFIX}/bin
@cp bin/zod_render ${PREFIX}/bin
@echo Installing awk lib files to ${AWKLIB}
@mkdir -p ${AWKLIB}
@cp lib/render.awk ${AWKLIB}
@cp lib/markdown.awk ${AWKLIB}
@cp lib/config.awk ${AWKLIB}
@cp lib/opt_builder.awk ${AWKLIB}
@echo Installation Complete
uninstall:
@echo Uninstalling zod executable
@rm ${PREFIX}/bin/zod
@rm ${PREFIX}/bin/zod_render
@echo Uninstalling awk lib files
@rm -rf ${AWKLIB}
@echo Uninstallation Complete

92
bin/zod.template Normal file → Executable file
View File

@ -13,12 +13,94 @@ _zod_error() {
exit 1
}
_zod_config() {
cat - "$cfg" <<!
[parse]
htm,html
[parse_convert]
md awk -f "$zod_lib/markdown.awk"
[ignore]
helpers.awk
*.layout
*.meta
config
!
}
_zod_find_opt_builder() {
phase="$1"
_zod_config |
awk -f "$zod_lib/config.awk" \
-f "$zod_lib/opt_builder.awk" \
-v phase="$phase"
}
_zod_destination() {
file="$1"
# Find the target directory if one must be created
subdir="${file%/*}"
subdir="${subdir#$proj}"
if [ "$subdir" ]; then
destination="$target$subdir"
mkdir -p "$destination"
else
# There is no directory to create in target,
# i.e. file is in the root of the project
destination="$target"
fi
echo $destination;
}
_zod_render() {
file="$1"
ext="${file##*.}"
meta="${file%.$ext}.meta"
set -- -f "$zod_lib/config.awk"
set -- "$@" -f "$zod_lib/render.awk"
[ -f "$proj/helpers.awk" ] && set -- "$@" -f "$proj/helpers.awk"
set -- "$@" -
[ -f "$proj/global.meta" ] && set -- "$@" "$proj/global.meta"
[ -f "$meta" ] && set -- "$@" "$meta"
set -- "$@" "$file"
[ -f "$proj/main.layout" ] && set -- "$@" "$proj/main.layout"
page="${file##*/}"
page="${page%.$ext}.html"
destination=$(_zod_destination "$file")
_zod_config | awk "$@" > "$destination/$page"
}
_zod_copy() {
file="$1"
destination="$(_zod_destination "$file")"
cp "$file" "$destination"
}
_zod_exec() {
phase="$@"
set -- "$proj" -type f
for instruction in $(_zod_find_opt_builder "$phase" "$cfg"); do
inst=$(echo $instruction | sed 's/"//g')
case $inst in
or ) set -- "$@" -o;;
not ) set -- "$@" !;;
* ) set -- "$@" -name "$inst";;
esac
done
find "$@" | while read -r file; do
case "$phase" in
render ) _zod_render "$file";;
copy ) _zod_copy "$file";;
esac
done
}
[ "$#" -ne 2 ] && { echo "usage: zod projectdir targetdir"; exit; }
[ ! -d "$proj" ] && _zod_error "project directory does not exist"
[ ! -d "$target" ] && _zod_error "target directory does not exist"
find "$proj" -type f \
! -name "*.layout" \
! -name "*.meta" \
! -name "helpers.awk" \
-exec zod_render "$zod_lib" "$proj" "$target" {} \;
[ -f "$proj/.zod/config" ] && cfg="$proj/.zod/config"
_zod_exec "render"
_zod_exec "copy"

View File

@ -1,32 +0,0 @@
#!/bin/sh
# render a zodiac page
# zod_render zodlibdir projdir targetdir md_builtin [files]
zod_lib="$1"
proj="$2"
target="$3"
md_builtin="$4"
f="$5"
# source zod sh functions
. $zod_lib/zod_functions
ext=${f##*.}
meta=${f%.$ext}.meta
set -- -f "$zod_lib/render.awk"
[ -f "$proj/helpers.awk" ] && set -- "$@" -f "$proj/helpers.awk"
set -- "$@" -v markdown_filter_cmd="$md_builtin"
[ -f "$proj/global.meta" ] && set -- "$@" $proj/global.meta
[ -f "$meta" ] && set -- "$@" $meta
set -- "$@" "$f"
find "$proj" -type f -name "*.partial" -o -name "*.layout"
while read -r part; do
set -- "$@" "$part"
done
page=${f##*/}
page=${page%.$ext}.html
__zod_destination "$proj" "$target" "$f"
awk "$@" > "$destination/$page"

View File

@ -1,8 +0,0 @@
#!/bin/sh
# return a list of all supported file extensions
zod_lib="$1"
filter_opts="$2"
awk -f "$zod_lib/supported_extensions.awk" "$filter_opts"

View File

@ -5,9 +5,10 @@ function load_helpers() {
}
function page_title( title) {
if ("title" in data) {
return data["title"] " - " data["site_title"]
if (data["title"]) {
title = data["title"] " - " data["site_title"]
} else {
return data["site_title"]
title = data["site_title"]
}
return title
}

View File

@ -6,10 +6,14 @@
<title>{{page_title}}</title>
</head>
<body>
{{>header}}
<header>
<h1><a href="/">{{site_title}}</a></h1>
</header>
<article>
{{{yield}}}
</article>
{{>footer}}
<footer>
<p>powered by static files</p>
</footer>
</body>
</html>

44
lib/config.awk Normal file
View File

@ -0,0 +1,44 @@
#
# Parse zodiac config
#
# Ignore comments and empty lines
action == "config" && (NF == 0 || /^;/) {
next
}
# Get the current section
action == "config" && (/^\[/ && match($0, /\[([[:alnum:]_]).*\]/)) {
section = substr($0, (RSTART + 1), (RLENGTH - 2))
next
}
# Get filters in the parse section
action == "config" && section == "parse" {
n = split($0, exts, ",")
for (i in exts) {
ext = exts[i]
gsub(/ /, "", ext)
filter[ext] = "none"
}
next
}
# Get filters in the parse_convert section
action == "config" && section == "parse_convert" && (NF > 1) {
ext_list = $1
cmd = $2
n = split(ext_list, exts, ",")
for (i in exts) {
ext = exts[i]
gsub(/ /, "", ext)
filter[ext] = cmd
}
next
}
# Get ignore patterns
action == "config" && section == "ignore" {
ignore[ignore_count++] = $0
next
}

35
lib/opt_builder.awk Normal file
View File

@ -0,0 +1,35 @@
#
# Build find options
#
BEGIN {
section = "none"
action = "config"
}
END {
for (ext in filter) {
exts[ext_count++] = "\"" "*." ext "\""
}
for (i = 0; i < length(ignore); i++) {
instructions[inst_count++] = "not"
instructions[inst_count++] = "\"" ignore[i] "\""
}
if (phase == "render") {
instructions[inst_count++] = exts[0]
for (i = 1; i < length(exts); i++) {
instructions[inst_count++] = "or"
instructions[inst_count++] = exts[i]
}
} else if (phase == "copy") {
for (i = 0; i < length(exts); i++) {
instructions[inst_count++] = "not"
instructions[inst_count++] = exts[i]
}
}
# print all instructions
for (i = 0; i < length(instructions); i++) {
instruction = instruction " " instructions[i]
}
print instruction
}

View File

@ -1,47 +1,30 @@
# render.awk - Awk-based templating
#
# Render a zodiac page
#
BEGIN {
action = "none"
ext = "none"
main_content_type = "_main"
main = "_main"
helpers_loaded = "no"
layout = ""
filter_cmds["htm"] = "none"
filter_cmds["html"] = "none"
filter_cmds["md"] = markdown_filter_cmd
}
{
split(FILENAME, parts, ".")
ext = parts[length(parts)]
if (ext == "config") {
if ((FILENAME == "config") || (FILENAME == "-")) {
action = "config"
} else if (ext == "meta") {
action = "meta"
} else if (ext == "layout") {
action = "layout"
} else {
# not a known extension, assuming this line
# is from a page
content_extension = ext
action = "page"
filter_ext = ext
}
}
# Process lines from config
# Also ignore comments and empty lines
action == "config" && (NF > 0) && (!/^;.*/) {
split($0, filter_kv, ": ")
split(filter_kv[1], filter_extensions, ",")
filter_cmd = filter_kv[2]
for (i = 1; i <= length(filter_extensions); i++) {
filter_cmds[filter_extensions[i]] = filter_cmd
}
next
}
# Process lines from meta files
action == "meta" {
split($0, kv, ": ")
@ -58,14 +41,14 @@ action != "meta" && helpers_loaded == "no" && helpers == "yes" {
# Process lines from the page
action == "page" {
if (!contents[main_content_type]) {
contents[main_content_type] = bind_data($0)
if (!contents[main]) {
contents[main] = bind_data($0)
# save the extension for this content type
# to find the appropriate filter to render it
content_extensions[main_content_type] = ext
filter_exts[main] = ext
} else {
contents[main_content_type] = contents[main_content_type] "\n" bind_data($0)
contents[main] = contents[main] "\n" bind_data($0)
}
next
}
@ -75,7 +58,7 @@ action == "layout" {
# replace yield with rendered content
if (match($0, /{{{yield}}}/)) {
sub(/{{{yield}}}/, render_content(main_content_type))
sub(/{{{yield}}}/, render_content(main))
}
if (layout == "") {
@ -89,14 +72,14 @@ END {
if (layout != "") {
print layout
} else {
print render_content(main_content_type)
print render_content(main)
}
}
function bind_data(txt, tag, key) {
if (match(txt, /{{([^}]*)}}/)) {
tag = substr(txt, RSTART, RLENGTH)
match(tag, /(\w|[?]).*[^}]/)
match(tag, /([[:alnum:]_]|[?]).*[^}]/)
key = substr(tag, RSTART, RLENGTH)
gsub(tag, data[key], txt)
return bind_data(txt, data)
@ -105,14 +88,14 @@ function bind_data(txt, tag, key) {
}
}
function render_content(type, ext_key, content_extension, filter_cmd, txt) {
function render_content(type, ext_key, filter_ext, filter_cmd, txt) {
ext_key = type "_ext"
# Get the extension of the content type
content_extension = content_extensions[type]
filter_ext = filter_exts[type]
# Get the appropriate filter command for this extension
filter_cmd = filter_cmds[content_extension]
filter_cmd = filter[filter_ext]
# Get the text of the content for the given type
txt = contents[type]
@ -125,6 +108,7 @@ function render_content(type, ext_key, content_extension, filter_cmd, txt) {
}
function run_filter(cmd, txt, rand_date, tmpfile, rendered_txt, date_cmd, markdown_cmd, line) {
# TODO use mktemp instead
date_cmd = "date +%Y%m%d%H%M%S"
date_cmd | getline rand_date
close(date_cmd)

View File

@ -1,22 +0,0 @@
BEGIN {
extensions="md html"
}
# Process lines from config
# Also ignore comments and empty lines
(NF > 0) && (!/^;.*/) {
split($0, filter_kv, ": ")
split(filter_kv[1], filter_extensions, ",")
for (i = 1; i <= length(filter_extensions); i++) {
ext = filter_extensions[i]
if (!match(extensions, ext)) {
extensions = extensions " " ext
}
}
next
}
END {
print extensions
}