squeeze

A static site generator that can put the toothpaste back in the tube.
git clone https://git.stjo.hn/squeeze
Log | Files | Refs | README | LICENSE

commit 51171e2b1f3ea041f7acb3b3c22541bec31706fc
Author: St John Karp <contact@stjo.hn>
Date:   Tue,  9 Jul 2019 05:58:25 -0500

Initial commit

First pass at a static site generating suite.

Diffstat:
Aentries.pl | 38++++++++++++++++++++++++++++++++++++++
Ahelpers.pl | 46++++++++++++++++++++++++++++++++++++++++++++++
Ahtml.pl | 230+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amarkdown.pl | 26++++++++++++++++++++++++++
Arss.pl | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atastic.sh | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 551 insertions(+), 0 deletions(-)

diff --git a/entries.pl b/entries.pl @@ -0,0 +1,37 @@ +:- include('helpers.pl'). +:- include('html.pl'). +:- include('markdown.pl'). + +parse_entry:- + read_file(user_input, HTML), + parse_html(HTML). + +parse_entry(Filename):- + open(Filename, read, Stream), + read_file(Stream, HTML), + close(Stream), + parse_html(HTML). + +parse_html(HTML):- + page(EntryCodes, Title, Subtitle, Date, HTML, []), + markdown(EntryCodes, Title, Subtitle, Date, MarkdownCodes, []), + atom_codes(Markdown, MarkdownCodes), + write(Markdown), + halt. + +generate_entry:- + read_file(user_input, Entry), + generate_html(Entry). + +generate_entry(Filename):- + open(Filename, read, Stream), + read_file(Stream, Entry), + close(Stream), + generate_html(Entry). + +generate_html(Markdown):- + markdown(EntryCodes, Title, Subtitle, Date, Markdown, []), + page(EntryCodes, Title, Subtitle, Date, HTMLCodes, []), + atom_codes(HTML, HTMLCodes), + write(HTML), + halt. +\ No newline at end of file diff --git a/helpers.pl b/helpers.pl @@ -0,0 +1,45 @@ +% Helpers + +read_file(Stream, []):- + at_end_of_stream(Stream). + +read_file(Stream, [Code|Rest]):- + \+ at_end_of_stream(Stream), + get_code(Stream, Code), + read_file(Stream, Rest). + + +take_last(_, [], []). + +take_last(Max, [First|Rest], Result):- + take_last(Max, Rest, ResultSoFar), + take_append(Max, First, ResultSoFar, Result). + +take_append(Max, _, ResultSoFar, ResultSoFar):- + length(ResultSoFar, Max). + +take_append(_, Item, ResultSoFar, [Item|ResultSoFar]). + +replace(_, _, [], []). + +replace(FindCodes, ReplaceCodes, Haystack, Result):- + append(FindCodes, HaystackMinusMatch, Haystack), + replace(FindCodes, ReplaceCodes, HaystackMinusMatch, ReplacedHaystackMinusMatch), + append(ReplaceCodes, ReplacedHaystackMinusMatch, Result). + +replace(FindCodes, ReplaceCodes, [Code|Haystack], [Code|Result]):- + replace(FindCodes, ReplaceCodes, Haystack, Result). + + +anything([]) --> []. + +anything([X|Rest]) --> [X], anything(Rest). + + +whitespace --> []. + +whitespace --> "\n", whitespace. + +whitespace --> "\t", whitespace. + +whitespace --> " ", whitespace. +\ No newline at end of file diff --git a/html.pl b/html.pl @@ -0,0 +1,229 @@ +page(Entry, Title, Subtitle, Date) --> + doctype, + whitespace, + html(Entry, Title, Subtitle, Date). + +html(Entry, Title, Subtitle, Date) --> + html_open, + whitespace, + head(Title), + whitespace, + body(Entry, Title, Subtitle, Date), + whitespace, + html_close. + +head(Title) --> + head_open, + whitespace, + title(Title), + whitespace, + meta, + whitespace, + styles, + whitespace, + rss, + whitespace, + head_close. + +body(Entry, Title, Subtitle, Date) --> + body_open, + whitespace, + header(Title), + whitespace, + article(Entry, Title, Subtitle, Date), + whitespace, + entry_utility, + whitespace, + footer, + whitespace, + body_close. + +header(Title) --> + header_open, + whitespace, + header_title(Title), + whitespace, + header_subtitle, + whitespace, + header_close. + +article(Entry, Title, Subtitle, Date) --> + article_open, + whitespace, + article_header(Title, Subtitle, Date), + whitespace, + div_entry_open, + whitespace, + anything(Entry), + whitespace, + div_entry_close, + whitespace, + article_close, + { [First|_] = Entry, char_code('<', First) }. + +article_header(null, null, null) --> []. + +article_header(Title, null, Date) --> + article_title(Title), + whitespace, + article_meta(Date). + +article_header(Title, Subtitle, Date) --> + article_title(Title), + whitespace, + article_subtitle(Subtitle), + whitespace, + article_meta(Date). + +footer --> + footer_open, + whitespace, + p_center_open, + whitespace, + license_link, + whitespace, + br, + whitespace, + license_text, + whitespace, + p_close, + whitespace, + footer_close. + +doctype --> "<!DOCTYPE html>". + +html_open --> "<html lang=\"en\">". + +head_open --> "<head>". + +meta --> "<meta charset=\"utf-8\" />". + +title(null) --> + "<title>", + site_title, + " | ", + site_subtitle, + "</title>". + +title(Title) --> + "<title>", + Title, + "</title>". + +title(_) --> + "<title>", + anything(_), + "</title>". + +styles --> + "<link rel=\"stylesheet\" href=\"", + site_url, + "/theme/css/styles.css\" />". + +rss --> + "<link rel=\"alternate\" type=\"application/rss+xml\" href=\"", + site_url, + "/feeds/rss.xml\" title=\"", + site_title, + " Latest Posts\" />". + +head_close --> "</head>". + +body_open --> "<body>". + +header_open --> "<header>". + +header_title(Title) --> + "<", + header_node(Title), + " id=\"blog-title\"><a href=\"", + site_url, + "\" title=\"", + site_title, + "\" rel=\"home\">", + site_title, + "</a></", + header_node(Title), + ">". + +header_node(null) --> "h1". + +header_node(_) --> "p". + +header_subtitle --> + "<p id=\"blog-description\">", + site_subtitle, + "</p>". + +header_close --> "</header>". + +article_open --> "<article>". + +article_open --> + "<article id=\"", + anything(_), + "\">". + +article_title(ArticleTitle) --> + "<h1 class=\"entry-title\">", + anything(ArticleTitle), + "</h1>". + +article_subtitle(ArticleSubtitle) --> + "<p class=\"entry-subtitle\">", + anything(ArticleSubtitle), + "</p>". + +article_meta(ArticleDate) --> + "<div class=\"entry-meta\">", + whitespace, + "<time datetime=\"", + anything(ArticleDate), + anything(_), + "\">", + anything(ArticleDate), + "</time>", + whitespace, + "</div><!-- .entry-meta -->". + +div_entry_open --> "<div class=\"entry-content\">". + +div_entry_close --> "</div><!-- .entry-content -->". + +article_close --> + "</article><!-- ", + anything(_), + " -->". + +entry_utility --> []. + +entry_utility --> + "<div class=\"entry-utility\">", + anything(_), + "</div><!-- #entry-utility -->". + +footer_open --> "<footer>". + +p_center_open --> "<p class=\"center\">". + +license_link --> + "<a rel=\"license\" href=\"http://creativecommons.org/licenses/by-nc-sa/3.0/\"><img alt=\"Creative Commons License\" style=\"border-width:0\" src=\"", + site_url, + "/theme/images/by-nc-sa_80x15.png\" /></a>". + +br --> "<br />". + +license_text --> + "Unless otherwise noted content on this website by <a href=\"mailto:", + email, + "\">", + name, + "</a> is licensed under a<br /><a rel=\"license\" href=\"http://creativecommons.org/licenses/by-nc-sa/3.0/\">Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License</a>.". + +p_close --> "</p>". + +footer_close --> "</footer>". + +body_close --> "</body>". + +html_close --> "</html>". +\ No newline at end of file diff --git a/markdown.pl b/markdown.pl @@ -0,0 +1,25 @@ +% Markdown definition + +markdown(Entry, Title, null, Date) --> + metadata("Title", Title), + "\n", + metadata("Date", Date), + "\n\n", + anything(Entry). + +markdown(Entry, Title, Subtitle, Date) --> + metadata("Title", Title), + "\n", + metadata("Subtitle", Subtitle), + "\n", + metadata("Date", Date), + "\n\n", + anything(Entry). + +markdown(Entry, null, null, null) --> + anything(Entry). + +metadata(Key, Value) --> + Key, + ": ", + anything(Value). +\ No newline at end of file diff --git a/rss.pl b/rss.pl @@ -0,0 +1,142 @@ +:- include('helpers.pl'). +:- include('markdown.pl'). + +generate_rss(BuildDate, Filenames):- + files_to_articles(Filenames, Articles), + sort(Articles, SortedArticles), + take_last(5, SortedArticles, TakenArticles), + rss(BuildDate, TakenArticles, RSSCodes, []), + atom_codes(RSS, RSSCodes), + write(RSS), + halt. + +files_to_articles([], []). + +files_to_articles([Filename|Filenames], [article(Date, Title, Link, Description)|Articles]):- + open(Filename, read, Stream), + read_file(Stream, Markdown), + close(Stream), + % Grab the link. + atom_codes(Filename, FilenameCodes), + site_url(URL, []), + append(_, "/source", StartPath), + append(StartPath, Path, FilenameCodes), + append(PathWithoutFile, "index.md", Path), + append(URL, PathWithoutFile, Link), + % Extract the title, entry, etc. from the Markdown. + markdown(Entry, Title, _, Date, Markdown, []), + % XML escape the description. + replace("&", "&amp;", Entry, EntryAmp), + replace("<", "&lt;", EntryAmp, EntryLT), + replace(">", "&gt;", EntryLT, Description), + files_to_articles(Filenames, Articles). + +rss(BuildDate, Articles) --> + rss_open, + "\n", + channel_meta(BuildDate), + "\n", + items(Articles), + "\n", + rss_close. + +rss_open --> + "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>", + "\n", + "<rss version=\"2.0\">", + "\n", + "<channel>". + +channel_meta(BuildDate) --> + "<title>", + site_title, + "</title>", + "\n", + "<description>", + site_subtitle, + "</description>", + "\n", + "<link>", + site_url, + "</link>", + "\n", + language, + "\n", + copyright, + "\n", + webmaster, + "\n", + last_build_date(BuildDate). + +title(Title) --> + "<title>", + Title, + "</title>". + +description(Description) --> + "<description>", + Description, + "</description>". + +link(Link) --> + "<link>", + Link, + "</link>". + +language --> + "<language>", + "en-US", + "</language>". + +copyright --> + "<copyright>", + "Licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.", + "</copyright>". + +webmaster --> + "<webMaster>", + email, + "</webMaster>". + +last_build_date(BuildDate) --> + "<lastBuildDate>", + BuildDate, + "</lastBuildDate>". + +items([]) --> []. + +items([First|Rest]) --> item(First), items(Rest). + +item(article(Date, Title, Link, Description)) --> + item_open, + "\n", + title(Title), + "\n", + link(Link), + "\n", + description(Description), + "\n", + author, + "\n", + pubdate(Date), + "\n", + item_close. + +item_open --> "<item>". + +author --> + "<author>", + name, + "</author>". + +pubdate(Date) --> + "<pubDate>", + Date, + "</pubDate>". + +item_close --> "</item>". + +rss_close --> + "</channel>", + "\n", + "</rss>". +\ No newline at end of file diff --git a/tastic.sh b/tastic.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +OUTPUT_DIR=output +SOURCE_DIR=source + +SITE_PATH=$2 + +if [ "$1" == "ungenerate" ] +then + # Create the directory structure. + rm -rf $SITE_PATH/$SOURCE_DIR/* + find $SITE_PATH/$OUTPUT_DIR -type d | \ + sed "s|^$SITE_PATH/$OUTPUT_DIR|$SITE_PATH/$SOURCE_DIR|" | \ + xargs -0 -d '\n' mkdir -p -- + + # Parse and create all the markdown files. + for file in `find $SITE_PATH/$OUTPUT_DIR -type f -name "*.html"`; do + NEW_PATH=`echo $file | sed "s|^$SITE_PATH/$OUTPUT_DIR|$SITE_PATH/$SOURCE_DIR|" | sed 's|.html$|.md|'` + cat $file | \ + swipl --traditional -q -l entries.pl -g "parse_entry." | \ + # Unsmarten the punctuation. + sed "s|&nbsp;| |g" | \ + sed "s|&#8216;|'|g" | \ + sed "s|&#8217;|'|g" | \ + sed "s|&#8220;|\"|g" | \ + sed "s|&#8221;|\"|g" \ + > $NEW_PATH + done + + # Copy anything else directly. + for file in `find $SITE_PATH/$OUTPUT_DIR -type f -not -name "*.html"`; do + NEW_PATH=`echo $file | sed "s|^$SITE_PATH/$OUTPUT_DIR|$SITE_PATH/$SOURCE_DIR|"` + cp $file $NEW_PATH + done +elif [ "$1" == "generate" ] +then + # Create the directory structure. + rm -rf $SITE_PATH/$OUTPUT_DIR/* + find $SITE_PATH/$SOURCE_DIR -type d | \ + sed "s|^$SITE_PATH/$SOURCE_DIR|$SITE_PATH/$OUTPUT_DIR|" | \ + xargs -0 -d '\n' mkdir -p -- + + # Parse and create all the HTML files. + for file in `find $SITE_PATH/$SOURCE_DIR -type f -name "*.md"`; do + NEW_PATH=`echo $file | sed "s|^$SITE_PATH/$SOURCE_DIR|$SITE_PATH/$OUTPUT_DIR|" | sed 's|.md$|.html|'` + cat $file | \ + swipl --traditional -q -l entries.pl -g "consult('$SITE_PATH/site.pl'), generate_entry." | \ + tidy -quiet --indent auto --indent-with-tabs yes --wrap 0 | \ + ~/.local/bin/smartypants \ + > $NEW_PATH + done + + # Copy anything else directly. + for file in `find $SITE_PATH/$SOURCE_DIR -type f -not -name "*.md"`; do + NEW_PATH=`echo $file | sed "s|^$SITE_PATH/$SOURCE_DIR|$SITE_PATH/$OUTPUT_DIR|"` + cp $file $NEW_PATH + done + + # Generate the RSS feed. + mkdir -p $SITE_PATH/$OUTPUT_DIR/feeds + ARTICLES=`grep -Rl --include=\*.md "^Date: " $SITE_PATH/$SOURCE_DIR | paste -sd ',' - | sed "s|,|','|g"` + BUILD_DATE=`date +"%Y-%m-%d %T"` + swipl --traditional -q -l rss.pl -g "consult('$SITE_PATH/site.pl'), generate_rss(\"$BUILD_DATE\", ['$ARTICLES'])." \ + > $SITE_PATH/$OUTPUT_DIR/feeds/rss.xml +else + echo "Invalid argument." + exit 1 +fi