~charles/epbsd-browser

67dfc4c049202d90e980e5b09768dc471d4d6858 — Charles Daniels 1 year, 3 months ago
initial commit
6 files changed, 462 insertions(+), 0 deletions(-)

A .gitignore
A LICENSE
A README.md
A bin/build-browser
A bin/generate-md
A bin/ssg3
A  => .gitignore +2 -0
@@ 1,2 @@
html/
markdown/

A  => LICENSE +29 -0
@@ 1,29 @@
Copyright (c) 2019, Charles Daniels
All rights reserved.

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.


A  => README.md +4 -0
@@ 1,4 @@
# epbsd-browser

Unofficial [EPBSD](https://epbsd.org/) repository browser generator, inspired
by (but not derived from) [openports.se](http://openports.se/).

A  => bin/build-browser +13 -0
@@ 1,13 @@
#!/bin/sh

# Generate the EPBSD ports browser

cd "$(dirname "$0")"

rm -rf ../html
rm -rf ../markdown
mkdir ../markdown
mkdir ../html

./generate-md 'https://gitlab.com/epbsd/ports.git' ../markdown 'https://gitlab.com/epbsd/ports/blob/master/' 'https://gitlab.com/epbsd/ports/commit/'
./ssg3 ../markdown/ ../html/ "EPBSD Ports Browser" "https://example.com"

A  => bin/generate-md +153 -0
@@ 1,153 @@
#!/bin/sh

# Script to generate EPBSD browser site
#
# This script will generate files under $2/*/* in the same structure as
# the input ports tree ($1). Files are generated in markdown format for later
# processing by ssg.
#
# The "tree base" specifies the base URL for linking to files in the master
# branch of the EPBSD git repository.
#
# The "commit base" specifies the base URL for linking to commits in the EPBSD
# repository
#
# Usage:
#
#	$1 . . . repository URL
#	$2 . . . output directory
#	$3 . . . tree base
#	$4 . . . commit base

set -e
set -u

if [ $# -ne 4 ] ; then
	echo "$0 REPO_URL OUTPUT_DIR TREE_BASE COMMIT_BASE"
	exit 1
fi

REPO_URL="$1"
TREE_BASE="$3"
COMMIT_BASE="$4"

# get the fully qualified path to the output directory -- we use pwd instead of
# realpath since it's more portable
mkdir -p "$2"
cd "$2"
OUTPUT_DIR="$(pwd)"

# cd to the parent dir of this script
cd "$(dirname "$0")"

# pwd will give us the fully-qualified path to where we are installed
INSTALL_DIR="$(pwd)"

# dir to clone the EPBSD tree into
TEMP_DIR="$(mktemp -d)"

cd "$TEMP_DIR"

# fetch the EPBSD tree
git clone "$REPO_URL" ports
cd ports

get_make_variable () {
	# get a single variable ($1) from $port/Makefile

	# TODO: should use make -C show=$1 - how to set environment variables?
	perl -ne 'print if /^'"$1"'/' < "$port/Makefile" | \
		awk '{$1=$1}1' | \
		cut -d '=' -f 2-
}

gen_port_page () {
	# generate the output Markdown for a single port, specified by the
	# global $port
	#
	# Note that $port needs to have the format ./category/portname/

	# extract primary category and name
	category="$(echo "$port" | cut -d '/' -f 2)"
	name="$(echo "$port" | cut -d '/' -f 3)"

	# extract information from the Makefile
	comment="$(get_make_variable COMMENT)"
	categories="$(get_make_variable CATEGORIES)"
	homepage="$(get_make_variable HOMEPAGE)"
	maintainer="$(get_make_variable MAINTAINER)"

	# get package description
	if [ ! -e "$port/pkg/DESCR" ] ; then
		description="Missing DESCR file."
	else
		description="$(cat "$port/pkg/DESCR")"
	fi

	# create target directory
	page_dir="$OUTPUT_DIR/$category/$name"
	mkdir -p "$page_dir"

	page="$page_dir/index.md"

	# generate page contents
	echo "# [$category](../index.html)/$name - $comment" > "$page"
	echo "" >> "$page"
	echo "## Quick Links" >> "$page"
	echo "" >> "$page"

	# handle empty homepage
	if [ "$homepage" = "" ] ; then
		echo "* HOMEPAGE not set" >> "$page"
	else
		echo "* [$name's homepage]($homepage)" >> "$page"
	fi

	echo "* [ports/$category/$name on GitLab]($TREE_BASE/$category/$name)" >> "$page"
	echo "" >> "$page"
	echo "## Description"  >> "$page"
	echo "" >> "$page"
	echo "$description" >> "$page"
	echo "" >> "$page"
	echo "## Commit History" >> "$page"
	echo "" >> "$page"
	git log --format=format:'%h#%H#%an#%ai#%s' -- "$port" | \
		awk 'BEGIN {FS="#"} {printf("* [`%s`]('"$COMMIT_BASE"'/%s) by %s at %s: %s\n", $1, $2, $3, $4, $5)}' >> "$page"

	# add the page to the index
	echo "$name" >> "$OUTPUT_DIR/$category/index.list"

	# make sure the category has been added to the top-level index
	echo "$category" >> "$OUTPUT_DIR/index.list"

}

# clear existing index files
rm -f "$OUTPUT_DIR/index.list"
rm -f "$OUTPUT_DIR"/*/index.list

# generate pages for each port
for port in ./*/*/ ; do
	echo "$0: begin processing port $port"
	gen_port_page

done

# generate the top-level index
echo "# Index for ./\n" > "$OUTPUT_DIR/index.md"
sort < "$OUTPUT_DIR/index.list" | uniq | while read -r category ; do
	echo "* [$category](./$category/index.html)" >> "$OUTPUT_DIR/index.md"
done

# generate the index for each category
for category_dir in ./*/ ; do
	category="$(basename "$category_dir")"
	echo "$0: generating index for $category"
	echo "# Index for [.](../index.html)/$category/\n" > "$OUTPUT_DIR/$category/index.md"
	sort < "$OUTPUT_DIR/$category/index.list" | uniq | while read -r name ; do
		echo "* [$name](./$name/index.html)" >> "$OUTPUT_DIR/$category/index.md"
	done
done

cd "$OUTPUT_DIR"
rm -rf "$TEMP_DIR"

A  => bin/ssg3 +261 -0
@@ 1,261 @@
#!/bin/sh
#
# https://www.romanzolotarev.com/bin/ssg3
# Copyright 2018 Roman Zolotarev <hi@romanzolotarev.com>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
set -e

main() {
	test -n "$1" || usage
	test -n "$2" || usage
	test -n "$3" || usage
	test -n "$4" || usage
 	test -d "$1" || no_dir "$1"
 	test -d "$2" || no_dir "$2"

	src=$(readlink -f "$1")
	dst=$(readlink -f "$2")


	# files

	title="$3"

	head=$(
	wrap_file "$src/_rss.html"
	wrap_file "$src/_styles.css" '<style>' '</style>'
	wrap_file "$src/_scripts.js" '<script>' '</script>'
	)
	header=$(wrap_file "$src/_header.html")
	footer=$(wrap_file "$src/_footer.html")

	list_dirs "$src" |
	(cd "$src" && cpio -pdu "$dst")

	fs=$(
 	if test -f "$dst/.files"
	then list_affected_files "$src" "$dst/.files"
	else list_files "$1"
	fi
	)

	if test -n "$fs"
	then
		echo "$fs" | tee "$dst/.files"

		if echo "$fs" | grep -q '\.md$'
		then test -x "$(which lowdown)" || exit 3
		fi
		echo "$fs" | grep '\.md$' |
		render_md_files "$src" "$dst" \
		"$title" "$head" "$header" "$footer"

		echo "$fs" | grep '\.html$' |
		render_html_files "$src" "$dst" \
		"$title" "$head" "$header" "$footer"

		echo "$fs" | grep -Ev '\.md$|\.html$' |
		(cd "$src" && cpio -pu "$dst")
	fi

	printf '[ssg] ' >&2
	print_status 'file,' 'files,' "$fs" >&2


	# sitemap

	base_url="$4"
	date=$(date +%Y-%m-%d)
 	urls=$(list_pages "$src")

	if test -n "$urls"
	then
		echo "$urls" |
		render_urls "$base_url" "$date" |
		render_sitemap > "$dst/sitemap.xml"
	fi

	print_status 'url' 'urls' "$urls" >&2
	echo >&2
}


wrap_file() {
	if test -f "$1"
	then echo "$2$(cat "$1")$3"
	fi
}


print_status() {
	if test -n "$3"
	then echo "$3" | line_counter "$1" "$2"
	else printf 'no %s ' "$2"
	fi
}


line_counter() {
	wc -l |
	awk '{printf $1" "}($1=="1"){printf "'"$1"' "}($1>"1"){printf "'"$2"' "}'
}


usage() {
	echo "usage: ${0##*/} src dst title base_url" >&2
	exit 1
}


no_dir() {
	echo "${0##*/}: $1: No such directory" >&2
	exit 2
}


list_dirs() {
	cd "$1" &&
	find . -type d \
	! -name '.' ! -path '*/.*' ! -path '*/CVS/*' ! -path '*/_*'
}


list_files() {
	cd "$1" &&
	find . -type f \
	! -name '.' ! -path '*/.*' ! -path '*/CVS/*' ! -path '*/_*'
}


list_dependant_files () {
	cd "$1" &&
	find . -type f \
	! -name '.' ! -path '*/.*' ! -path '*/CVS/*' ! -path '*/_*' \
	\( -name '*.html' -o -name '*.md' -o -name '*.css' -o -name '*.js' \)
}


list_newer_files() {
	cd "$1" &&
	find . -type f \
	! -name '.' ! -path '*/.*' ! -path '*/CVS/*' \
	-newer "$2"
}


has_partials() {
	grep -qE '^./_.*\.html$|^./_.*\.js$|^./_.*\.css$'
}


list_affected_files() {
	fs=$(list_newer_files "$1" "$2")

	if echo "$fs" | has_partials
	then list_dependant_files "$1"
	else echo "$fs"
	fi
}


render_html_files() {
	while read -r f
	do render_html_file "$1" "$3" "$4" "$5" "$6" < "$1/$f" > "$2/$f"
	done
}


render_md_files() {
	while read -r f
	do
		lowdown \
		-D html-skiphtml \
		-D smarty \
		-d metadata \
		-d autolink < "$1/$f" |
		render_html_file "$1" "$3" "$4" "$5" "$6" \
		> "$2/${f%\.md}.html"
	done
}


render_html_file() {
	body=$(cat)
	src="$1"
	site_title="$2"
	head="$3"
	header="$4"
	footer="$5"

	echo "$body" | grep -iq '<html' &&
	echo "$body" && return

	title=$(echo "$body" | awk 'tolower($0)~/^<h1/{gsub(/<[^>]*>/,"",$0);print;exit}')
	echo '<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="generator" content="romanzolotarev.com/bin/ssg3">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="/favicon.png">
<title>'"$(test -n "$title" && echo "$title - ")$site_title"'</title>
'"$head"'
</head>
<body>
'"$header"'
'"$body"'
'"$footer"'
</body>
</html>'
}

list_pages() {
	cd "$1" && find . -type f ! -path '*/.*' ! -path '*/_*' \
	\( -name '*.html' -o -name '*.md' \) |
	sed 's#^./##;s#.md$#.html#;s#/index.html$#/#'
}


render_urls() {
	while read -r url
	do render_url "$1/$url" "$2"
	done
}


render_url() {
	url="$1"
	date="$2"
	echo '<url>
<loc>'"$url"'</loc>
<lastmod>'"$date"'</lastmod>
<priority>1.0</priority>
</url>'
}


render_sitemap() {
	echo '<?xml version="1.0" encoding="UTF-8"?>
<urlset
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
'"$(cat)"'
</urlset>'
}


main "$@"