#!/usr/bin/env zsh # # WebNomad, your slick and static website publisher # # Copyright (C) 2012-2015 Denis Roio # # This source code is free software; you can redistribute it and/or # modify it under the terms of the GNU Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This source code is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # Please refer to the GNU Public License for more details. # # You should have received a copy of the GNU Public License along with # this source code; if not, write to: # Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # full path to webnomad's system SYS="`pwd`/webnomad" helpers=1 source $SYS/zuper vars+=(SYS DIR CMD dst) vars+=(TITLE DESCRIPTION KEYWORDS TYPE IMAGE EXTENSION) vars+=(WEB_ROOT FILES_ROOT THUMB_SIZE BOOTSTRAP destination) vars+=(FLOWTYPE FONT_RATIO EXIF_CLEAN EXIF_COMMENT EXIF_ROTATE) vars+=(total_fonts cssfound) arrs+=(includecss includejs fonts) arrs+=(custom_fonts) # used by maildir vars+=(mail_id mail_render mail_date mail_insert_date mail_from mail_subj) vars+=(mdsrc mdname) source $SYS/zuper.init VERSION=0.4 # fill path to the source website root DIR="`pwd`" CMD="$1" [[ -r config.zsh ]] || { error "Directory not configured for WebNomad. First use /webnomad/init" return 1 } source config.zsh # thumbnail size THUMB_SIZE=${THUMB_SIZE:-256} #################################### # string match case insensitive unsetopt CASE_GLOB notice "Rendering your website" act "Title: $B $TITLE $r" # destination directory to render # also used by test to substitute pub/ destination="$DIR/pub" # setup paths for test { test "$CMD" = "test" } && { WEB_ROOT="file://`PWD=${SYS} pwd`/test" destination="$DIR/test" notice "Test settings for indexing" } # base to be added to all urls, from config.zsh baseurl="$WEB_ROOT" act "SYS = $SYS" act "WEB_ROOT = $WEB_ROOT" render_file() { if [[ -r $1 ]]; then sed -e "s@\${baseurl}@${baseurl}@g" $@ else warn "$1: render_file not found" fi } render_header() { if [[ "$IMAGE" =~ "http://" ]] ; then _image=$IMAGE else _image=$WEB_ROOT/$IMAGE fi cat < $TITLE EOF [[ $BOOTSTRAP = 0 ]] || { cat < EOF } cat < EOF [[ "$WEB_ROOT" = "" ]] || { cat < EOF } cat < EOF [[ "$TWITTER" = "" ]] || { cat < EOF } # include all css files found in views/css cssfound=`find $DIR/views/css -iname '*.css'` for c in ${(f)cssfound}; do includecss+=(${c##*/}) done # include also generated css for fonts if present [[ -f "${destination}"/css/custom.fonts.css ]] && includecss+=(custom.fonts.css) # add all css needed for c in $includecss; do act "+ css: $c" func "" cat < EOF if [[ -r "$DIR"/views/css/$c ]]; then cp -a $DIR/views/css/$c "${destination}"/css/ elif [[ -r "$SYS"/css/${c} ]]; then cp -a "$SYS"/css/${c} "${destination}"/css/ fi done # add the user configured header render_file "$DIR"/tmpl/header.html } render_footer() { cat < EOF # include all css files found in views/css jsfound=`find $DIR/views/js -type f -name '*.js'` for j in ${(f)jsfound}; do includejs+=(${j##*/}) done # insert and copy all js files for j in $includejs; do act "+ js: $j" cat < EOF if [[ -r "$DIR"/views/js/$j ]]; then cp -f "$DIR"/views/js/$j "$destination"/js elif [[ -r "$SYS"/js/$j ]]; then cp -f "$SYS"/js/$j "$destination"/js fi done render_file "$DIR"/tmpl/footer.html # add any string argument to the footer [[ "$1" = "" ]] || { print "${@}"; print } # if test mode then render the test footer [[ "$CMD" = "test" ]] && render_test_footer [[ "$FLOWTYPE" = "" ]] || { # if there is flowtype.js then use it cat < \$('p').flowtype({ minimum : 500, maximum : 1200, minFont : 12, maxFont : 40, fontRatio : $FONT_RATIO }); \$('blockquote').flowtype({ minimum : 500, maximum : 1200, minFont : 12, maxFont : 40, fontRatio : $(( $FONT_RATIO + 3 )) }); \$('h1').flowtype({ minimum : 500, maximum : 1200, minFont : 12, maxFont : 40, fontRatio : $(( $FONT_RATIO - 20 )) }); \$('h2').flowtype({ minimum : 500, maximum : 1200, minFont : 12, maxFont : 40, fontRatio : $(( $FONT_RATIO - 15 )) }); \$('h3').flowtype({ minimum : 500, maximum : 1200, minFont : 12, maxFont : 40, fontRatio : $(( $FONT_RATIO - 10 )) }); \$('h4').flowtype({ minimum : 500, maximum : 1200, minFont : 12, maxFont : 40, fontRatio : $(( $FONT_RATIO - 5 )) }); \$('h5').flowtype({ minimum : 500, maximum : 1200, minFont : 12, maxFont : 40, fontRatio : $(( $FONT_RATIO - 3 )) }); EOF } cat < EOF } render_html() { ####################################### ## we support the tag inline # parses the html and put all stuff contained in tags # inside separate files, leaving a trace of them into the main html # (a line starting with tmp.md$RAND) tmp="tmp.$RANDOM" awk 'BEGIN { srand(); markdown=0; } // { markdown=1; out="tmp.md" rand(); print out; next } /<\/markdown>/ { markdown=0; next } { if(markdown==1) { print $0 >out; next } else { print $0 } } ' > $tmp # first pass marks the markdown parts and saves them separate mds=(`find . -name 'tmp.md*'`) [[ "${#mds}" = "0" ]] || { # second pass substituted saved parts with rendered markdown act -n "${#mds} markdown fields " # check which markdown parser is available in PATH if command -v pandoc > /dev/null; then parser="pandoc -f markdown_github -t html5" # parses all html and renders each markdown in the html for i in $mds; do md=`basename $i` newtemp="tmp.$RANDOM" cat $tmp | awk ' /^'"$md"'/ { system("cat '"$md"' | '"$parser"'"); next } { print $0; }' > $newtemp rm $tmp; tmp=$newtemp done else error "Required program not found: pandoc" error "Install pandoc on this system to render markdown" fi } cat $tmp # extra js for html pages [[ "$FLOWTYPE" = "" ]] || { includejs+=(jquery.min.js) includejs+=(flowtype.js) } # clean up from temporary files rm -f tmp.* } read_meta() { # read metadata on each file which can change global settings # format: # # title put anything here # # description put your description here # # keywords list of keywords here tmp=`head -n 3 | awk ' !/^#/ { next } /title/ { printf "title=\""; for(i=3;i<=NF;i++) printf "%s ", $i; printf "\";" } /description/ { printf "description=\""; for(i=3;i<=NF;i++) printf "%s ", $i; printf "\";" } /keywords/ { printf "keywords=\""; for(i=3;i<=NF;i++) printf "%s ", $i; printf "\";" } '` eval "$tmp" } # calculate the destination path for a file or folder to render from views calc_dest() { [[ "$1" = "" ]] && { error "error calculating destination: cannot read $1" return 0 } if [[ "$CMD" = "test" ]]; then # when in test mode force .html as an extension # since that will make the local preview in browsers work ext=".html" else # optional 2nd arg: extension ext="$2" [[ "$ext" = "" ]] && { ext="$EXTENSION" } fi func "calc_dest \"$1\" \"$2\"" dstfile=${1##*/} dstfile=${dstfile%.*}${ext} func "destination file: $dstfile" dstdir=${1##*views/} dstdir=${dstdir%/*} func "destination subdir: $dstdir" # compute destination file if [ "${dstfile%.*}" = "${dstdir%.*}" ]; then # no subdirs, root level dst="${destination}/${dstfile}" else dst="${destination}/${dstdir}/${dstfile}" mkdir -p ${destination}/${dstdir} fi func "calculated destination: $dst" print $dst } ######### # MAIN func MAIN { test "$1" = "source" } && { return 0 } { test "$1" = "test" } && { act "Local test rendering inside test/" source $SYS/test } # render the base skeleton dir tree mkdir -p "$destination/css" mkdir -p "$destination/js" mkdir -p "$destination/img" [[ "$BOOTSTRAP" = "" ]] || { #{ test -r "$destination"/css/bootstrap.css } || { cp "$SYS"/css/bootstrap.css "$destination"/css/ cp "$SYS"/img/* "$destination"/img/ cp "$SYS"/css/bootstrap.min.css "$destination"/css/ cp "$SYS"/css/bootstrap-responsive.css "$destination"/css/ #} cp "$SYS"/js/bootstrap.min.js "$destination"/js/ cp "$SYS"/js/html5.js "$destination"/js/ } # copy to destination all subdirs in views/ act -n "publishing all $B views $r ... " rsync -a -W "$DIR/views/" "${destination}/" # make sure that new css styles are updated [[ -r "$DIR/views/css/custom.css" ]] && { cp -f "$DIR/views/css/custom.css" "${destination}/css/" } # prepare all fonts source $SYS/fonts # [[ ${#fonts} -gt 0 ]] && { # rsync -a -W "$DIR/fonts/" "$destination/" } act -n "Clean up all temp files ... " temps=(`find "$destination" -type f -name 'temp-*'`) for t in $temps; do rm -f $t; done [[ $QUIET = 1 ]] || print "done" # publish all .txt files as-is # useful for robots.txt txts=(`find views -maxdepth 1 -type f -name '*.txt'`) for t in $txts; do txt=`basename $t` dst=`calc_dest "$t" .txt` act "publishing plain text: $txt" cp $t ${destination}/$txt done publish_html() { src="$1" ext=${2:-.html} # read meta commands cat ${src} | read_meta dst=`calc_dest "$src" $ext` [[ "$FLOWTYPE" = "" ]] || includecss+=(flowtype.css) # render html act "Html rendering: $B $dst $r" render_header > $dst # close as nothing else is needed cat <> $dst EOF # don't forget the navbar render_file "$DIR"/tmpl/navbar.html >> $dst [[ $BOOTSTRAP = 0 ]] || { cat <> $dst

 

EOF } cat $src | render_html >> $dst [[ $BOOTSTRAP = 0 ]] || { cat <> $dst

 

 

EOF } render_footer >> $dst act "done" } # render all maildirs maildirs=(`find views -type f -name '*.maildir'`) maildirs+=(`find views -type f -name '*.maildirs'`) [[ "${#maildirs}" > 0 ]] && { source $SYS/maildir for mdsrc in $maildirs; do act "Maildirs list: $mdsrc" # base directory where to render mdname=${"$(basename $mdsrc)"[(ws:.:)1]} act "rendering in views/${mdname}.msg" mkdir -p views/${mdname}.msg cat ${mdsrc} | read_meta for md in ${(f)"$(cat $mdsrc)"}; do act "Maildir rendering: $md" maildircheck "$md" || continue mdb="${mdsrc}.db" [[ -r "$mdb" ]] || maildb_create "$mdb" # iterate through emails in maildir and insert new ones mails=`find "${md}/new" "${md}/cur" "${md}/tmp" -type f` act "`print ${mails} | wc -l` total emails found" for m in ${(f)mails}; do mail_id=${"$(sha256sum $m)"[(w)1]} u=`maildb_lookup_uid "$mdb" "$mail_id"` [[ "$u" = "" ]] && { mail_getinfo "$m" # $u is a new message act "new message found: $mail_subj" maildb_insert "$mdb" "$m" } done # iterate through database and render emails missing for m in ${(f)"$(maildb_list $mdb)"}; do mail_fill "$m" [[ -r pub/${mdname}.msg/${mail_id}.html ]] && { func "skip already rendered: $mail_id" continue } mail_file_render \ "$mail_file" \ > "views/${mdname}.msg/${mail_id}.html" done done # render indexes mail_index_render mail_atom_render done } # render all HTML views htmls=(`find views -type f -name '*.html'`) for src in $htmls; do publish_html $src done # render PHP files the same way phps=(`find views -type f -name '*.php'`) for src in $phps; do publish_html $src .php done # render all image galleries gals=(`find views -type f -name '*.gal'`) gals+=(`find views -type f -name '*.gallery'`) [[ "${#gals}" > 0 ]] && { source $SYS/gallery cp "$SYS"/js/*blueimp* "$destination"/js/ cp "$SYS"/css/*blueimp* "$destination"/css/ cp "$SYS"/js/jquery.min.js "$destination"/js/ for src in $gals; do cat ${src} | read_meta dst=`calc_dest "$src"` act -n "Gallery rendering: $B $dst $r ... " cat $src | render_gallery > $dst [[ $QUIET = 1 ]] || print "done" done } # render all directory indexes (TODO: properly fix recursion) idxs=(`find views -type f -name '*.idx'`) idxs+=(`find views -type f -name '*.index'`) { test ${#idxs} = 0 } || { source $SYS/index # loop through all .idx files for idx in $idxs; do # destination dir is named after the .idx file dst=`calc_dest "$idx"` # strip extension: an index builds a dir structure dst="${dst%.*}" notice "Directory index rendering to: $dst" # loop through all contents of the idx: # one directory to index recursively on each line dirs=`cat ${idx}` for d in ${(f)dirs}; do mkdir -p "${dst}" pushd "${dst}" # recursion wrap: $1=archive $2=diralias $3=indextype recursive_index "${d[(ws: :)1]}" "${d[(ws: :)2]}" "${d[(ws: :)3]}" # archive: full path where the files to be indexed are found # diralias: index directory appended to url # indextype: type of index (short | long) popd done done } # Here all files are copied inside the destination # we can run special operations on file-types and overwrite them now # for instance edit EXIF information on images etc. # clean up jpegs if jhead is installed command -v jhead && { jpegs=`find "$destination" -iname "*.jpg"` [[ $EXIF_CLEAN = 1 ]] && { for j in ${(f)jpegs}; do jhead -purejpg $j done } [[ "$EXIF_COMMENT" != "" ]] && { for j in ${(f)jpegs}; do jhead -cl "$EXIF_COMMENT" $j done } [[ $EXIF_ROTATE = 1 ]] && { for j in ${(f)jpegs}; do jhead -autorot $j done } } [[ $QUIET = 1 ]] || print "done" # if the whole website is a "slideshow" (set in config.zsh) then we start with # a full screen slideshow of all uploaded photos, cycling random every time. # galleries are supported and can be linked in menu and pages. { test "$WEBSITE" = "slideshow" } && { notice "Site is configured as slideshow" # generate a list of all images (removing duplicates) act "Indexing all images ... " find pub -iname '*.jpg' | sed -e 's/^pub\///g' -e 's/^.\/pub\///g' -e "s@'@@g" | sort | uniq \ | render_gallery views/index.html > ${destination}/index } notice "Website refreshed."