├── includes └── layout ├── wswsh.conf.default ├── LICENSE ├── wswsh └── README.md /includes/layout: -------------------------------------------------------------------------------- 1 | # Page content 2 | 3 | function page_header { 4 | print -r -- ' 5 | 6 | 7 | 8 | ' 9 | if (( WSH_CSS == 1 )); then 10 | print -r -- " " 11 | fi 12 | if [[ -n $WSH_TITLE ]]; then 13 | print -r -- " ${WSH_TITLE}${WSH_GENFILE_TITLE:+ | $WSH_GENFILE_TITLE}" 14 | else 15 | print -r -- " ${WSH_GENFILE_TITLE}" 16 | fi 17 | print -r -- ' 18 | ' 19 | } 20 | 21 | function page_footer { 22 | print -r -- ' 23 | 24 | ' 25 | } 26 | -------------------------------------------------------------------------------- /wswsh.conf.default: -------------------------------------------------------------------------------- 1 | # wswsh.conf 2 | # Ypnose - http://ywstd.fr 3 | # One config per website. 4 | 5 | # Site title. 6 | #WSH_TITLE="" 7 | 8 | # CSS style. Pretty obvious, right? 9 | # Write entire location from the src directory. 10 | #WSH_CSSFILE="" 11 | 12 | # Default extension is txt, when WSH_EXT isn't specified. 13 | # If you want to write articles with HTML fragments, WSH_EXT should be 14 | # wshtml or wtfhtml. 15 | # DON'T WRITE html (redundant) or .txt (you don't need the dot). 16 | # For example, if you choose txt, it'll "use" files with the following 17 | # extensions: txt,TXT,txT,TxT,tXt and so on. 18 | #WSH_EXT="" 19 | 20 | # Write the other interpreter 21 | # You can leave this option if you only concatenate files with cat(1). 22 | #WSH_INTERP="" 23 | 24 | # Tag to match to extract page title, it defaults to

25 | #WSH_PATTERN="" 26 | 27 | # Regex to clear the HTML tags 28 | # Read the source code to see the default regex 29 | #WSH_REGEX="" 30 | 31 | # Enable source file copy. 32 | # The file in src/ used for the converting, will be copied to dest/ 33 | # Valid value: 1. Disable it by commenting out the line. 34 | #WSH_COPY="1" 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Modified BSD License 2 | 3 | Copyright (c) 2013-2020, Ypnose 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | Redistributions in binary form must reproduce the above copyright notice, this 13 | list of conditions and the following disclaimer in the documentation and/or 14 | other materials provided with the distribution. 15 | 16 | Neither the name of the {organization} nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /wswsh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env mksh 2 | # Script by Ypnose - http://ywstd.fr 3 | set -e 4 | 5 | WSH_BASEDIR="$1" 6 | WSH_CSS="0" 7 | WSH_COPY="0" 8 | 9 | function p_err { 10 | print -ru2 -- "$1" 11 | exit 1 12 | } 13 | 14 | function get_config { 15 | if [[ ! -r ${WSH_BASEDIR}/wswsh.conf ]]; then 16 | p_err "Missing wswsh.conf in $WSH_BASEDIR" 17 | fi 18 | . "${WSH_BASEDIR}/wswsh.conf" 19 | # NOT FINAL (SYMLINKS?)... 20 | if [[ -f ${WSH_BASEDIR}/includes/layout ]]; then 21 | . "${WSH_BASEDIR}/includes/layout" 22 | fi 23 | } 24 | 25 | function get_recdirs { 26 | typeset WSH_DESTDIR 27 | 28 | if [[ ! -d ${WSH_BASEDIR}/src ]]; then 29 | p_err "src directory is missing" 30 | fi 31 | # Create src/ hierarchy in dest/ 32 | ( cd "${WSH_BASEDIR}/src"; find . -type d ) | while IFS= read -r WSH_DESTDIR; do 33 | mkdir -p "${WSH_BASEDIR}/dest/${WSH_DESTDIR#*/}" 34 | done 35 | } 36 | 37 | function run_hook { 38 | # Run hooks before & after generating the page 39 | # Hooks can use all variables defined inside wswsh 40 | typeset WSH_HOOK 41 | 42 | WSH_HOOK="$1" 43 | if [[ -f ${WSH_BASEDIR}/includes/${WSH_HOOK} ]]; then 44 | . "${WSH_BASEDIR}/includes/${WSH_HOOK}" 45 | fi 46 | } 47 | 48 | function gen_page { 49 | typeset WSH_GENFILE WSH_GENFILE_NOSRC WSH_GENFILE_FINAL WSH_GENFILE_TITLE 50 | 51 | if [[ -n $WSH_INTERP && ! -x $(whence -p $WSH_INTERP) ]]; then 52 | p_err "$WSH_INTERP not found in PATH" 53 | fi 54 | 55 | if [[ -n $WSH_CSSFILE ]]; then 56 | if [[ -r ${WSH_BASEDIR}/src/${WSH_CSSFILE} ]]; then 57 | # WSH_CSS variable can be used in layout to trigger actions 58 | WSH_CSS="1" 59 | cp -a "${WSH_BASEDIR}/src/${WSH_CSSFILE}" "${WSH_BASEDIR}/dest/${WSH_CSSFILE}" 60 | else 61 | p_err "${WSH_BASEDIR}/src/${WSH_CSSFILE} is missing" 62 | fi 63 | fi 64 | 65 | cd "$WSH_BASEDIR" 66 | find src -type f -iname "*.${WSH_EXT:-txt}" | while IFS= read -r WSH_GENFILE; do 67 | WSH_GENFILE_NOSRC="${WSH_GENFILE#*/}" 68 | WSH_GENFILE_FINAL="dest/${WSH_GENFILE_NOSRC%%.*}.html" 69 | # WSH_GENFILE_TITLE can be used in layout 70 | WSH_GENFILE_TITLE="$(awk '/'"${WSH_PATTERN:-

}"'/{gsub(/'"${WSH_REGEX:-^[\t ]*

[\t ]*|[\t ]*<\/h1>.*$}"'/,"");print $0;exit}' "$WSH_GENFILE")" 71 | 72 | run_hook pre_genpage 73 | page_header >"$WSH_GENFILE_FINAL" 74 | run_hook post_header 75 | if ! "${WSH_INTERP:-cat}" "$WSH_GENFILE" >>"$WSH_GENFILE_FINAL"; then 76 | p_err "Generating $WSH_GENFILE failed" 77 | fi 78 | run_hook pre_footer 79 | page_footer >>"$WSH_GENFILE_FINAL" 80 | run_hook post_genpage 81 | 82 | if (( WSH_COPY == 1 )); then 83 | cp -a "src/${WSH_GENFILE_NOSRC}" "dest/${WSH_GENFILE_NOSRC}" 84 | fi 85 | done 86 | } 87 | 88 | function copy_html { 89 | typeset WSH_HTML 90 | 91 | # We copy existing *.html files from src to dest. 92 | find "src" -type f -iname "*.html" | while IFS= read -r WSH_HTML; do 93 | if [[ -f dest/${WSH_HTML#*/} ]]; then 94 | print "dest/${WSH_HTML#*/} already exists" 95 | continue 96 | fi 97 | cp -a -- "$WSH_HTML" "dest/${WSH_HTML#*/}" 98 | done 99 | } 100 | 101 | if [[ -z $1 || $1 = "-h" ]]; then 102 | print "usage: ${0##*/} [DIR]" 103 | exit 104 | fi 105 | 106 | get_config 107 | get_recdirs 108 | gen_page 109 | copy_html 110 | 111 | for f in robots.txt sitemap.xml; do 112 | if [[ -r ${WSH_BASEDIR}/src/${f} ]]; then 113 | cp -a "${WSH_BASEDIR}/src/${f}" "${WSH_BASEDIR}/dest/${f}" 114 | fi 115 | done 116 | 117 | WSH_TOT="$(find "${WSH_BASEDIR}/dest" -type f | wc -l)" 118 | print "$WSH_TOT file(s) in ${WSH_BASEDIR}/dest" 119 | 120 | exit 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wswsh - [w]eb [s]ucks [w]ithout [sh]ell 2 | --------------------------------------- 3 | 4 | wswsh is a static website script using shell. It means [w]eb [s]ucks 5 | [w]ithout [sh]ell. Simple name for a simple script. 6 | It has many advantages: 7 | 8 | * Lightweight 9 | * Only requires a shell + UNIX utilities 10 | * Compatible with [ahrf](https://github.com/Ypnose/ahrf) 11 | * Easily "hackable" with external scripts / interpreters 12 | * Less than 140 LOC (without external layouts) 13 | * Human readable configuration 14 | 15 | *You can read another howto with examples [here](http://ywstd.fr/blog/2013/blogging-shell.html) (might be a good intro).* 16 | 17 | How to use it? 18 | -------------- 19 | 20 | Create a directory including the following files: 21 | 22 | includes/layout 23 | wswsh 24 | wswsh.conf.default 25 | 26 | You'll need a config file. Copy the file `wswsh.conf.default` to 27 | `wswsh.conf`. Edit it according to your needs. The comments explain 28 | almost everything. 29 | 30 | A typical hierarchy contains a `src` directory, with your website inside 31 | it. 32 | 33 | . 34 | ├── includes/ 35 | │ └── layout 36 | ├── src/ 37 | │ ├── css/ 38 | │ │ └── style.css 39 | │ ├── blog/ 40 | │ │ └── my_post.txt 41 | │ ├── me/ 42 | │ │ └── john_doe.txt 43 | │ └── foo/ 44 | │ └── baz/ 45 | │ └── this_is_a_test.txt 46 | ├── wswsh 47 | └── wswsh.conf 48 | 49 | Each directory in `src` will be reproduced in a new directory `dest`. 50 | 51 | There is no default interpreter, only `cat` is called. It involves posts 52 | written in HTML. 53 | **wswsh** also supports [ahrf](https://github.com/Ypnose/ahrf). 54 | 55 | When you're ready, launch `./wswsh "$PWD"`. Using the previous example, 56 | we now have: 57 | 58 | . 59 | ├── includes/ 60 | │ └── layout 61 | ├── dest/ 62 | │ ├── css/ 63 | │ │ └── style.css 64 | │ ├── blog/ 65 | │ │ └── my_post.html 66 | │ ├── me/ 67 | │ │ └── john_doe.html 68 | │ └── foo/ 69 | │ └── baz/ 70 | │ └── this_is_a_test.html 71 | ├── src/ 72 | │ ├── css/ 73 | │ │ └── style.css 74 | │ ├── blog/ 75 | │ │ └── my_post.txt 76 | │ ├── me/ 77 | │ │ └── john_doe.txt 78 | │ └── foo/ 79 | │ └── baz/ 80 | │ └── this_is_a_test.txt 81 | ├── wswsh 82 | └── wswsh.conf 83 | 84 | `dest` is your generated website. You can upload it anywhere. 85 | 86 | Note(s) 87 | ------- 88 | 89 | If you want to have the same `wswsh` executable for all your blogs 90 | (assuming you have several websites), it's possible to put `wswsh` in 91 | your `PATH`, instead of having a "redundant" file, in every directory. 92 | 93 | The default behavior allows you to modify `wswsh` for your websites. So, 94 | it's possible to write custom modifications per site. 95 | 96 | The true "power" of `wswsh` resides in hooks launched at many different 97 | steps. Those hooks are sourced inside `wswsh` env allowing you to use 98 | inherited variables to launch all kind of specific actions. 99 | 100 | An "interpreter" can be run if it's placed ouside your `PATH`. Write the 101 | full path to the executable, within `wswsh.conf`: 102 | 103 | WSH_INTERP="/home/foo/my_exec" 104 | 105 | Why not provide a script sh compliant (or even bash)? 106 | ----------------------------------------------------- 107 | 108 | Few months ago, I still wanted to write a `sh` compliant version but I 109 | decided to drop that idea. At the moment, we are less than 5 people who 110 | use it. Creating a second version would be a waste of time, especially 111 | when the users already switched to `mksh`. 112 | Maintaining two redundant versions isn't easy and I do not want to work 113 | for nothing. If you're still interested, you're free to adapt to `sh`. 114 | It shouldn't be complicated. 115 | 116 | `awk` compatibility 117 | ------------------- 118 | 119 | When I added awk regexes, one of my goal was to support `nawk`, `mawk` 120 | and `gawk`. `gawk` is very common among Linux distributions, so I didn't 121 | have the choice. The regexes were "created" on `mawk`. I tested the 122 | compatibility and it worked flawlessly with the required implementations. 123 | So, you can gain some precious seconds if you're brave enough to use 124 | `nawk` or `mawk`. 125 | 126 | Copyright 127 | --------- 128 | 129 | Code created by Ypnose, under BSD (3-Clause) License. 130 | 131 | Website(s) 132 | ---------- 133 | 134 | Powering: 135 | * http://ywstd.fr/blog/ 136 | * http://savoirvivre.intraaktion.de 137 | 138 | You decided to adopt wswsh for your website(s)? Please contact me. I 139 | would be happy to add it in the README. 140 | --------------------------------------------------------------------------------