diff --git a/.gitignore b/.gitignore index 675a75a..72f89c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ -test-filetree +# SPDX-FileCopyrightText: 2019-2022 Robin Vobruba +# +# SPDX-License-Identifier: Unlicense + +.html/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..bf54057 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2019-2022 Robin Vobruba +# +# SPDX-License-Identifier: Unlicense + +[submodule "filters"] + path = filters + url = https://github.com/movedo/movedo-filters.git diff --git a/.pdsite.yml.default b/.pdsite.yml.default index 254ebcc..3b59453 100644 --- a/.pdsite.yml.default +++ b/.pdsite.yml.default @@ -1,5 +1,10 @@ # pdsite configuration +# SPDX-FileCopyrightText: 2016 Gord Stephen +# SPDX-FileCopyrightText: 2019 Robin Vobruba +# +# SPDX-License-Identifier: Unlicense + theme: default inputextension: .md outputfolder: .html @@ -7,5 +12,7 @@ outputfolder: .html # Site-wide template variables sitename: "My pdsite" +site-url: "https://myuser.gitlab.io" +site-base-path: /myproject pagetitle-suffix: "" footer: "My pdsite footer" diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..16af223 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2019-2022 Robin Vobruba +# +# SPDX-License-Identifier: Unlicense + +language: python + +addons: + apt: + packages: + - pandoc + - python3-setuptools + - texlive-latex-base + +before_script: + - python --version + - pip install -r requirements.txt + +script: + - cd docs + - ../bin/pdsite build + diff --git a/LICENSES/BSD-3-Clause.txt b/LICENSES/BSD-3-Clause.txt new file mode 100644 index 0000000..ea890af --- /dev/null +++ b/LICENSES/BSD-3-Clause.txt @@ -0,0 +1,11 @@ +Copyright (c) . + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSES/CC0-1.0.txt b/LICENSES/CC0-1.0.txt new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/LICENSES/CC0-1.0.txt @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/LICENSES/Unlicense.txt b/LICENSES/Unlicense.txt new file mode 100644 index 0000000..cde4ac6 --- /dev/null +++ b/LICENSES/Unlicense.txt @@ -0,0 +1,10 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/README.md b/README.md new file mode 100644 index 0000000..178494d --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ + + +# pdsite + +![](https://api.travis-ci.org/hoijui/pdsite.svg?branch=master) +[![License: BSD-3-Clause]( + https://img.shields.io/badge/License-BSD%203%20Clause-blue.svg)]( + https://opensource.org/licenses/BSD-3-Clause) +[![REUSE status]( + https://api.reuse.software/badge/github.com/hoijui/pdsite)]( + https://api.reuse.software/info/github.com/hoijui/pdsite) + +`pdsite` is short for "**p**an**d**oc**site**". + +It is a simplistic, single shell script, **static site generator** (SSG). + +It is written in the [BASH](https://www.gnu.org/software/bash/) scripting language, +uses the [pandoc](https://pandoc.org/) document converter +and requires [Markdown](https://en.wikipedia.org/wiki/Markdown#Example) files +as sources. + +While it can be used stand-alone, +it is also used by default by the [MoVeDo](https://github.com/movedo/MoVeDo) +documentation build system. diff --git a/bin/pdsite b/bin/pdsite index 536ebe5..6e650f0 100755 --- a/bin/pdsite +++ b/bin/pdsite @@ -1,149 +1,588 @@ #! /usr/bin/env bash -scriptpath=$(readlink -f $0) -themespath=${scriptpath%/*}/../themes -defaultconfig=${scriptpath%/*}/../.pdsite.yml.default - -function init(){ - cp $defaultconfig .pdsite.yml +# SPDX-FileCopyrightText: 2016-2019 Gord Stephen +# SPDX-FileCopyrightText: 2019-2026 Robin Vobruba +# +# SPDX-License-Identifier: BSD-3-Clause + +# Exit immediately on each error and unset variable; +# see: https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +set -Eeuo pipefail + +script_path=$(readlink -f "${BASH_SOURCE[0]}") +install_path="${script_path%/*}/.." +themes_path="${install_path}/themes" +filters_path="${install_path}/filters" +default_config="${install_path}/.pdsite.yml.default" +debug=false + +function print_help() { + echo "$(basename "$0") - Simple static site generator, written in BASH, using pandoc." + echo + echo "Usage:" + echo " $(basename "$0") command [OPTION...]" + echo + echo "Commands:" + echo " help, -h print this help message and exit" + echo " version, -v print the tools version and exit" + echo " init initializes a new pdsite project in the current directory" + echo " build builds the static site in the current directory" + echo " serve serves the static site in the current directory on a local webserver" + echo + echo "Options:" + echo " -h, --help print this help message and exit" + echo " -v, --version print the tools version and exit" + echo " -d, --debug Additional output and do not clean up *.tmp files" + echo " -l, --local build or serve for a local run of the site" + echo " (not using the URL and base path)" + echo " -t, --theme set the name of the theme" + echo " -i, --input-ext set the input extension (for example \".md\")" + echo " --input-index Read the list of (Markdown) input files from a file," + echo " where they are separated by new-lines" + echo " -o, --output-folder set the path to the output folder" + echo " -u, --site-url set the sites base URL (for example \"https://www.mysite.com\")" + echo " -p, --site-base-path set the sites base path (for example \"/base/path\")" } -function build(){ - - # Load config variables from file - if [ -f ./.pdsite.yml ]; then - configfile=$(pwd)'/.pdsite.yml' - theme=$(cat $configfile | grep '^theme:' | sed 's|^theme:\s*\(.*\)$|\1|') - inputextension=$(cat $configfile | grep '^inputextension:' | sed 's|^inputextension:\s*\(.*\)$|\1|') - outputfolder=$(cat $configfile | grep '^outputfolder:' | sed 's|^outputfolder:\s*\(.*\)$|\1|') - [ "$theme" ] && [ "$inputextension" ] && [ "$outputfolder" ] || { - echo "ERROR: Missing config variables in .pdsite.yml" 1>&2 - exit 1 - } - else - echo "ERROR: .pdsite.yml configuration file not detected" 1>&2 - exit 1 - fi - - themepath=$themespath/$theme - outputfolder=$(readlink -f $outputfolder) - - # Build glob expressions - extensionglob='*'$inputextension - indexfileglob='*index'$inputextension - - # Define temporary file locations - globaltree=$outputfolder/tree.yml.tmp - localtree=localtree.yml.tmp - localblocktemplate=$outputfolder/localtemplate.yml.tmp - localblock=local.yml.tmp - configblock=$outputfolder/config.yml.tmp - - # Define web-safe URL creation from file/directory names - makeslug(){ - tr -dc '[:graph:][:space:]' | tr '[:upper:]' '[:lower:]' | tr -s ' -_' | tr ' _' '-' - } - - # Define human-readable page title creation from web-safe filenames - makepretty(){ - tr '-' ' ' | awk '{for(i=1;i<=NF;i++){ $i=toupper(substr($i,1,1)) substr($i,2) }}1' - } +function print_version() { + local ret=0 + local git_version + local file_version + git_version=$(cd "$install_path"; git describe --always --tags 2> /dev/null || true) + file_version=$(cat VERSION 2> /dev/null || true) + if [ -n "$git_version" ] + then + version="$git_version" + elif [ -n "$file_version" ] + then + version="$file_version" + else + version="" + ret=1 + fi + echo "pdsite version: '$version'" + return $ret +} - # Set up output folder - if [ -d $outputfolder ]; then - echo -n "Clearing old build folder..." - rm -r $outputfolder/* - else - echo -n "Creating new build folder..." - mkdir $outputfolder - fi - echo " done." +# Checks whether a binary we depend on is available +function check_dep() { + local ret=0 + local cmd_name="$1" + local cmd_use_case="${2:-}" + local cmd_path + cmd_path=$(which "$cmd_name") + if [ -z "$cmd_path" ]; then + ret=1 + echo "Could not find dependency '$cmd_name', used ${cmd_use_case}." 1>&2 + fi + return $ret +} - echo -n "Building site..." +# Checks whether our dependencies are available +function check_deps() { + local ret=0 - echo -e "\n---" > $configblock - cat $configfile >> $configblock - echo -e "...\n" >> $configblock + # required + check_dep pandoc "to convert '*.md' -> '*.html'" \ + || ret=$((ret + $?)) - echo -e "\n---\npagenameintitle: y\npagename: _\n...\n" > $localblocktemplate + # optional + check_dep busybox "for local http hosting" \ + || true - # Generate base file structure - find -not -path "*/\.*" -type d | makeslug | xargs -I path mkdir -p "$outputfolder"/path - find -not -path "*/\.*" -not -path "$indexfileglob" -path "$extensionglob" -type f | sed 's|\(.*\)\..*|\1|' | makeslug | xargs -I path mkdir -p $outputfolder/path + [ $ret -gt 0 ] && exit $ret + return $ret +} - # Copy in index files - find -not -path "*/\.*" -path "$indexfileglob" -type f | while read inpath; do - outpath=$(echo $inpath | makeslug) - cp "$inpath" "$outputfolder/$outpath" - done +function init() { + if [ ! -e .pdsite.yml ]; then + cp "$default_config" .pdsite.yml + else + echo "ERROR: .pdsite.yml is already present" 1>&2 + exit 1 + fi +} - # Copy in other content files - find -not -path "*/\.*" -not -path "$indexfileglob" -path "$extensionglob" -type f | while read inpath; do - outpath=$(echo $inpath | sed 's|\./\(.*\)\.\(.*\)|\1/index.\2|' | makeslug) - cp "$inpath" "$outputfolder"/"$outpath" - done +# Define web-safe URL creation from file/directory names +function make_slug() { + tr -dc '[:graph:][:space:]' \ + | tr '[:upper:]' '[:lower:]' \ + | tr -s ' -_' \ + | tr ' _' '-' +} - # Copy in other (non-written-content) files - find -not -path "*/\.*" -not -path "$extensionglob" -type f -exec cp {} "$outputfolder"/{} \; +function simplify_path() { + echo -n "${@}" | sed -e 's|^\./||' -e 's/[^-_.~a-zA-Z0-9/]/_/g' +} - cd "$outputfolder" +# Define human-readable page title creation from web-safe filenames +function make_pretty() { + tr '-' ' ' \ + | awk '{ for (i = 1; i <= NF; i++) { $i = toupper(substr($i, 1, 1)) substr($i, 2) }} 1' +} - # Generate global file structure for navigation templates - echo -e '\n---' > $globaltree - tree -dfJ --noreport | cut -c 2- | while read line; do +# Check whether a shell variable is set +_var_set() { + set | grep '^'"$1"'=' > /dev/null +} - # Generate path relative to site root - path=$(echo $line | grep 'type' | sed 's|.*"name":"\.\(.*\)","contents".*|\1|') +# Remove quotes if present +_remove_quotes() { + _val="$1" + temp="${_val%\"}" + temp="${temp#\"}" + echo "$temp" +} - # Generate pretty page name automatically - name=$(echo $path | sed 's|.*/\(.*\)|\1|' | makepretty) +# Set a shell variable if it is yet unset +_set_if_unset() { + _var_name="$1" + shift + if ! _var_set "$_var_name" + then + eval "$_var_name=$(_remove_quotes "${@}")" + fi +} - # Inject page name and path into site tree - echo $line | sed 's|"name":"\(.*\)","contents"|"name":"'"$name"'","path":"'"$path"'","contents"|' +function get_config_var() { + local ret=0 + local var_name="$1" + local value + value=$(grep '^'"$var_name"':' < "$config_file" | sed 's|^'"$var_name"':\s*\(.*\)$|\1|') + [ "$value" ] || { + echo "Missing config variable '$var_name' in .pdsite.yml" 1>&2 + ret=1 + } + echo -n "$value" + return $ret +} - done >> $globaltree - echo -e '...\n' >> $globaltree +function find_source_files_navi() { + find . -not -path "*/\.*" -name "$extension_glob" -type f +} - # Generate local YAML - find -path "$indexfileglob" -type f | while read line; do +function find_index_source_files() { + find . -not -path "*/\.*" -not -path "${out_exclude}*" -path "$index_file_glob" -type f +} - relpath=${line%/*} - siteabspath=$(echo $relpath | cut -c 2-) +function find_non_index_source_files() { + find . -not -path "*/\.*" -not -path "${out_exclude}*" -not -regex ".*/\(README\|LICENSE\|COPYING\|TODO\|AUTHORS\)$extension_glob" -not -path "$index_file_glob" -path "$extension_glob" -type f +} - # Create local YAML block with auto-generated page name - name=$(echo ${relpath##*/} | makepretty) - sed 's|^pagename: _$|pagename: '"$name"'|' $localblocktemplate > $relpath/$localblock +function find_source_dirs() { + find . -not -path "*/\.*" -not -path "${out_exclude}*" -type d +} - # Create local YAML block with context-aware file tree data - sed 's|"path":"'$siteabspath'",|\0"active":y,|' $globaltree > $relpath/$localtree +function list_source_files_navi() { + if _var_set "input_index_file" + then + cat "$input_index_file" + else + find_source_files_navi + fi +} - done +function list_index_source_files() { + if _var_set "input_index_file" + then + grep ".$index_file_glob\$" < "$input_index_file" || true + else + find_index_source_files + fi +} - # Convert content files to context-aware HTML - find -path "$indexfileglob" -type f -execdir pandoc --template $themepath/template.html --toc -o index.html {} $localtree $localblock $configblock \; -delete +function list_non_index_source_files() { + if _var_set "input_index_file" + then + grep -v ".$index_file_glob\$" < "$input_index_file" + else + find_non_index_source_files + fi +} - # Clean up - find -path "*.tmp" -type f -delete +function list_source_dirs() { + if _var_set "input_index_file" + then + sed -e 's|/\?[^/]*$||' < "$input_index_file" | sort -u + else + find_source_dirs + fi +} - # Copy in theme assets - cd "$themepath" - find -not -path "." -type d -exec mkdir -p "$outputfolder"/{} \; - find -not -path "./template.html" -type f -exec cp {} "$outputfolder"/{} \; +# Generate global file structure for navigation templates +function navi() { + echo -e '\n---' + + # Match nothing + local np_prev="" + local np_open=0 + while read -r np + do + # Close finished sub-folders + while [[ "$np" != "$np_prev"* ]] + do + echo "]}," + np_open=$((np_open - 1)) + np_prev=$(dirname "$np_prev") + done + + # Open parent folders without content + while [[ "$np" != "$np_prev" ]] || [[ $np_open -eq 0 ]] + do + # Adding one path element + # Generate path relative to site root + local path + path="${np_prev}$(echo "${np:${#np_prev}}" | sed -e 's|\(.\)/.*|\1|')" + + # Generate pretty page name automatically + local name + name=$(grep "^title:" "$path/index.md" 2> /dev/null | head -n 1 | sed -E -e 's|^title:[[:space:]]*||' || echo "") + if [ -z "$name" ] + then + name=$(echo "$path" | sed 's|.*/\(.*\)|\1|' | make_pretty) + else + # Removes quotes if present + name="$(echo "$name" | sed -e 's|^"\(.*\)"$|\1|' -e "s|^'\(.*\)'$|\1|")" + fi + + local path_rtf="${path#.}" + local path_part="" + [ -n "$path_rtf" ] && path_part='"path":"'$link_prefix$path_rtf'",' + echo '{"type":"directory","name":"'"$name"'",'"$path_part"'"contents":[' + np_open=$((np_open + 1)) + + np_prev="$path" + done + done < <(list_source_files_navi | tr '\n' '\0' | xargs -0 dirname | sort | uniq) + + # Close remaining folders + while [ $np_open -gt 0 ] + do + echo "]}" + np_open=$((np_open - 1)) + done + + echo -e '...\n' +} - echo " done." +function build() { + local build_local=false + + while [ -n "${1:-}" ] + do + swt=$1 + shift + case $swt in + -h|--help) + print_help + exit 0 + ;; + -v|--version) + print_version + exit 0 + ;; + -d|--debug) + debug=true + ;; + -l|--local) + build_local=true + ;; + -t|--theme) + theme="$1" + shift + ;; + -i|--input-ext) + input_extension="$1" + shift + ;; + --input-index) + input_index_file="$1" + shift + ;; + -o|--output-folder) + output_folder="$1" + shift + ;; + -u|--site-url) + site_url="$1" + shift + ;; + -p|--site-base-path) + site_base_path="$1" + shift + ;; + *) + echo "Unknown switch '$swt'" 1>&2 + print_help + exit 1 + ;; + esac + done + + echo "Build property: local='$build_local'" + + # Load config variables from file + config_file=$(pwd)'/.pdsite.yml' + if [ -f "$config_file" ]; then + theme="${theme:-"$(get_config_var 'theme')"}" + _set_if_unset input_extension "$(get_config_var 'inputextension')" + output_folder="${output_folder:-"$(get_config_var 'outputfolder')"}" + _set_if_unset site_url "$(get_config_var 'site-url' || true)" + _set_if_unset site_base_path "$(get_config_var 'site-base-path' || true)" + else + echo "WARNING: '.pdsite.yml' configuration file not found." 1>&2 + echo " Using default and command line supplied values" 1>&2 + fi + _set_if_unset theme "default" + _set_if_unset input_extension ".md" + output_folder="${output_folder:-"build"}" + _set_if_unset site_url "" + _set_if_unset site_base_path "" + if $build_local + then + # NOTE This makes the site not (fully) position independent. + # The alternative would be, to use relative paths to the root. + link_prefix="$output_folder" + else + link_prefix="$site_base_path" + fi + + # Set up output folder + if [ -d "$output_folder" ]; then + echo -n "Clearing old build folder..." + rm -rf "${output_folder:?}/"* + else + echo -n "Creating new build folder..." + mkdir -p "$output_folder" + fi + echo " done." + + theme_path="$themes_path/$theme" + output_folder=$(cd "$output_folder"; pwd) + + # Build glob expressions + extension_glob='*'$input_extension + index_file_glob='*index'$input_extension + + # Define temporary file locations + global_tree="$output_folder/tree.yml.tmp" + local_tree="local_tree.yml.tmp" + local_block_template="$output_folder/localtemplate.yml.tmp" + local_block="local.yml.tmp" + config_block="$output_folder/config.yml.tmp" + + echo "Building site..." + + { + echo -e "\n---" + #cat $config_file + echo "theme: $theme" + echo "inputextension: $input_extension" + echo "outputfolder: $output_folder" + echo "site-url: $site_url" + echo "site-base-path: $site_base_path" + echo -e "...\n" + } > "$config_block" + + echo -e "\n---\npagenameintitle: y\npagename: _\n...\n" > "$local_block_template" + + # By default, match nothing + out_exclude="XXX_XXX_XXX" + # If the output folder is contained within the source folder, exclude it + [[ "$output_folder" == $(pwd)* ]] && out_exclude='.'${output_folder#"$(pwd)"} + + export simplify_path + + if $debug + then + list_source_dirs > "$output_folder/list_source_dirs.txt.tmp" + list_non_index_source_files > "$output_folder/list_non_index_source_files.txt.tmp" + list_index_source_files > "$output_folder/list_index_source_files.txt.tmp" + list_source_files_navi > "$output_folder/list_source_files_navi.txt.tmp" + fi + + echo "Generate base file structure ..." + list_source_dirs \ + | make_slug \ + | while read -r in_path + do + mkdir -p "$output_folder/$(simplify_path "$in_path")" + done + list_non_index_source_files \ + | sed 's|\(.*\)\..*|\1|' \ + | make_slug \ + | while read -r in_path + do + mkdir -p "$output_folder/$(simplify_path "$in_path")" + done + + echo "Copy in index files ..." + list_index_source_files \ + | while read -r in_path + do + out_path=$(simplify_path "$(echo "$in_path" | make_slug)") + cp "$in_path" "$output_folder/$out_path" + done + + echo "Copy in other content files ..." + list_non_index_source_files | while read -r in_path + do + out_path=$(simplify_path "$(echo "$in_path" | sed -e 's|^\./||' -e 's|\(.*\)\.\(.*\)|\1/index.\2|' | make_slug)") + cp "$in_path" "$output_folder/$out_path" + done + + echo "Copy in other (non-written-content) files ..." + find . -not -path "*/\.*" -not -path "${out_exclude}*" -not -path "*$extension_glob" -type f | while read -r file_path + do + new_dir=$(dirname "$file_path") + new_dir=$(simplify_path "$new_dir") + new_file=$(basename "$file_path") + mkdir -p "$output_folder/$new_dir" + cp "$file_path" "$output_folder/$new_dir/$new_file" + done + + cd "$output_folder" + + echo "Generating the navigation menu JSON file ..." + navi > "$global_tree" + + echo "Generate local YAML ..." + find . -not -path "${out_exclude}*" -path "$index_file_glob" -type f | while read -r line; do + rel_path=${line%/*} + site_path_abs=$(echo "$rel_path" | cut -c 2-) + + # Create local YAML block with auto-generated page name + name=$(echo "${rel_path##*/}" | make_pretty) + sed 's|^pagename: _$|pagename: '"$name"'|' "$local_block_template" > "$rel_path/$local_block" + + # Create local YAML block with context-aware file tree data + sed 's|"path":"'"$site_path_abs"'",|\0"active":y,|' "$global_tree" > "$rel_path/$local_tree" + done + + # This meta-data file is used for each markdown file to be converted, + # but only during conversion. + # We need this, as it is not possible to supply a YAML array + # with "-M" to pandoc, which we require for "panflute-filters". + common_meta_file="$(mktemp --suffix=".yml")" + # Remove this file when script exits + trap 'rm -rf -- "$common_meta_file"' EXIT + { + echo "panflute-path: \"$filters_path\"" + echo "panflute-filters: [add_local_link_prefix, replace_link_suffixes]" + if $debug + then + echo "panflute-verbose: true" + fi + } > "$common_meta_file" + + echo "Convert content files to context-aware HTML ..." + find . -not -path "${output_folder}*" -path "$index_file_glob" -type f \ + | while read -r index_md + do + dir_before="$(pwd)" + cd "$(dirname "$index_md")" + site_base_path_rel="$index_md" + site_base_path_rel="${site_base_path_rel#./}" + site_base_path_rel="${site_base_path_rel%/index.md}" + site_base_path_rel="$(echo "$site_base_path_rel" | sed -e 's|[^/]\+|..|g')" + index_md_local="$(basename "$index_md")" + pandoc \ + -V site_base_path="$site_base_path_rel" \ + --template "$theme_path/template.html" \ + --toc \ + -o index.html \ + -M allp_prefix="../" \ + -M allp_file="$index_md_local" \ + -M rls_relative_only=True \ + -M rls_ext_from=".md" \ + -M rls_ext_to=".html" \ + --metadata-file="$common_meta_file" \ + -F panflute \ + "$index_md_local" "$local_tree" "$local_block" "$config_block" + cd "$dir_before" + rm "$index_md" + done + + echo "Check if site has a usable structure ..." + start_path="index.html" + if [ ! -e "$output_folder/$start_path" ]; then + echo "WARNING: There is no index.html in the web-site root folder." 1>&2 + # Choose an arbitrary *.html file from our output as start path + start_path=$(cd "$output_folder"; find . -regex "\./.+\.html" | sort | head -n 1 | sed -e 's|^\./||') + if [ -z "$start_path" ]; then + echo "ERROR: There is no *.html in the generated web-site." 1>&2 + exit 1 + fi + fi + start_path_http="${start_path%index.html}" + + if $debug + then + echo "Clean up ..." + find . -path "*.tmp" -type f -delete + else + echo "Skipping cleanup due to debug mode." + fi + + echo "Copy in theme assets ..." + cd "$theme_path" + find . -not -path "." -type d | while read -r dir_path + do + new_dir=$(simplify_path "$dir_path") + mkdir -p "$output_folder/$new_dir" + done + find . -not -path "./template.html" -type f | while read -r file_path + do + new_dir=$(dirname "$file_path") + new_dir=$(simplify_path "$new_dir") + new_file=$(basename "$file_path") + cp "$file_path" "$output_folder/$new_dir/$new_file" + done + + echo "done." } -function serve(){ - build - webfsd -Fd -r $outputfolder -f index.html -l - +function serve() { + build --local "${@}" + if check_dep busybox 2> /dev/null + then + echo "Hosting with busybox on 'http://127.0.0.1:8080/${start_path_http}' (stop with Ctrl-C) ..." + busybox httpd -f -p 127.0.0.1:8080 -h "$output_folder" + else + echo "WARNING: busybox (used for HTTP hosting) not found, falling back on the file-system." 1>&2 + echo "You may open the browser on 'file://${output_folder}/${start_path}'." + fi } -case $1 in - "init") init;; - "build") build;; - "serve") serve;; - *) - echo "Unknown command $1" - exit 1 - ;; +cmd=${1:-} +shift + +case $cmd in + -h|--help|help) + print_help + exit 0 + ;; + -v|--version|version) + print_version + exit 0 + ;; +esac + +check_deps + +case $cmd in + init) + init "${@}" + ;; + build) + build "${@}" + ;; + serve) + serve "${@}" + ;; + *) + echo "Unknown command '$cmd'" 1>&2 + print_help + exit 1 + ;; esac diff --git a/docs/.html/CNAME b/docs/.html/CNAME deleted file mode 100644 index a3d3d6b..0000000 --- a/docs/.html/CNAME +++ /dev/null @@ -1 +0,0 @@ -pdsite.org diff --git a/docs/.html/index.html b/docs/.html/index.html deleted file mode 100644 index 4880964..0000000 --- a/docs/.html/index.html +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - pdsite | Pandoc-backed static site generator - - - - - - - - -
-
-
-

pdsite

-

pdsite is a Pandoc-backed static site generator for Unix-like systems (including OSX, Linux, and BSD). It's comprised of a single shell script and has no dependencies on particular programming environments: it arose out of a desire for MkDocs-like functionality with broader input format support and without the Python dependencies.

-

Fully decoupled content and presentation

-

pdsite is built around the premise that content should be able to be kept completely seperate from presentation. Many site generators require both written content and visual presentation resources to be stored under a single path or Git repository: pdsite allows standalone file hierarchies (with content formatted in Markdown, Emacs org-mode, LaTex, etc) to be easily converted to linked HTML via an independently-specified theme. As such, switching HTML themes is just a matter of changing a line in a config file and rebuilding the site.

-

Minimal dependencies

-

pdsite is built on standard Unix tools for portability (apologies if you use Windows). Its few dependencies are widely-available as tiny precompiled binaries (except for Pandoc, which is widely-available as a moderately-sized precompiled binary).

-
-
-
- -