#!/usr/bin/env zsh # # ZShelf, file directory indexing for the web # # Copyright (C) 2014 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. # API in brief: # # filetype_icon() - takes a filename as arg (can be file only or real path) # - returns a filename for the icon to be used from icons/ # # preview_file() - takes a filename as arg # renders the file in a usable format (thumb if image, html if markdown, etc.) # returns the filename of the rendered result # # file list map for current dir # format: filename;size;date typeset -alU files typeset archive="" # full path to be indexed set by recursive_index() typeset diralias="" # index directory appended to url set by recursive_index() typeset url="" # address at which the web pages are available typeset data="" # address from which the data is downloadable typeset parent=".." # the directory above the current one # filetype_icon() # - takes a filename as arg (can be file only or real path) # - returns a filename for the icon to be used from icons/ filetype_icon() { { test "$1" = "" } && { error "filetype_icon called without argument"; return 1 } fpath="$1" filename="${fpath##*/}" ext="${filename##*.}" name="${filename%%.*}" res="default.png" # analize extensions, will be overridden by name case $ext:l in txt) res=text-plain.png ;; html) res=text-html.png ;; xml) res=text-xml.png ;; md5|sha*) res=hash.png ;; asc|gpg) res=signature.png ;; tex|md|org) res=text-x-texinfo.png ;; patch) res=text-x-patch.png ;; sql) res=text-x-sql.png ;; vcart) res=text-x-vcard.png ;; dmg) res=dmg.png ;; pdf) res=application-pdf.png ;; esac # analize name case $name:l in changelog) res=text-x-changelog.png ;; readme) res=text-x-readme.png ;; install) res=text-x-install.png ;; authors) res=text-x-authors.png ;; makefile) res=text-x-makefile.png ;; news|usage) res=text-x-nfo.png ;; copying) res=text-x-copying.png ;; known_bugs|todo) res=text-x-log.png ;; sources) res=folder-development.png ;; doc|docs) res=folder-documents.png ;; releases|binaries) res=folder-downloads.png ;; esac # todo using mimetype -b and more print "$res" } # external call from the render cli interface # should be called from inside the destination directory (pub or test) # it does cascade call anything else necessary in this file recursive_index() { # render_header "" { test -d "$1" } || { error "cannot index directory not found: $1"; return 1 } # archive: full path where the files to be indexed are found # diralias: index directory appended to url # indextype: type of index (short | long) archive="$1" basedir="/`basename "$archive"`/" diralias="$2" indextype="$3" dest="`pwd`" # if its a test use the file:// root for test dir [[ "$CMD" = "test" ]] && { url="file://`PWD=${SYS} pwd`" data="file://${archive}" } [[ "$url" = "" ]] && { url="$WEB_ROOT/$diralias" } [[ "$FILES_ROOT" = "" ]] || { data="$FILES_ROOT" } # if no special root is specified then use web_root [[ "$data" = "" ]] && { data="$WEB_ROOT" } func "index archive: $archive" func "index basedir: $basedir" func "index type: $indextype" func "index url: $url" func "index data: $data" # copy default icons mkdir -p $destination/icons$THUMB_SIZE cp $SYS/icons/$THUMB_SIZE/image-x-generic.png $destination/icons$THUMB_SIZE cp $SYS/icons/$THUMB_SIZE/symlink.png $destination/icons$THUMB_SIZE cp $SYS/icons/$THUMB_SIZE/folder.png $destination/icons$THUMB_SIZE cp $SYS/icons/$THUMB_SIZE/go-up.png $destination/icons$THUMB_SIZE dirs=`find "$archive" -type d` for d in ${(f)dirs}; do dir="${d##*${basedir}}" func "actual file path: $d" func "relative path: $dir" # we must check if its the parent directory [[ "$dir" = "$archive" ]] && { dir="" } func "destination: ${dest}/${dir}" mkdir -p "${dest}/${dir}" pushd "${dest}/${dir}" render_header > index${EXTENSION} render_file "$DIR"/tmpl/navbar.html >> index${EXTENSION} cat <> index${EXTENSION}
EOF # if indextype ends in _readme then renders README besides [[ "$indextype" =~ "readme" ]] && { test -r "${d}/README" } && { print "
" >> index${EXTENSION} } # string composited function call (indextype supports _appends) # takes 3 arguments: base dir, alias dir and indexed directory index_${indextype%_readme*} "${dir}" >> index${EXTENSION} # here also strips the _readme modifier [[ "$indextype" =~ "readme" ]] && { test -r "${d}/README" } && { print "
" >> index${EXTENSION}
            cat "${d}/README" >> index${EXTENSION}

            print "
" >> index${EXTENSION} } cat <> index${EXTENSION}

 

EOF render_footer >> index${EXTENSION} popd done } index_short() { dir="${1}" func "index_short \"$dir\"" func "index data: $archive/$dir" func "index url: $url / $dir" { test -d "$archive/$dir" } || { error "cannot index: not a directory '$archive/$dir'"; return 1 } files=() ttmp=`ls -l --time-style=long-iso "${archive}/${dir}" | awk ' /^total/ { next } /^$/ { next } /'"index${EXTENSION}"'/ { next } { printf "files+=(\"%s\");", $8 } '` { test $? = 0 } || { error "Error parsing directory: $archive/$dir" return 1 } # func "$ttmp" eval "$ttmp" act "${#files} files parsed in $dir" cat < EOF # if not parent offer to go up [[ "$dir" = "" ]] || { func "parent: $parent" cat < parent directory .. [ parent ] EOF } for f in ${files}; do name="${f}" ext="${name##*.}" # file extension file="${archive}/${dir}/${name}" # file path typefield="" namefield="" previewfield="" link="" icon="" icon_width="width=\"50px\"" if [ -L "$file" ]; then # is a symlink if [ -d "$file" ]; then # symlink to folder func "$name folder symlink" link="" typefield="${link}\"symlink\"" namefield="${link}${name}" else # symlink to file func "$name file symlink" link="" typefield="${link}\"symlink\"" namefield="${link}${name}" fi elif [ -d "$file" ]; then # is a folder func "$name folder" link="" typefield="${link}\"folder\"" namefield="${link}${name}" else # is a file func "$name file" { test "$icon" = "" } && { icon="`filetype_icon ${file}`" } link="" typefield="${link}\"${icon}\"" namefield="${link}${name}" fi # render it all cat < ${typefield} ${namefield} EOF tpwd="`pwd`" { test "$icon" = "" } && { continue } popd # copy the icon file { test -r ${destination}/icons${THUMB_SIZE}/${icon} } || { func "copy icon in place: $icon (PWD: `pwd`)" cp $SYS/icons/$THUMB_SIZE/${icon} \ ${destination}/icons${THUMB_SIZE}/${icon} } pushd "$tpwd" done print "" } index_long_preview() { dir="${1}" func "index_long_preview \"$dir\"" func "index data: $archive/$dir" func "index url: $url / $dir" { test -d "$archive/$dir" } || { error "cannot index: not a directory '$archive/$dir'"; return 1 } files=() ttmp=`ls -l --time-style=long-iso "${archive}/${dir}" | awk ' /^total/ { next } /^$/ { next } { printf "files+=(\"%s;%s;%s\");", $8, $5, $6 } '` { test $? = 0 } || { error "Error parsing directory: ${archive}/${dir}" return 1 } eval "$ttmp" act "${#files} files parsed in $dir" # human size dividers _mb=$((1024 * 1024)) _gb=$((1024 * 1024 * 1024)) cat < Filename Size Date Preview EOF # if not parent offer to go up [[ "$dir" = "" ]] || { func "parent: $parent" cat < parent directory .. [ parent ] EOF } for f in ${files}; do name="${f[(ws:;:)1]}" size="${f[(ws:;:)2]}" date="${f[(ws:;:)3]}" ext="${name##*.}" # file extension file="${1}/${name}" # file path # format size to human readable form if [[ $size -lt 1024 ]]; then hsize="$size B" elif [[ $size -gt 1024 ]]; then hsize="$(( $size / 1024 )) KB" elif [[ $size -gt 1048576 ]]; then hsize="$(( $size / $mb )) MB" else hsize="$(( $size / $gb )) GB" fi # format date to human readable form hdate=`date -d "$date" +'%d %b %Y'` func "$name \t $size \t $date" typefield="" namefield="" previewfield="" link="" icon="" preview=`preview_file "$file"` { test "$preview" = "" } || { func "file preview produced" case $ext:l in jpg|jpeg|png|gif|pdf|svg|eps) func "preview is a thumbnail" preview="\"$name\"" icon="image-x-generic.png" ;; esac } icon_width="width=\"50px\"" if [ -L "$file" ]; then # is a symlink if [ -d "$file" ]; then # symlink to folder link="" typefield="${link}\"symlink\"" namefield="${link}${name}" previewfield="" else # symlink to file link="" typefield="${link}\"symlink\"" namefield="${link}${name}" previewfield="${link}${preview}" fi elif [ -d "$file" ]; then # is a folder link="" typefield="${link}\"folder\"" namefield="${link}${name}" previewfield="" else # is a file { test "$icon" = "" } && { icon="`filetype_icon ${file}`" } link="" typefield="${link}\"${icon}\"" namefield="${link}${name}" previewfield="${link}${preview}" fi # render it all cat < ${typefield} ${namefield} $hsize $hdate ${previewfield} EOF tpwd="`pwd`" { test "$icon" = "" } && { continue } popd # copy the icon file { test -r ${destination}/icons${THUMB_SIZE}/${icon} } || { func "copy icon in place: $icon (PWD: `pwd`)" cp $SYS/icons/$THUMB_SIZE/${icon} \ ${destination}/icons${THUMB_SIZE}/${icon} } pushd "$tpwd" done print "" } # preview_file() - takes a filename as arg # renders the file in a usable format (thumb if image, html if markdown, etc.) # returns the filename of the rendered result preview_file() { { test "$1" = "" } && { error "no file to preview."; return 1 } # get the file extension using zsh builtins # %% is for deleting prefix and ## is for deleting suffix for f in ${1}; do filename="${f##*/}" name="${filename%%.*}" ext="${f##*.}" # lowercase case $ext:l in md) # markdown notice "$f: rendering markdown using maruku" output="$name".html maruku --html --html-frag $f -o "$output" print "$output" ;; jpg|jpeg|png) output="${name}-thumb.${ext}" { test -r "$output" } && { act "$f: thumbnail found, skip rendering" print "$output" continue } notice "$f: rendering thumbnail using ImageMagick" convert $f -resize 100 $output print "$output" ;; pdf|gif) # only first page / animation frame output="${name}-thumb.jpg" { test -r "$output" } && { act "$f: thumbnail found, skip rendering" print "$output" continue } notice "$f: rendering thumbnail using ImageMagick" convert "${f}[0]" -resize 100 $output print "$output" ;; svg|eps) output="${name}-thumb.jpg" { test -r "$output" } && { act "$f: thumbnail found, skip rendering" print "$output" continue } notice "$f: rendering thumbnail using ImageMagick" convert -density 144 "${f}" -resize 100 $output print "$output" ;; esac done return 0 }