📦 NEW: Path to 0.1

This commit is contained in:
Josemar Lohn 2020-12-19 19:43:21 +00:00
parent 554ad1615b
commit adfc0fdde1
46 changed files with 2563 additions and 1284 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
coverage
build

5
.shellspec Normal file
View File

@ -0,0 +1,5 @@
--require spec_helper
--kcov-options "--include-path=. --path-strip-level=1"
--kcov-options "--exclude-pattern=/.shellspec,/spec/,/coverage/,/report/,/build/,/.git/"
--kcov-options "--include-pattern=.sh"
--shell "/bin/bash"

75
CHANGELOG.md Normal file
View File

@ -0,0 +1,75 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Three awk scripts to create a version of page for web/gemini/gopher without the need of any external tool
### Changed
- The documentation is now split in many files (Changelog, License, Contributing...)
- The funcions is now split in many files for easy of editing
- The Makefile script concatenate all the functions in a single file
### Deprecated
### Removed
### Fixed
### Security
- The scripts now are shellcheck validated
- Unit tests based on shellspec
# Old Changelog
- 2.8 Bugfixes<br/>
Slavic language support thanks to Tomasz Jadowski<br/>
Removed the now defunct Twitter JSON API share count<br/>
Support for static, not managed by bashblog html files<br/>
- 2.7 Store post date on a comment in the html file (#96).<br/>
On rebuild, the post date will be synchronised between comment date and file date, with precedence for comment date.
- 2.6 Support for multiple authors, use a different `.config` for each one
- 2.5 Massive code cleanup by Martijn Dekker<br/>
'tags' command<br/>
The word 'posts' in the tag list (both website and command) now has a singular form, check out `template_tags_posts_singular`
- 2.4 Added Twitter summaries metadata for posts (#36)
- 2.3.3 Removed big comment header.<br/>
Added option to display tags for cut articles on index pages (#61)<br/>
Cleaned up "all posts" page (#57)
- 2.3.2 Option to use topsy instead of twitter for references
- 2.3.1 Cookieless Twitter option
- 2.3 Intelligent tag rebuilding and Markdown by default
- 2.2 Flexible post title -> filename conversion
- 2.1 Support for tags/categories.<br/>
'delete' command
- 2.0.3 Support for other analytics code, via external file
- 2.0.2 Fixed bug when $body_begin_file was empty.<br/>
Added extra line in the footer linking to the github project
- 2.0.1 Allow personalized header/footer files
- 2.0 Added Markdown support.<br/>
Fully support BSD date
- 1.6.4 Fixed bug in localized dates
- 1.6.3 Now supporting BSD date
- 1.6.2 Simplified some functions and variables to avoid duplicated information
- 1.6.1 'date' fix when hours are 1 digit.
- 1.6.0 Disqus comments. External configuration file. Check of 'date' command version.
- 1.5.1 Misc bugfixes and parameter checks
- 1.5 Đurađ Radojičić (djura-san) refactored some code and added flexibility and i18n
- 1.4.2 Now issues are handled at Github
- 1.4.1 Some code refactoring
- 1.4 Using twitter for comments, improved 'rebuild' command
- 1.3 'edit' command
- 1.2.2 Feedburner support
- 1.2.1 Fixed the timestamps bug
- 1.2 'list' command
- 1.1 Draft and preview support
- 1.0 Read http://is.gd/Bkdoru

15
LICENSE Normal file
View File

@ -0,0 +1,15 @@
License
-------
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

View File

@ -2,11 +2,27 @@ PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
install:
$(info Installing the executable to $(BINDIR))
@install -Dm755 bb.sh $(BINDIR)/bb
$(info Generating the main executable)
@mkdir -p build
@awk '1{ if (NR > 1) { print x }};/### BEGIN SOURCEFILES/{exit};{x=$$0}' tildelog.sh > ./build/tildelog
@grep -hv '^[[:space:]]*declare\|^#!' ./lib/*.sh >> ./build/tildelog
@awk 'p;/### END SOURCEFILES/{p=1}' tildelog.sh >> ./build/tildelog
$(info Installing the executables to $(BINDIR))
@install -Dm755 ./build/tildelog $(BINDIR)/tildelog
@install -Dm755 md2html.awk $(BINDIR)/md2html.awk
@install -Dm755 md2gemini.awk $(BINDIR)/md2gemini.awk
@install -Dm755 md2gopher.awk $(BINDIR)/md2gopher.awk
uninstall:
$(info Removing the executable from $(BINDIR))
@rm -f $(BINDIR)/bb
@rm -f $(BINDIR)/tildelog
@rm -f $(BINDIR)/md2html.awk
@rm -f $(BINDIR)/md2gemini.awk
@rm -f $(BINDIR)/md2gopher.awk
.PHONY: install uninstall
test:
$(info Running shellspec tests)
@rm -rf ./coverage
@shellspec --kcov
.PHONY: install uninstall test

View File

@ -1,4 +1,4 @@
bashblog
Tildelog
========
This is the source for the customized bashblog used on [tilde.team](https://tilde.team). We have a [wiki page](https://tilde.team/wiki/?page=tildeblogs) with more details on our specific setup.
@ -117,86 +117,3 @@ Detailed features
Read the Changelog section for more updates or [check out the news on my blog](http://cfenollosa.com/blog/tag_bashblog.html)
Contributing
------------
Bashblog started at 500 SLOC and it now has hit the 1000 SLOC barrier.
If we want to keep the code minimal and understandable, we need to make the difficult effort to restrain ourselves
from adding too many features.
All bugfixes are welcome, but brand new features need to be strongly justified to get into the main tree.
Every new request will be honestly and civilly discussed on the comments.
As a guideline, pull requests should:
- Fix a use case for some people (e.g. internationalization)
- Add a use case which is arguably very common (e.g. disqus integration for comments)
- Be very small when possible (a couple lines of code)
- Don't require a significant rewrite of the code (Don't break `create_html_file()` or `write_entry()`, etc)
- It must work on Linux, BSD and Mac. Beware of using GNU coreutils with non-POSIX flags (i.e. `date` or `grep`)
- Follow the UNIX philosophy: do one thing and do it well, rely on third party software for external features, etc
- **Always** keep backwards compatibility when using the default configuration
Changelog
---------
- 2.8 Bugfixes<br/>
Slavic language support thanks to Tomasz Jadowski<br/>
Removed the now defunct Twitter JSON API share count<br/>
Support for static, not managed by bashblog html files<br/>
- 2.7 Store post date on a comment in the html file (#96).<br/>
On rebuild, the post date will be synchronised between comment date and file date, with precedence for comment date.
- 2.6 Support for multiple authors, use a different `.config` for each one
- 2.5 Massive code cleanup by Martijn Dekker<br/>
'tags' command<br/>
The word 'posts' in the tag list (both website and command) now has a singular form, check out `template_tags_posts_singular`
- 2.4 Added Twitter summaries metadata for posts (#36)
- 2.3.3 Removed big comment header.<br/>
Added option to display tags for cut articles on index pages (#61)<br/>
Cleaned up "all posts" page (#57)
- 2.3.2 Option to use topsy instead of twitter for references
- 2.3.1 Cookieless Twitter option
- 2.3 Intelligent tag rebuilding and Markdown by default
- 2.2 Flexible post title -> filename conversion
- 2.1 Support for tags/categories.<br/>
'delete' command
- 2.0.3 Support for other analytics code, via external file
- 2.0.2 Fixed bug when $body_begin_file was empty.<br/>
Added extra line in the footer linking to the github project
- 2.0.1 Allow personalized header/footer files
- 2.0 Added Markdown support.<br/>
Fully support BSD date
- 1.6.4 Fixed bug in localized dates
- 1.6.3 Now supporting BSD date
- 1.6.2 Simplified some functions and variables to avoid duplicated information
- 1.6.1 'date' fix when hours are 1 digit.
- 1.6.0 Disqus comments. External configuration file. Check of 'date' command version.
- 1.5.1 Misc bugfixes and parameter checks
- 1.5 Đurađ Radojičić (djura-san) refactored some code and added flexibility and i18n
- 1.4.2 Now issues are handled at Github
- 1.4.1 Some code refactoring
- 1.4 Using twitter for comments, improved 'rebuild' command
- 1.3 'edit' command
- 1.2.2 Feedburner support
- 1.2.1 Fixed the timestamps bug
- 1.2 'list' command
- 1.1 Draft and preview support
- 1.0 Read http://is.gd/Bkdoru
License
-------
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

1196
bb.sh

File diff suppressed because it is too large Load Diff

50
lib/all_posts.sh Normal file
View File

@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Create an index page with all the posts
all_posts() {
declare archive_index
declare template_archive_index_page
declare template_archive_title
declare date_allposts_header
declare date_format
declare date_locale
declare index_file
declare global_title
declare global_author
echo -n "Creating an index page with all the posts "
contentfile=$archive_index.$RANDOM
while [[ -f $contentfile ]]; do
contentfile=$archive_index.$RANDOM
done
{
echo "<h3>$template_archive_title</h3>"
prev_month=""
while IFS='' read -r i; do
is_boilerplate_file "$i" && continue
echo -n "." 1>&3
# Month headers
month=$(LC_ALL=$date_locale date -r "$i" +"$date_allposts_header")
if [[ $month != "$prev_month" ]]; then
[[ -n $prev_month ]] && echo "</ul>" # Don't close ul before first header
echo "<h4 class='allposts_header'>$month</h4>"
echo "<ul>"
prev_month=$month
fi
# Title
title=$(get_post_title "$i")
echo -n "<li><a href=\"$i\">$title</a> &mdash;"
# Date
date=$(LC_ALL=$date_locale date -r "$i" +"$date_format")
echo " $date</li>"
done < <(ls -t ./*.html)
echo "" 1>&3
echo "</ul>"
echo "<div id=\"all_posts\"><a href=\"./$index_file\">$template_archive_index_page</a></div>"
} 3>&1 >"$contentfile"
create_html_page "$contentfile" "$archive_index.tmp" yes "$global_title &mdash; $template_archive_title" "$global_author"
mv "$archive_index.tmp" "$archive_index"
chmod 644 "$archive_index"
rm "$contentfile"
}

47
lib/all_tags.sh Normal file
View File

@ -0,0 +1,47 @@
#!/usr/bin/env bash
# Create an index page with all the tags
all_tags() {
declare tags_index
declare template_tags_posts
declare template_tags_posts_singular
declare template_tags_posts_2_4
declare template_tags_title
declare prefix_tags
declare index_file
declare template_archive_index_page
declare global_author
declare global_title
echo -n "Creating an index page with all the tags "
contentfile=$tags_index.$RANDOM
while [[ -f $contentfile ]]; do
contentfile=$tags_index.$RANDOM
done
{
echo "<h3>$template_tags_title</h3>"
echo "<ul>"
# shellcheck disable=SC2231 # Intended splitting of $prefix_tags
for i in $prefix_tags*.html; do
[[ -f "$i" ]] || break
echo -n "." 1>&3
nposts=$(grep -c "<\!-- text begin -->" "$i")
tagname=${i#"$prefix_tags"}
tagname=${tagname%.html}
case $nposts in
1) word=$template_tags_posts_singular;;
2|3|4) word=$template_tags_posts_2_4;;
*) word=$template_tags_posts;;
esac
echo "<li><a href=\"$i\">$tagname</a> &mdash; $nposts $word</li>"
done
echo "" 1>&3
echo "</ul>"
echo "<div id=\"all_posts\"><a href=\"./$index_file\">$template_archive_index_page</a></div>"
} 3>&1 > "$contentfile"
create_html_page "$contentfile" "$tags_index.tmp" yes "$global_title &mdash; $template_tags_title" "$global_author"
mv "$tags_index.tmp" "$tags_index"
chmod 644 "$tags_index"
rm "$contentfile"
}

48
lib/create_css.sh Normal file
View File

@ -0,0 +1,48 @@
#!/usr/bin/env bash
# Create the css file from scratch
create_css() {
# To avoid overwriting manual changes. However it is recommended that
# this function is modified if the user changes the blog.css file
(( ${#css_include[@]} > 0 )) && return || css_include=('main.css' 'blog.css')
if [[ ! -f blog.css ]]; then
# blog.css directives will be loaded after main.css and thus will prevail
echo '#title{font-size: x-large;}
a.ablack{color:black !important;}
li{margin-bottom:8px;}
ul,ol{margin-left:24px;margin-right:24px;}
#all_posts{margin-top:24px;text-align:center;}
.subtitle{font-size:small;margin:12px 0px;}
.content p{margin-left:24px;margin-right:24px;}
h1{margin-bottom:12px !important;}
#description{font-size:large;margin-bottom:12px;}
h3{margin-top:42px;margin-bottom:8px;}
h4{margin-left:24px;margin-right:24px;}
img{max-width:100%;}
#twitter{line-height:20px;vertical-align:top;text-align:right;font-style:italic;color:#333;margin-top:24px;font-size:14px;}' > blog.css
fi
# If there is a style.css from the parent page (i.e. some landing page)
# then use it. This directive is here for compatibility with my own
# home page. Feel free to edit it out, though it doesn't hurt
if [[ -f ../style.css ]] && [[ ! -f main.css ]]; then
ln -s "../style.css" "main.css"
elif [[ ! -f main.css ]]; then
echo 'body{font-family:Georgia,"Times New Roman",Times,serif;margin:0;padding:0;background-color:#F3F3F3;}
#divbodyholder{padding:5px;background-color:#DDD;width:100%;max-width:874px;margin:24px auto;}
#divbody{border:solid 1px #ccc;background-color:#fff;padding:0px 48px 24px 48px;top:0;}
.headerholder{background-color:#f9f9f9;border-top:solid 1px #ccc;border-left:solid 1px #ccc;border-right:solid 1px #ccc;}
.header{width:100%;max-width:800px;margin:0px auto;padding-top:24px;padding-bottom:8px;}
.content{margin-bottom:5%;}
.nomargin{margin:0;}
.description{margin-top:10px;border-top:solid 1px #666;padding:10px 0;}
h3{font-size:20pt;width:100%;font-weight:bold;margin-top:32px;margin-bottom:0;}
.clear{clear:both;}
#footer{padding-top:10px;border-top:solid 1px #666;color:#333333;text-align:center;font-size:small;font-family:"Courier New","Courier",monospace;}
a{text-decoration:none;color:#003366 !important;}
a:visited{text-decoration:none;color:#336699 !important;}
blockquote{background-color:#f9f9f9;border-left:solid 4px #e9e9e9;margin-left:12px;padding:12px 12px 12px 24px;}
blockquote img{margin:12px 0px;}
blockquote iframe{margin:12px 0px;}' > main.css
fi
}

91
lib/create_html_page.sh Normal file
View File

@ -0,0 +1,91 @@
#!/usr/bin/env bash
# Adds all the bells and whistles to format the html page
# Every blog post is marked with a <!-- entry begin --> and <!-- entry end -->
# which is parsed afterwards in the other functions. There is also a marker
# <!-- text begin --> to determine just the beginning of the text body of the post
#
# $1 a file with the body of the content
# $2 the output file
# $3 "yes" if we want to generate the index.html,
# "no" to insert new blog posts
# $4 title for the html header
# $5 original blog timestamp
# $6 post author
create_html_page() {
declare body_begin_file
declare date_inpost
declare date_locale
declare date_format
declare date_format_timestamp
declare global_url
declare body_end_file
content=$1
filename=$2
index=$3
title=$4
timestamp=$5
author=$6
# Create the actual blog post
# html, head
{
cat ".header.html"
echo "<title>$title</title>"
twitter_card "$content" "$title"
echo "</head><body>"
# stuff to add before the actual body content
[[ -n $body_begin_file ]] && cat "$body_begin_file"
# body divs
echo '<div id="divbodyholder">'
echo '<div class="headerholder"><div class="header">'
# blog title
echo '<div id="title">'
cat .title.html
echo '</div></div></div>' # title, header, headerholder
echo '<div id="divbody"><div class="content">'
file_url=${filename#./}
file_url=${file_url%.rebuilt} # Get the correct URL when rebuilding
# one blog entry
if [[ $index == no ]]; then
echo '<!-- entry begin -->' # marks the beginning of the whole post
echo "<h3><a class=\"ablack\" href=\"$file_url\">"
# remove possible <p>'s on the title because of markdown conversion
title=${title//<p>/}
title=${title//<\/p>/}
echo "$title"
echo '</a></h3>'
if [[ -z $timestamp ]]; then
echo "<!-- $date_inpost: #$(LC_ALL=$date_locale date +"$date_format_timestamp")# -->"
else
echo "<!-- $date_inpost: #$(LC_ALL=$date_locale date +"$date_format_timestamp" --date="$timestamp")# -->"
fi
if [[ -z $timestamp ]]; then
echo -n "<div class=\"subtitle\">$(LC_ALL=$date_locale date +"$date_format")"
else
echo -n "<div class=\"subtitle\">$(LC_ALL=$date_locale date +"$date_format" --date="$timestamp")"
fi
[[ -n $author ]] && echo -e " &mdash; \n$author"
echo "</div>"
echo '<!-- text begin -->' # This marks the text body, after the title, date...
fi
cat "$content" # Actual content
if [[ $index == no ]]; then
echo -e '\n<!-- text end -->'
twitter "$global_url/$file_url"
echo '<!-- entry end -->' # absolute end of the post
fi
echo '</div>' # content
# page footer
cat .footer.html
# close divs
echo '</div></div>' # divbody and divbodyholder
[[ -n $body_end_file ]] && cat "$body_end_file"
echo '</body></html>'
} > "$filename"
}

51
lib/create_includes.sh Normal file
View File

@ -0,0 +1,51 @@
#!/usr/bin/env bash
# generate headers, footers, etc
create_includes() {
declare global_author
declare global_author_url
declare global_description
declare global_email
declare global_feedburner
declare global_license
declare global_title
declare global_url
declare index_file
declare header_file
declare css_include
declare template_subscribe_browser_button
declare blog_feed
declare footer_file
{
echo "<h1 class=\"nomargin\"><a class=\"ablack\" href=\"$global_url/$index_file\">$global_title</a></h1>"
echo "<div id=\"description\">$global_description</div>"
} > ".title.html"
if [[ -f $header_file ]]; then
cp "$header_file" .header.html
else
{
echo '<!DOCTYPE html>'
echo '<html lang="en"><head>'
echo '<meta charset="UTF-8">'
echo '<meta name="viewport" content="width=device-width, initial-scale=1.0">'
printf '<link rel="stylesheet" href="%s" type="text/css">\n' "${css_include[@]}"
if [[ -z $global_feedburner ]]; then
echo "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"$template_subscribe_browser_button\" href=\"$blog_feed\">"
else
echo "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"$template_subscribe_browser_button\" href=\"$global_feedburner\">"
fi
} > ".header.html"
fi
if [[ -f $footer_file ]]; then
cp "$footer_file" .footer.html
else
{
protected_mail=${global_email//@/&#64;}
protected_mail=${protected_mail//./&#46;}
echo "<div id=\"footer\">$global_license <a href=\"$global_author_url\">$global_author</a> &mdash; <a href=\"mailto:$protected_mail\">$protected_mail</a><br>"
echo 'generated with <a href="https://tildegit.org/team/bashblog">bashblog</a>, a single bash script to easily create blogs like this one</div>'
} >> ".footer.html"
fi
}

View File

@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Detects if GNU date is installed
date_version_detect() {
declare date_format_full
if ! stat -c"%U" /dev/null >/dev/null 2>&1 ; then
# BSD environment
if command -v gdate >/dev/null 2>&1 ; then
date() {
gdate "$@"
}
else
date() {
if [[ $1 == -r ]]; then
# Fall back to using stat for 'date -r'
format=${3//+/}
stat -f "%Sm" -t "$format" "$2"
elif [[ $2 == --date* ]]; then
# convert between dates using BSD date syntax
command date -j -f "$date_format_full" "${2#--date=}" "$1"
else
# acceptable format for BSD date
command date -j "$@"
fi
}
fi
fi
}

6
lib/delete_includes.sh Normal file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
# Delete the temporarily generated include files
delete_includes() {
rm ".title.html" ".footer.html" ".header.html"
}

83
lib/do_main.sh Normal file
View File

@ -0,0 +1,83 @@
#!/usr/bin/env bash
# Main function
# Encapsulated on its own function for readability purposes
#
# $1 command to run
# $2 file name of a draft to continue editing (optional)
declare global_config
do_main() {
# make sure we're in the right directory
if [ "$(pwd)" != "$HOME/public_html/blog" ]; then
echo "You're not in your blog directory. Moving you there now"
mkdir -p "$HOME/public_html/blog"
cd "$HOME/public_html/blog" || exit 1
fi
# Detect if using BSD date or GNU date
date_version_detect
# Load default configuration, then override settings with the config file
global_variables
# shellcheck disable=SC1090 # variable config file
[[ -f "$global_config" ]] && source "$global_config" &> /dev/null
global_variables_check
# Check for $EDITOR
[[ -z $EDITOR ]] &&
echo "Please set your \$EDITOR environment variable. For example, to use nano, add the line 'export EDITOR=nano' to your \$HOME/.bashrc file" && exit
# Check for validity of argument
[[ $1 != "reset" && $1 != "post" && $1 != "rebuild" && $1 != "list" && $1 != "edit" && $1 != "delete" && $1 != "tags" ]] &&
usage && return
[[ $1 == list ]] &&
list_posts && return
[[ $1 == tags ]] &&
list_tags "$@" && return
if [[ $1 == edit ]]; then
if (($# < 2)) || [[ ! -f ${!#} ]]; then
echo "Please enter a valid .md or .html file to edit"
exit
fi
fi
# Test for existing html files
if ls ./*.html &> /dev/null; then
# We're going to back up just in case
tar -c -z -f ".backup.tar.gz" -- *.html &&
chmod 600 ".backup.tar.gz"
elif [[ $1 == rebuild ]]; then
echo "Can't find any html files, nothing to rebuild"
return
fi
# Keep first backup of this day containing yesterday's version of the blog
[[ ! -f .yesterday.tar.gz || $(date -r .yesterday.tar.gz +'%d') != "$(date +'%d')" ]] &&
cp .backup.tar.gz .yesterday.tar.gz &> /dev/null
[[ $1 == reset ]] &&
reset && return
create_css
create_includes
[[ $1 == post ]] && write_entry "$@"
[[ $1 == rebuild ]] && rebuild_all_entries && rebuild_tags
[[ $1 == delete ]] && rm "$2" &> /dev/null && rebuild_tags
if [[ $1 == edit ]]; then
if [[ $2 == -n ]]; then
edit "$3"
elif [[ $2 == -f ]]; then
edit "$3" full
else
edit "$2" keep
fi
fi
rebuild_index
all_posts
all_tags
make_rss
make_gophermap
make_gemini
delete_includes
}

70
lib/edit.sh Normal file
View File

@ -0,0 +1,70 @@
#!/usr/bin/env bash
# Edit an existing, published .html file while keeping its original timestamp
# Please note that this function does not automatically republish anything, as
# it is usually called from 'main'.
#
# Note that it edits HTML file, even if you wrote the post as markdown originally
# Note that if you edit title then filename might also change
#
# $1 the file to edit
# $2 (optional) edit mode:
# "keep" to keep old filename
# "full" to edit full HTML, and not only text part (keeps old filename)
# leave empty for default behavior (edit only text part and change name)
edit() {
declare date_format_full
declare date_format_timestamp
declare template_tags_line_header
declare prefix_tags
[[ ! -f "${1%%.*}.html" ]] && \
printf "Can't edit post \"%s.html\", did you mean to use \"bb.sh post <draft_file>\"?\\n" "${1%%.*}" && exit 1
# Original post timestamp
edit_timestamp=$(LC_ALL=C date -r "${1%%.*}.html" +"$date_format_full" )
touch_timestamp=$(LC_ALL=C date -r "${1%%.*}.html" +"$date_format_timestamp")
tags_before=$(tags_in_post "${1%%.*}.html")
if [[ $2 == full ]]; then
$EDITOR "$1"
filename=$1
else
if [[ ${1##*.} == md ]]; then
if test_markdown; then
echo "Markdown is not working, please edit HTML file directly."
exit
fi
# editing markdown file
$EDITOR "$1"
TMPFILE=$(mrkdwn "$1")
filename=${1%%.*}.html
else
# Create the content file
TMPFILE=$(basename "$1").$RANDOM.html
# Title
get_post_title "$1" > "$TMPFILE"
# Post text with plaintext tags
get_html_file_content 'text' 'text' <"$1" | sed "/^<p>$template_tags_line_header/s|<a href='$prefix_tags\([^']*\).html'>\\1</a>|\\1|g" >> "$TMPFILE"
$EDITOR "$TMPFILE"
filename=$1
fi
rm "$filename"
if [[ $2 == keep ]]; then
parse_file "$TMPFILE" "$edit_timestamp" "$filename"
else
parse_file "$TMPFILE" "$edit_timestamp" # this command sets $filename as the html processed file
[[ ${1##*.} == md ]] && mv "$1" "${filename%%.*}.md" 2>/dev/null
fi
rm "$TMPFILE"
fi
touch -t "$touch_timestamp" "$filename"
touch -t "$touch_timestamp" "$1"
chmod 644 "$filename"
echo "Posted $filename"
tags_after=$(tags_in_post "$filename")
relevant_tags=$(echo "$tags_before $tags_after" | tr ',' ' ' | tr ' ' '\n' | sort -u | tr '\n' ' ')
if [[ -n $relevant_tags ]]; then
# shellcheck disable=SC2086 # Intended splitting of $relevant_tags
relevant_posts="$(posts_with_tags $relevant_tags) $filename"
rebuild_tags "$relevant_posts" "$relevant_tags"
fi
}

View File

@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Reads HTML file from stdin, prints its content to stdout
# $1 where to start ("text" or "entry")
# $2 where to stop ("text" or "entry")
# $3 "cut" to remove text from <hr /> to <!-- text end -->
# note that this does not remove <hr /> line itself,
# so you can see if text was cut or not
get_html_file_content() {
declare cut_line cut_tags
declare template_tags_line_header
awk "/<!-- $1 begin -->/, /<!-- $2 end -->/{
if (!/<!-- $1 begin -->/ && !/<!-- $2 end -->/) print
if (\"$3\" == \"cut\" && /$cut_line/){
if (\"$2\" == \"text\") exit # no need to read further
while (getline > 0 && !/<!-- text end -->/) {
if (\"$cut_tags\" == \"no\" && /^<p>$template_tags_line_header/ ) print
}
}
}"
}

8
lib/get_post_author.sh Normal file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
# Return the post author
#
# $1 the html file
get_post_author() {
awk '/<div class="subtitle">.+/, /<!-- text begin -->/{if (!/<div class="subtitle">.+/ && !/<!-- text begin -->/) print}' "$1" | sed 's/<\/div>//g'
}

8
lib/get_post_title.sh Normal file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
# Return the post title
#
# $1 the html file
get_post_title() {
awk '/<h3><a class="ablack" href=".+">/, /<\/a><\/h3>/{if (!/<h3><a class="ablack" href=".+">/ && !/<\/a><\/h3>/) print}' "$1"
}

165
lib/global_variables.sh Normal file
View File

@ -0,0 +1,165 @@
#!/usr/bin/env bash
# shellcheck disable=SC2034
# Global variables
# It is recommended to perform a 'rebuild' after changing any of this in the code
# Config file. Any settings "key=value" written there will override the
# global_variables defaults. Useful to avoid editing bb.sh and having to deal
# with merges in VCS
global_config=".tildelog"
# This function will load all the variables defined here. They might be overridden
# by the 'global_config' file contents
global_variables() {
global_software_name="tildelog"
global_software_version="0.1"
# Blog title
global_title="my tildelog"
# The typical subtitle for each blog
global_description="a blog about tildes"
# The server base domain
global_domain="tilde.team"
# The public base URL for this blog
global_url="https://${global_domain}/~$USER/blog"
# Your name
global_author="~$USER"
# You can use twitter or facebook or anything for global_author_url
global_author_url="https://${global_domain}/~$USER/"
# Your email
global_email="$USER@${global_domain}"
# CC by-nc-nd is a good starting point, you can change this to "&copy;" for Copyright
global_license="CC by-nc-nd"
# Leave this empty (i.e. "") if you don't want to use feedburner,
# or change it to your own URL
global_feedburner=""
# Change this to your username if you want to use twitter for comments
global_twitter_username=""
# Set this to false for a Twitter button with share count. The cookieless version
# is just a link.
global_twitter_cookieless="true"
# Blog generated files
# index page of blog (it is usually good to use "index.html" here)
index_file="index.html"
number_of_index_articles="10"
# global archive
archive_index="all_posts.html"
tags_index="all_tags.html"
# ignore gophermap file
gophermap="gophermap"
# ignore gemini generation script and gemini index
gemini_index="index.gmi"
# Non blogpost files. Bashblog will ignore these. Useful for static pages and custom content
# Add them as a bash array, e.g. non_blogpost_files=("news.html" "test.html")
# TODO: Dash have no support for arrays. Get rid of it!
non_blogpost_files=""
# feed file (rss in this case)
blog_feed="feed.rss"
number_of_feed_articles="50"
# "cut" blog entry when putting it to index page. Leave blank for full articles in front page
# i.e. include only up to first '<hr>', or '----' in markdown
cut_do="cut"
# When cutting, cut also tags? If "no", tags will appear in index page for cut articles
cut_tags="yes"
# Regexp matching the HTML line where to do the cut
# note that slash is regexp separator so you need to prepend it with backslash
cut_line='<hr ?\/?>'
# save markdown file when posting with "bb post -m". Leave blank to discard it.
save_markdown="yes"
# prefix for tags/categories files
# please make sure that no other html file starts with this prefix
prefix_tags="tag_"
# personalized header and footer (only if you know what you're doing)
# DO NOT name them .header.html, .footer.html or they will be overwritten
# leave blank to generate them, recommended
header_file=""
footer_file=""
# extra content to add just after we open the <body> tag
# and before the actual blog content
body_begin_file=""
# extra content to add just before we cloese <body tag (just before
# </body>)
body_end_file=""
# CSS files to include on every page, f.ex. css_include=('main.css' 'blog.css')
# leave empty to use generated
# TODO: Dash have no support for arrays. Get rid of it!
css_include=""
# HTML files to exclude from index, f.ex. post_exclude=('imprint.html 'aboutme.html')
# TODO: Dash have no support for arrays. Get rid of it!
html_exclude=""
# Localization and i18n
# "Comments?" (used in twitter link after every post)
template_comments="comments?"
# "Read more..." (link under cut article on index page)
template_read_more="read more..."
# "View more posts" (used on bottom of index page as link to archive)
template_archive="archive"
# "All posts" (title of archive page)
template_archive_title="all posts"
# "All tags"
template_tags_title="all tags"
# "posts" (on "All tags" page, text at the end of each tag line, like "2. Music - 15 posts")
template_tags_posts="posts"
template_tags_posts_2_4="posts" # Some slavic languages use a different plural form for 2-4 items
template_tags_posts_singular="post"
# "Posts tagged" (text on a title of a page with index of one tag, like "My Blog - Posts tagged "Music"")
template_tag_title="posts tagged"
# "Tags:" (beginning of line in HTML file with list of all tags for this article)
template_tags_line_header="tags:"
# "Back to the index page" (used on archive page, it is link to blog index)
template_archive_index_page="back home"
# "Subscribe" (used on bottom of index page, it is link to RSS feed)
template_subscribe="rss"
# "Subscribe to this page..." (used as text for browser feed button that is embedded to html)
template_subscribe_browser_button="subscribe to this page..."
# "Tweet" (used as twitter text button for posting to twitter)
template_twitter_button="tweet"
template_twitter_comment="&lt;type your comment here but please leave the URL so that other people can follow the comments&gt;"
# The locale to use for the dates displayed on screen
date_format="%B %d, %Y"
date_locale="C"
date_inpost="bashblog_timestamp"
# Don't change these dates
date_format_full="%a, %d %b %Y %H:%M:%S %z"
date_format_timestamp="%Y%m%d%H%M.%S"
date_allposts_header="%B %Y"
# Perform the post title -> filename conversion
# Experts only. You may need to tune the locales too
# Leave empty for no conversion, which is not recommended
# This default filter respects backwards compatibility
convert_filename="iconv -f utf-8 -t ascii//translit | sed 's/^-*//' | tr [:upper:] [:lower:] | tr ' ' '-' | tr -dc '[:alnum:]-'"
# URL where you can view the post while it's being edited
# same as global_url by default
# You can change it to path on your computer, if you write posts locally
# before copying them to the server
preview_url=""
# Markdown location. Trying to autodetect by default.
# The invocation must support the signature 'markdown_bin in.md > out.html'
markdown_bin=$(which md2html.awk)
}
# Check for the validity of some variables
# DO NOT EDIT THIS FUNCTION unless you know what you're doing
global_variables_check() {
[[ $header_file == .header.html ]] && \
echo "Please check your configuration. '.header.html' is not a valid value for the setting 'header_file'" && \
exit
[[ $footer_file == .footer.html ]] && \
echo "Please check your configuration. '.footer.html' is not a valid value for the setting 'footer_file'" && \
exit
}

View File

@ -0,0 +1,37 @@
#!/usr/bin/env bash
# Check if the file is a 'boilerplate' (i.e. not a post)
# The return values are designed to be used like this inside a loop:
# is_boilerplate_file <file> && continue
#
# $1 the file
#
# Return 0 (bash return value 'true') if the input file is an index, feed, etc
# or 1 (bash return value 'false') if it is a blogpost
is_boilerplate_file() {
declare non_blogpost_files
declare index_file
declare archive_index
declare gophermap
declare gemini_index
declare tags_index
declare footer_file
declare header_file
declare prefix_tags
declare html_exclude
name=${1#./}
# First check against user-defined non-blogpost pages
for item in "${non_blogpost_files[@]}"; do
[[ "$name" == "$item" ]] && return 0
done
case $name in
( "$index_file" | "$archive_index" | "$gophermap" | "$gemini_index" | "$tags_index" | "$footer_file" | "$header_file" | "$prefix_tags"* )
return 0 ;;
( * ) # Check for excluded
for excl in "${html_exclude[@]}"; do
[[ $name == "$excl" ]] && return 0
done
return 1 ;;
esac
}

21
lib/list_posts.sh Normal file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Displays a list of the posts
list_posts() {
declare date_format
declare date_locale
if ls ./*.html > /dev/null; then
echo "No posts yet. Use 'bb.sh post' to create one" && return
fi
lines=""
n=1
while IFS='' read -r i; do
is_boilerplate_file "$i" && continue
line="$n # $(get_post_title "$i") # $(LC_ALL=$date_locale date -r "$i" +"$date_format")"
lines+=$line\\n
n=$(( n + 1 ))
done < <(ls -t ./*.html)
echo -e "$lines" | column -t -s "#"
}

32
lib/list_tags.sh Normal file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env bash
# Displays a list of the tags
#
# $2 if "-n", tags will be sorted by number of posts
list_tags() {
declare prefix_tags
declare template_tags_posts_singular
declare template_tags_posts
if [[ $2 == -n ]]; then do_sort=1; else do_sort=0; fi
if ls "./${prefix_tags}"*.html > /dev/null; then
echo "No posts yet. Use 'bb.sh post' to create one" && return
fi
lines=""
for i in "./${prefix_tags}"*.html; do
[[ -f "$i" ]] || break
nposts=$(grep -c "<\!-- text begin -->" "$i")
tagname=${i#"$prefix_tags"}
tagname=${tagname#.html}
((nposts > 1)) && word=$template_tags_posts || word=$template_tags_posts_singular
line="$tagname # $nposts # $word"
lines+=$line\\n
done
if (( do_sort == 1 )); then
echo -e "$lines" | column -t -s "#" | sort -nrk 2
else
echo -e "$lines" | column -t -s "#"
fi
}

27
lib/make_gemini.sh Normal file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env bash
make_gemini() {
declare gemini_index
if [ ! -d "${HOME}/public_gemini" ]; then
printf "Creating ~/public_gemini\\n"
mkdir "${HOME}/public_gemini"
fi
if [ ! -L "${HOME}/public_gemini/blog" ]; then
ln -sf "${HOME}/public_html/blog/" "${HOME}/public_gemini/blog"
fi
if [ ! -f "${HOME}/public_gemini/blog/$gemini_index" ]; then
cat <<- 'EOF' > "${HOME}/public_gemini/blog/${gemini_index}"
#!/usr/bin/env sh
printf "20 text/gemini\r\n"
printf "my bashblog posts\r\n"
user=$(stat -c '%U' $0)
for post in $(ls -t /home/$user/public_gemini/blog/*.md); do
post=$(basename $post)
printf "=> /~$user/blog/$post $post\r\n"
done
EOF
chmod +x "${HOME}/public_gemini/blog/${gemini_index}"
fi
}

27
lib/make_gophermap.sh Normal file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env bash
make_gophermap() {
declare gophermap
if [ ! -d "${HOME}/public_gopher" ]; then
printf "Creating gopher hole\\n"
mkdir "${HOME}/public_gopher"
fi
if [ ! -L "${HOME}/public_gopher/blog" ]; then
ln -sf "${HOME}/public_html/blog/" "${HOME}/public_gopher/blog"
fi
if [ ! -f "${HOME}/public_gopher/blog/$gophermap" ]; then
cat <<- 'EOF' > "${HOME}/public_html/blog/${gophermap}"
#!/usr/bin/env sh
printf "my bashblog posts\n"
user=$(stat -c '%U' .)
for post in $(ls -t *.md); do
post=$(basename $post)
printf "0$post\t/~$user/blog/$post\ttilde.team\t70\n"
done
EOF
chmod +x "${HOME}/public_html/blog/${gophermap}"
fi
chmod 644 "./*.md"
}

51
lib/make_rss.sh Normal file
View File

@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Generate the feed file
make_rss() {
declare blog_feed
declare date_format_full
declare global_title
declare global_url
declare global_description
declare index_file
declare number_of_feed_articles
declare cut_do
echo -n "Making RSS "
rssfile=$blog_feed.$RANDOM
while [[ -f $rssfile ]]; do rssfile=$blog_feed.$RANDOM; done
{
pubdate=$(LC_ALL=C date +"$date_format_full")
echo '<?xml version="1.0" encoding="UTF-8" ?>'
echo '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">'
echo "<channel><title>$global_title</title><link>$global_url/$index_file</link>"
echo "<description>$global_description</description><language>en</language>"
echo "<lastBuildDate>$pubdate</lastBuildDate>"
echo "<pubDate>$pubdate</pubDate>"
echo "<atom:link href=\"$global_url/$blog_feed\" rel=\"self\" type=\"application/rss+xml\">"
n=0
while IFS='' read -r i; do
is_boilerplate_file "$i" && continue
((n >= number_of_feed_articles)) && break # max 10 items
echo -n "." 1>&3
echo '<item><title>'
get_post_title "$i"
echo '</title><description><![CDATA['
get_html_file_content 'text' 'entry' "$cut_do" <"$i"
echo "]]></description><link>$global_url/${i#./}</link>"
echo "<guid>$global_url/$i</guid>"
echo "<dc:creator>$(get_post_author "$i")</dc:creator>"
echo "<pubDate>$(LC_ALL=C date -r "$i" +"$date_format_full")</pubDate></item>"
n=$(( n + 1 ))
done < <(ls -t ./*.html)
echo '</channel></rss>'
} 3>&1 >"$rssfile"
echo ""
mv "$rssfile" "$blog_feed"
chmod 644 "$blog_feed"
}

13
lib/mrkdwn.sh Normal file
View File

@ -0,0 +1,13 @@
#!/usr/bin/env bash
# Parse a Markdown file into HTML and return the generated file
declare markdown_bin
mrkdwn() {
out=${1%.md}.html
while [[ -f $out ]]
do
out=${out%.md}.$RANDOM.html
done
$markdown_bin "$1" > "$out"
echo "$out"
}

53
lib/parse_file.sh Normal file
View File

@ -0,0 +1,53 @@
#!/usr/bin/env bash
# Parse the plain text file into an html file
#
# $1 source file name
# $2 (optional) timestamp for the file
# $3 (optional) destination file name
# note that although timestamp is optional, something must be provided at its
# place if destination file name is provided, i.e:
# parse_file source.txt "" destination.html
parse_file() {
declare convert_filename
declare template_tags_line_header
declare prefix_tags
declare global_author
# Read for the title and check that the filename is ok
title=""
while IFS='' read -r line; do
if [[ -z $title ]]; then
# remove extra <p> and </p> added by markdown
title=$(echo "$line" | sed 's/<\/*p>//g')
if [[ -n $3 ]]; then
filename=$3
else
filename=$title
[[ -n $convert_filename ]] && filename=$(echo "$title" | eval "$convert_filename")
[[ -n $filename ]] || filename=$RANDOM # don't allow empty filenames
filename=$filename.html
# Check for duplicate file names
while [[ -f $filename ]]; do
filename=${filename%.html}$RANDOM.html
done
fi
content=$filename.tmp
# Parse possible tags
elif [[ $line == "<p>$template_tags_line_header"* ]]; then
tags=$(echo "$line" | cut -d ":" -f 2- | sed -e 's/<\/p>//g' -e 's/^ *//' -e 's/ *$//' -e 's/, /,/g')
IFS=, read -r -a array <<< "$tags"
echo -n "<p>$template_tags_line_header " >> "$content"
for item in "${array[@]}"; do
echo -n "<a href='$prefix_tags$item.html'>$item</a>, "
done | sed 's/, $/<\/p>/g' >> "$content"
else
echo "$line" >> "$content"
fi
done < "$1"
# Create the actual html page
create_html_page "$content" "$filename" no "$title" "$2" "$global_author"
rm "$content"
}

11
lib/posts_with_tags.sh Normal file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Finds all posts referenced in a number of tags.
# Arguments are tags
# Prints one line with space-separated tags to stdout
posts_with_tags() {
(($# < 1)) && return
set -- "${@/#/$prefix_tags}"
set -- "${@/%/.html}"
sed -n '/^<h3><a class="ablack" href="[^"]*">/{s/.*href="\([^"]*\)">.*/\1/;p;}' "$@" 2> /dev/null
}

View File

@ -0,0 +1,37 @@
#!/usr/bin/env bash
# Regenerates all the single post entries, keeping the post content but modifying
# the title, html structure, etc
rebuild_all_entries() {
declare date_inpost
declare date_format_full
declare date_format_timestamp
echo -n "Rebuilding all entries "
for i in ./*.html; do
is_boilerplate_file "$i" && continue;
contentfile=.tmp.$RANDOM
while [[ -f $contentfile ]]; do contentfile=.tmp.$RANDOM; done
echo -n "."
# Get the title and entry, and rebuild the html structure from scratch (divs, title, description...)
title=$(get_post_title "$i")
get_html_file_content 'text' 'text' <"$i" >> "$contentfile"
# Read timestamp from post, if present, and sync file timestamp
timestamp=$(awk '/<!-- '"$date_inpost"': .+ -->/ { print }' "$i" | cut -d '#' -f 2)
[[ -n $timestamp ]] && touch -t "$timestamp" "$i"
# Read timestamp from file in correct format for 'create_html_page'
timestamp=$(LC_ALL=C date -r "$i" +"$date_format_full")
create_html_page "$contentfile" "$i.rebuilt" no "$title" "$timestamp" "$(get_post_author "$i")"
# keep the original timestamp!
timestamp=$(LC_ALL=C date -r "$i" +"$date_format_timestamp")
mv "$i.rebuilt" "$i"
chmod 644 "$i"
touch -t "$timestamp" "$i"
rm "$contentfile"
done
echo ""
}

53
lib/rebuild_index.sh Normal file
View File

@ -0,0 +1,53 @@
#!/usr/bin/env bash
# Generate the index.html with the content of the latest posts
rebuild_index() {
declare index_file
declare number_of_index_articles
declare cut_do
declare cut_line
declare template_read_more
declare template_archive
declare template_subscribe
declare template_tags_title
declare blog_feed
declare global_author
declare global_feedburner
declare global_title
declare archive_index
declare tags_index
echo -n "Rebuilding the index "
newindexfile=$index_file.$RANDOM
contentfile=$newindexfile.content
while [[ -f $newindexfile ]]; do
newindexfile=$index_file.$RANDOM
contentfile=$newindexfile.content
done
# Create the content file
{
n=0
while IFS='' read -r i; do
is_boilerplate_file "$i" && continue;
if ((n >= number_of_index_articles)); then break; fi
if [[ -n $cut_do ]]; then
get_html_file_content 'entry' 'entry' 'cut' <"$i" | awk "/$cut_line/ { print \"<p class=\\\"readmore\\\"><a href=\\\"$i\\\">$template_read_more</a></p>\" ; next } 1"
else
get_html_file_content 'entry' 'entry' <"$i"
fi
echo -n "." 1>&3
n=$(( n + 1 ))
done < <(ls -t ./*.html) # sort by date, newest first
feed=$blog_feed
if [[ -n $global_feedburner ]]; then feed=$global_feedburner; fi
echo "<div id=\"all_posts\"><a href=\"$archive_index\">$template_archive</a> &mdash; <a href=\"$tags_index\">$template_tags_title</a> &mdash; <a href=\"$feed\">$template_subscribe</a></div>"
} 3>&1 >"$contentfile"
echo ""
create_html_page "$contentfile" "$newindexfile" yes "$global_title" "$global_author"
rm "$contentfile"
mv "$newindexfile" "$index_file"
chmod 644 "$index_file"
}

67
lib/rebuild_tags.sh Normal file
View File

@ -0,0 +1,67 @@
#!/usr/bin/env bash
# Rebuilds tag_*.html files
# if no arguments given, rebuilds all of them
# if arguments given, they should have this format:
# "FILE1 [FILE2 [...]]" "TAG1 [TAG2 [...]]"
# where FILEn are files with posts which should be used for rebuilding tags,
# and TAGn are names of tags which should be rebuilt.
# example:
# rebuild_tags "one_post.html another_article.html" "example-tag another-tag"
# mind the quotes!
rebuild_tags() {
declare prefix_tags
declare cut_do
declare cut_line
declare template_read_more
declare template_tag_title
declare global_title
declare global_author
if (($# < 2)); then
# will process all files and tags
files=$(ls -t ./*.html)
all_tags=yes
else
# will process only given files and tags
files=$(printf '%s\n' "$1" | sort -u)
# shellcheck disable=SC2086 # Intended splitting of $files
files=$(ls -t $files)
tags=$2
fi
echo -n "Rebuilding tag pages "
#n=0
if [[ -n $all_tags ]]; then
rm ./"$prefix_tags"*.html &> /dev/null
else
for i in $tags; do
rm "./$prefix_tags$i.html" &> /dev/null
done
fi
# First we will process all files and create temporal tag files
# with just the content of the posts
tmpfile=tmp.$RANDOM
while [[ -f $tmpfile ]]; do tmpfile=tmp.$RANDOM; done
while IFS='' read -r i; do
is_boilerplate_file "$i" && continue;
echo -n "."
if [[ -n $cut_do ]]; then
get_html_file_content 'entry' 'entry' 'cut' <"$i" | awk "/$cut_line/ { print \"<p class=\\\"readmore\\\"><a href=\\\"$i\\\">$template_read_more</a></p>\" ; next } 1"
else
get_html_file_content 'entry' 'entry' <"$i"
fi >"$tmpfile"
for tag in $(tags_in_post "$i"); do
if [[ -n $all_tags || " $tags " == *" $tag "* ]]; then
cat "$tmpfile" >> "$prefix_tags$tag".tmp.html
fi
done
done <<< "$files"
rm "$tmpfile"
# Now generate the tag files with headers, footers, etc
while IFS='' read -r i; do
tagname=${i#./"$prefix_tags"}
tagname=${tagname%.tmp.html}
create_html_page "$i" "$prefix_tags$tagname.html" yes "$global_title &mdash; $template_tag_title \"$tagname\"" "$global_author"
rm "$i"
done < <(ls -t ./"$prefix_tags"*.tmp.html 2>/dev/null)
echo
}

15
lib/reset.sh Normal file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Delete all generated content, leaving only this script
reset() {
echo "Are you sure you want to delete all blog entries? Please write \"Yes, I am!\" "
read -r line
if [[ $line == "Yes, I am!" ]]; then
rm .*.html ./*.html ./*.css ./*.rss &> /dev/null
echo
echo "Deleted all posts, stylesheets and feeds."
echo "Kept your old '.backup.tar.gz' just in case, please delete it manually if needed."
else
echo "Phew! You dodged a bullet there. Nothing was modified."
fi
}

9
lib/tags_in_post.sh Normal file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# Finds all tags referenced in one post.
# Accepts either filename as first argument, or post content at stdin
# Prints one line with space-separated tags to stdout
tags_in_post() {
declare template_tags_line_header
sed -n "/^<p>$template_tags_line_header/{s/^<p>$template_tags_line_header//;s/<[^>]*>//g;s/[ ,]\+/ /g;p;}" "$1" | tr ', ' ' '
}

16
lib/test_markdown.sh Normal file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env bash
# Test if the markdown script is working correctly
test_markdown() {
declare markdown_bin
if [[ -n $markdown_bin ]]; then
local m1; m1=$("$markdown_bin" <<< "$'line 1\n\nline 2'")
local m2; m2=$("$markdown_bin" <<< "$'line 1\n\nline 2'")
local c1=$'<p>line 1</p>\n\n<p>line 2</p>'
local c2=$'<p>line 1</p>\n<p>line 2</p>'
# shellcheck disable=SC22350 # must enforce order of operations
[[ "$m1" == "$c1" ]] || [[ "$m2" == "$c2" ]]
else
return 1
fi
}

50
lib/twitter_card.sh Normal file
View File

@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Create a Twitter summary (twitter "card") for the post
#
# $1 the post file
# $2 the title
twitter_card() {
declare global_twitter_username
declare template_tags_line_header
declare global_url
declare global_twitter_cookieless
declare template_twitter_button
declare template_twitter_comment
declare template_comments
[[ -z $global_twitter_username ]] && return
echo "<meta name='twitter:card' content='summary'>"
echo "<meta name='twitter:site' content='@$global_twitter_username'>"
echo "<meta name='twitter:title' content='$2'>" # Twitter truncates at 70 char
description=$(grep -v "^<p>$template_tags_line_header" "$1" | sed -e 's/<[^>]*>//g' | head -c 250 | tr '\n' ' ' | sed "s/\"/'/g")
echo "<meta name='twitter:description' content=\"$description\">"
image=$(sed -n 's/.*<img.*src="\([^"]*\)".*/\1/p' "$1" | head -n 1) # First image is fine
[[ -z $image ]] && return
[[ $image =~ ^https?:// ]] || image=$global_url/$image # Check that URL is absolute
echo "<meta name='twitter:image' content='$image'>"
}
# Adds the code needed by the twitter button
#
# $1 the post URL
twitter() {
[[ -z $global_twitter_username ]] && return
if [[ $global_twitter_cookieless == true ]]; then
id=$RANDOM
search_engine="https://twitter.com/search?q="
echo "<p id='twitter'><a href='http://twitter.com/intent/tweet?url=$1&text=$template_twitter_comment&via=$global_twitter_username'>$template_comments $template_twitter_button</a> "
echo "<a href='$search_engine""$1'><span id='count-$id'></span></a>&nbsp;</p>"
return;
else
echo "<p id='twitter'>$template_comments&nbsp;";
fi
echo "<a href=\"https://twitter.com/share\" class=\"twitter-share-button\" data-text=\"$template_twitter_comment\" data-url=\"$1\""
echo " data-via=\"$global_twitter_username\""
echo ">$template_twitter_button</a> <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=\"//platform.twitter.com/widgets.js\";fjs.parentNode.insertBefore(js,fjs);}}(document,\"script\",\"twitter-wjs\");</script>"
echo "</p>"
}

24
lib/usage.sh Normal file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env bash
declare global_software_name
declare global_software_version
# Displays the help
usage() {
echo "$global_software_name $global_software_version"
echo "usage: $0 command [filename]"
echo ""
echo "Commands:"
echo " post [filename] insert a new blog post in markdown, or the filename of a draft to continue editing it"
echo " edit [-n] [filename] edit an already published .md file. **NEVER** edit manually a published .html file,"
echo " always use this function as it keeps internal data and rebuilds the blog"
echo " use '-n' to give the file a new name, if title was changed"
echo " delete [filename] deletes the post and rebuilds the blog"
echo " rebuild regenerates all the pages and posts, preserving the content of the entries"
echo " reset deletes everything except this script. Use with a lot of caution and back up first!"
echo " list list all posts"
echo " tags [-n] list all tags in alphabetical order"
echo " use '-n' to sort list by number of posts"
echo ""
echo "for more information please see https://tilde.team/wiki/?page=tildeblogs"
echo "source here: https://tildegit.org/team/bashblog"
}

103
lib/write_entry.sh Normal file
View File

@ -0,0 +1,103 @@
#!/usr/bin/env bash
# Manages the creation of the text file and the parsing to html file
# also the drafts
write_entry() {
declare template_tags_line_header
declare global_url
declare convert_filename
declare save_markdown
test_markdown && fmt=md || fmt=html
f=$2
[[ $2 == -html ]] && fmt=html && f=$3
if [[ -n $f ]]; then
TMPFILE=$f
if [[ ! -f $TMPFILE ]]; then
echo "The file doesn't exist"
delete_includes
exit
fi
# guess format from TMPFILE
extension=${TMPFILE##*.}
[[ $extension == md || $extension == html ]] && fmt=$extension
# but let user override it (`bb.sh post -html file.md`)
[[ $2 == -html ]] && fmt=html
# Test if Markdown is working before re-posting a .md file
if [[ $extension == md ]]; then
if test_markdown; then
echo "Markdown is not working, please edit HTML file directly."
exit
fi
fi
else
TMPFILE=.entry-$RANDOM.$fmt
echo -e "Title on this line\n" >> "$TMPFILE"
[[ $fmt == html ]] && cat << EOF >> "$TMPFILE"
<p>The rest of the text file is an <b>html</b> blog post. The process will continue as soon
as you exit your editor.</p>
<p>$template_tags_line_header keep-this-tag-format, tags-are-optional, example</p>
EOF
[[ $fmt == md ]] && cat << EOF >> "$TMPFILE"
The rest of the text file is a **Markdown** blog post. The process will continue
as soon as you exit your editor.
$template_tags_line_header keep-this-tag-format, tags-are-optional, beware-with-underscores-in-markdown, example
EOF
fi
chmod 600 "$TMPFILE"
post_status="E"
filename=""
while [[ $post_status != "p" && $post_status != "P" ]]; do
[[ -n $filename ]] && rm "$filename" # Delete the generated html file, if any
$EDITOR "$TMPFILE"
if [[ $fmt == md ]]; then
html_from_md=$(mrkdwn "$TMPFILE")
parse_file "$html_from_md"
rm "$html_from_md"
else
parse_file "$TMPFILE" # this command sets $filename as the html processed file
fi
chmod 644 "$filename"
[[ -n $preview_url ]] || preview_url=$global_url
echo "To preview the entry, open $preview_url/$filename in your browser"
echo -n "[P]ost this entry, [E]dit again, [D]raft for later? (p/E/d) "
read -r post_status
if [[ $post_status == d || $post_status == D ]]; then
mkdir -p "drafts/"
chmod 700 "drafts/"
title=$(head -n 1 $TMPFILE)
[[ -n $convert_filename ]] && title=$(echo "$title" | eval "$convert_filename")
[[ -n $title ]] || title=$RANDOM
draft=drafts/$title.$fmt
mv "$TMPFILE" "$draft"
chmod 600 "$draft"
rm "$filename"
delete_includes
echo "Saved your draft as '$draft'"
exit
fi
done
if [[ $fmt == md && -n $save_markdown ]]; then
mv "$TMPFILE" "${filename%%.*}.md"
else
rm "$TMPFILE"
fi
chmod 644 "$filename"
echo "Posted $filename"
relevant_tags=$(tags_in_post "$filename")
if [[ -n $relevant_tags ]]; then
# shellcheck disable=SC2086 # Intended splitting of $relevant_tags
relevant_posts="$(posts_with_tags $relevant_tags) $filename"
rebuild_tags "$relevant_posts" "$relevant_tags"
fi
}

11
md2gemini.awk Executable file
View File

@ -0,0 +1,11 @@
#!/bin/awk -f
#
# by: Josemar Lohn <j@lo.hn>
# lo.hn on www/gemini/gopher
#
# Based on md2html by Jesus Galan (yiyus) 2009
#
#
# Usage: md2gemini.awk file.md > file.html
1

560
md2gopher.awk Executable file
View File

@ -0,0 +1,560 @@
#!/bin/awk -f
#
# by: Josemar Lohn <j@lo.hn>
# lo.hn on www/gemini/gopher
#
# Based on md2html by Jesus Galan (yiyus) 2009
#
#
# Usage: md2gopher.awk file.md > file.html
function eschtml(t) {
#gsub("&", "\\&amp;", t);
#gsub("<", "\\&lt;", t);
return t;
}
function oprint(t){
if(nr == 0)
print t;
else
otext = otext "\n" t;
}
# https://unix.stackexchange.com/a/94751
function centralize(t,c){
L = col - c - length(t);
#print("====" length(t) "=====" t "=====\n")
for(i=1; i<=int(L/2); i++)
t = " "t;
for(i=1; i<=int(L/2+.5); i++)
t = t " "
return t
}
# function from https://unix.stackexchange.com/a/282338
function justify(t,i,nbchar,nbspc,spaces,spcpf,r){
$0=t
if (NF <= 1) { return t }
else {
nbchar = 0
for (i = 1; i <= NF; i++) {
nbchar += length($i)
}
nbspc = col - nbchar
spcpf = int(nbspc / (NF - 1))
for (i = 1; i < NF; i++) {
r = r $i
spaces = (NF == 2 || i == NF - 1) ? nbspc : spcpf
if (spaces < 1) spaces = 1
for (j = 0; j < spaces; j++) {
r = r " "
}
nbspc -= spaces
}
r = r $NF
}
return r
}
function subref(id){
for(; nr > 0 && sub("<<" id, ref[id], otext); nr--);
if(nr == 0 && otext) {
print otext;
otext = "";
}
}
function nextil(t) {
if(!match(t, /[`<&\[*_\\-]|(!\[)|(\[\^)/))
return t;
t1 = substr(t, 1, RSTART - 1);
tag = substr(t, RSTART, RLENGTH);
t2 = substr(t, RSTART + RLENGTH);
if(ilcode && tag != "`")
return eschtml(t1 tag) nextil(t2);
# Backslash escaping
if(tag == "\\"){
if(match(t2, /^[\\`*_{}\[\]()#+\-\.!]/)){
tag = substr(t2, 1, 1);
t2 = substr(t2, 2);
}
return t1 tag nextil(t2);
}
# Dashes
if(tag == "-"){
if(sub(/^-/, "", t2))
tag = "&#8212;";
return t1 tag nextil(t2);
}
# Inline Code
if(tag == "`"){
if(sub(/^`/, "", t2)){
if(!match(t2, /``/))
return t1 "&#8221;" nextil(t2);
ilcode2 = !ilcode2;
}
else if(ilcode2)
return t1 tag nextil(t2);
tag = "<code>";
if(ilcode){
t1 = eschtml(t1);
tag = "</code>";
}
ilcode = !ilcode;
return t1 tag nextil(t2);
}
if(tag == "<"){
# Autolinks
if(match(t2, /^[^ ]+[\.@][^ ]+>/)){
url = eschtml(substr(t2, 1, RLENGTH - 1));
t2 = substr(t2, RLENGTH + 1);
linktext = url;
if(match(url, /@/) && !match(url, /^mailto:/))
url = "mailto:" url;
return t1 "<a href=\"" url "\">" linktext "</a>" nextil(t2);
}
# Html tags
if(match(t2, /^[A-Za-z\/!][^>]*>/)){
tag = tag substr(t2, RSTART, RLENGTH);
t2 = substr(t2, RLENGTH + 1);
return t1 tag nextil(t2);
}
return t1 "&lt;" nextil(t2);
}
# Html special entities
if(tag == "&"){
if(match(t2, /^#?[A-Za-z0-9]+;/)){
tag = tag substr(t2, RSTART, RLENGTH);
t2 = substr(t2, RLENGTH + 1);
return t1 tag nextil(t2);
}
return t1 "&amp;" nextil(t2);
}
# Images
if(tag == "!["){
if(!match(t2, /(\[.*\])|(\(.*\))/))
return t1 tag nextil(t2);
match(t2, /^[^\]]*/);
alt = substr(t2, 1, RLENGTH);
t2 = substr(t2, RLENGTH + 2);
if(match(t2, /^\(/)){
# Inline
sub(/^\(/, "", t2);
match(t2, /^[^\)]+/);
url = eschtml(substr(t2, 1, RLENGTH));
t2 = substr(t2, RLENGTH + 2);
title = "";
if(match(url, /[ ]+".*"[ ]*$/)) {
title = substr(url, RSTART, RLENGTH);
url = substr(url, 1, RSTART - 1);
match(title, /".*"/);
title = " title=\"" substr(title, RSTART + 1, RLENGTH - 2) "\"";
}
if(match(url, /^<.*>$/))
url = substr(url, 2, RLENGTH - 2);
return t1 "<img src=\"" url "\" alt=\"" alt "\"" title " />" nextil(t2);
}
else{
# Referenced
sub(/^ ?\[/, "", t2);
id = alt;
if(match(t2, /^[^\]]+/))
id = substr(t2, 1, RLENGTH);
t2 = substr(t2, RLENGTH + 2);
if(ref[id])
r = ref[id];
else{
r = "<<" id;
nr++;
}
return t1 "<img src=\"" r "\" alt=\"" alt "\" />" nextil(t2);
}
}
# Footnotes
if(tag == "[^"){
match(t2, /^[^\]]*(\[[^\]]*\][^\]]*)*/);
linktext = substr(t2, 1, RLENGTH);
t2 = substr(t2, RLENGTH + 2);
return t1 "<sup class=\"fnref\"><a href=\"#fn-" linktext "\" id=\"fnref-" linktext "\">" linktext "</a></sup>" nextil(t2);
}
# Links
if(tag == "["){
if(!match(t2, /(\[.*\])|(\(.*\))/))
return t1 tag nextil(t2);
match(t2, /^[^\]]*(\[[^\]]*\][^\]]*)*/);
linktext = substr(t2, 1, RLENGTH);
t2 = substr(t2, RLENGTH + 2);
if(match(t2, /^\(/)){
# Inline
match(t2, /^[^\)]+(\([^\)]+\)[^\)]*)*/);
url = substr(t2, 2, RLENGTH - 1);
pt2 = substr(t2, RLENGTH + 2);
title = "";
if(match(url, /[ ]+".*"[ ]*$/)) {
title = substr(url, RSTART, RLENGTH);
url = substr(url, 1, RSTART - 1);
match(title, /".*"/);
title = " title=\"" substr(title, RSTART + 1, RLENGTH - 2) "\"";
}
if(match(url, /^<.*>$/))
url = substr(url, 2, RLENGTH - 2);
url = eschtml(url);
return t1 "<a href=\"" url "\"" title ">" nextil(linktext) "</a>" nextil(pt2);
}
else{
# Referenced
sub(/^ ?\[/, "", t2);
id = linktext;
if(match(t2, /^[^\]]+/))
id = substr(t2, 1, RLENGTH);
t2 = substr(t2, RLENGTH + 2);
if(ref[id])
r = ref[id];
else{
r = "<<" id;
nr++;
}
pt2 = t2;
return t1 "<a href=\"" r "\" />" nextil(linktext) "</a>" nextil(pt2);
}
}
# Emphasis
if(match(tag, /[*_]/)){
ntag = tag;
if(sub("^" tag, "", t2)){
if(stag[ns] == tag && match(t2, "^" tag))
t2 = tag t2;
else
ntag = tag tag
}
n = length(ntag);
tag = (n == 2) ? "strong" : "em";
if(match(t1, / $/) && match(t2, /^ /))
return t1 tag nextil(t2);
if(stag[ns] == ntag){
tag = "/" tag;
ns--;
}
else
stag[++ns] = ntag;
tag = "<" tag ">";
return t1 tag nextil(t2);
}
}
function inline(t) {
ilcode = 0;
ilcode2 = 0;
ns = 0;
return nextil(t);
}
#https://unix.stackexchange.com/a/337656
function wrap(t,align,q,y,z,final) {
while (t)
{
q = match(t, / |$/); y += q
if (y > col) {
if (align != 0)
{
if (align=="c")
#print "zzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
final = final centralize(z) RS
if (align=="j")
final = final justify(z) RS
}
else
{
final = final z RS
}
y = q - 1
z = ""
}
else if (z) z = z FS
z = z substr(t, 1, q - 1)
t = substr(t, q + 1)
}
if (align=="c")
{
final = final centralize(z)
}
else {
final = final z
}
return final
}
function printp(tag) {
if(!match(text, /^[ ]*$/)){
text = inline(text);
if(tag == "p")
{
oprint(wrap(text,"j"))
}
else
{
oprint(text);
}
}
text = "";
}
BEGIN {
blank = 0;
code = 0;
hr = 0;
html = 0;
nl = 0;
nr = 0;
otext = "";
text = "";
par = "p";
col=70;
c=0; do { lineheader = "=" lineheader; c++ } while ( c < col )
c=0; do { lineheadersmall = "-" lineheadersmall; c++ } while ( c < col )
}
# References
!code && /^ *\[\^![^\]]*\]:[ ]+/ {
sub(/^ *\[\^!/, "");
match($0, /\]/);
id = substr($0, 1, RSTART - 1);
sub(id "\\]:[ ]+", "");
title = "";
if(match($0, /".*"$/))
title = "\" title=\"" substr($0, RSTART + 1, RLENGTH - 2);
sub(/[ ]+".*"$/, "");
url = eschtml($0);
ref[id] = url title;
subref(id);
next;
}
!code && /^ *\[\^[^\]]*\]:[ ]+/ {
sub(/^ *\[\^/, "");
match($0, /\]/);
id = substr($0, 1, RSTART - 1);
sub(id "\\]:[ ]+", "");
sub(/[ ]+".*"$/, "");
url = eschtml($0);
fnref[id] = url;
subref(id);
next;
}
# html
!html && /^<(address|blockquote|center|dir|div|dl|fieldset|form|h[1-6r]|\
isindex|menu|noframes|noscript|ol|p|pre|table|ul|!--)/ {
if(code)
oprint("</pre></code>");
for(; !text && block[nl] == "blockquote"; nl--)
oprint("</blockquote>");
match($0, /^<(address|blockquote|center|dir|div|dl|fieldset|form|h[1-6r]|\
isindex|menu|noframes|noscript|ol|p|pre|table|ul|!--)/);
htag = substr($0, 2, RLENGTH - 1);
if(!match($0, "(<\\/" htag ">)|((^<hr ?\\/?)|(--)>$)"))
html = 1;
if(html && match($0, /^<hr/))
hr = 1;
oprint($0);
next;
}
html && (/(^<\/(address|blockquote|center|dir|div|dl|fieldset|form|h[1-6r]|\
isindex|menu|noframes|noscript|ol|p|pre|table|ul).*)|(--)>$/ ||
(hr && />$/)) {
html = 0;
hr = 0;
oprint($0);
next;
}
html {
oprint($0);
next;
}
# List and quote blocks
# Remove indentation
{
for(nnl = 0; nnl < nl; nnl++)
if((match(block[nnl + 1], /[ou]l/) && !sub(/^( | )/, "")) || \
(block[nnl + 1] == "blockquote" && !sub(/^> ?/, "")))
break;
}
nnl < nl && !blank && text && ! /^ ? ? ?([*+-]|([0-9]+\.)+)( +| )/ { nnl = nl; }
# Quote blocks
{
while(sub(/^> /, ""))
nblock[++nnl] = "blockquote";
}
# Horizontal rules
{ hr = 0; }
(blank || (!text && !code)) && /^ ? ? ?([-*_][ ]*)([-*_][ ]*)([-*_][ ]*)+$/ {
if(code){
oprint("</pre></code>");
code = 0;
}
blank = 0;
nnl = 0;
hr = 1;
}
# List items
block[nl] ~ /[ou]l/ && /^$/ {
blank = 1;
next;
}
{ newli = 0; }
!hr && (nnl != nl || !text || block[nl] ~ /[ou]l/) && /^ ? ? ?[*+-]( +| )/ {
sub(/^ ? ? ?[*+-]( +| )/, "");
nnl++;
nblock[nnl] = "ul";
newli = 1;
}
(nnl != nl || !text || block[nl] ~ /[ou]l/) && /^ ? ? ?([0-9]+\.)+( +| )/ {
sub(/^ ? ? ?([0-9]+\.)+( +| )/, "");
nnl++;
nblock[nnl] = "ol";
newli = 1;
}
newli {
if(blank && nnl == nl && !par)
par = "p";
blank = 0;
printp(par);
if(nnl == nl && block[nl] == nblock[nl])
oprint("</li><li>");
}
blank && ! /^$/ {
if(match(block[nnl], /[ou]l/) && !par)
par = "p";
printp(par);
par = "p";
blank = 0;
}
# Close old blocks and open new ones
nnl != nl || nblock[nl] != block[nl] {
if(code){
oprint("</pre></code>");
code = 0;
}
printp(par);
b = (nnl > nl) ? nblock[nnl] : block[nnl];
par = (match(b, /[ou]l/)) ? "" : "p";
}
nnl < nl || (nnl == nl && nblock[nl] != block[nl]) {
for(; nl > nnl || (nnl == nl && pblock[nl] != block[nl]); nl--){
if(match(block[nl], /[ou]l/))
oprint("</li>");
oprint("</" block[nl] ">");
}
}
nnl > nl {
for(; nl < nnl; nl++){
block[nl + 1] = nblock[nl + 1];
oprint("<" block[nl + 1] ">");
if(match(block[nl + 1], /[ou]l/))
oprint("<li>");
}
}
hr {
oprint(lineheader);
next;
}
# Code blocks
code && /^$/ {
if(blanK)
oprint("");
blank = 1;
next;
}
!text && sub(/^( | )/, "") {
if(blanK)
oprint("");
blank = 0;
if(!code)
oprint("<code><pre>");
code = 1;
$0 = eschtml($0);
oprint($0);
next;
}
code {
oprint("</pre></code>");
code = 0;
}
# Setex-style Headers
text && /^=+$/ {printp("h1"); next;}
text && /^-+$/ {printp("h2"); next;}
# Atx-Style headers
/^#+/ && (!newli || par=="p" || /^##/) {
for(n = 0; n < 6 && sub(/^# */, ""); n++)
{
sub(/#$/, "");
}
par = "h" n;
if (n == 1) {
oprint( text lineheader "\n=" centralize($0,2) "=\n" lineheader "\n" )
next;
}
if (n == 2) {
oprint("\n" text wrap($0,"c") "\n" lineheader "\n")
next;
}
if (n == 3) {
oprint(text centralize($0) "\n" lineheadersmall "\n")
next;
}
if (n > 3) {
text = text centralize($0) "\n"
next;
}
}
# Paragraph
/^$/ {
printp(par);
par = "p";
next;
}
# Add text
{ text = (text ? text " " : "") $0; }
function alen(a, ix, k) {
k = 0
for(ix in a) k++
return k
}
END {
if(code){
oprint("</pre></code>");
code = 0;
}
printp(par);
for(; nl > 0; nl--){
if(match(block[nl], /[ou]l/))
oprint("</li>");
oprint("</" block[nl] ">");
}
gsub(/<<[^"]*/, "", otext);
print(otext);
# Print footnotes
if(alen(fnref)>0) {
print "<ul class=\"fn-list\">";
for (i in fnref) print "<li id=\"fn-" i "\" class=\"fn-item\"><span class=\"fn-handle\">" i ": </span><span class=\"fn-text\">" inline(fnref[i]) " <a href=\"#fnref-" i "\" class=\"fn-backref\">↩&#xFE0E;</a></span></li>";
print "</ul>";
}
}

463
md2html.awk Executable file
View File

@ -0,0 +1,463 @@
#!/bin/awk -f
#
# by: Josemar Lohn <j@lo.hn>
# lo.hn on www/gemini/gopher
#
# Based on md2html by Jesus Galan (yiyus) 2009
#
#
# Usage: md2html.awk file.md > file.html
function eschtml(t) {
gsub("&", "\\&amp;", t);
gsub("<", "\\&lt;", t);
return t;
}
function oprint(t){
if(nr == 0)
print t;
else
otext = otext "\n" t;
}
function subref(id){
for(; nr > 0 && sub("<<" id, ref[id], otext); nr--);
if(nr == 0 && otext) {
print otext;
otext = "";
}
}
function nextil(t) {
if(!match(t, /[`<&\[*_\\-]|(!\[)|(\[\^)/))
return t;
t1 = substr(t, 1, RSTART - 1);
tag = substr(t, RSTART, RLENGTH);
t2 = substr(t, RSTART + RLENGTH);
if(ilcode && tag != "`")
return eschtml(t1 tag) nextil(t2);
# Backslash escaping
if(tag == "\\"){
if(match(t2, /^[\\`*_{}\[\]()#+\-\.!]/)){
tag = substr(t2, 1, 1);
t2 = substr(t2, 2);
}
return t1 tag nextil(t2);
}
# Dashes
if(tag == "-"){
if(sub(/^-/, "", t2))
tag = "&#8212;";
return t1 tag nextil(t2);
}
# Inline Code
if(tag == "`"){
if(sub(/^`/, "", t2)){
if(!match(t2, /``/))
return t1 "&#8221;" nextil(t2);
ilcode2 = !ilcode2;
}
else if(ilcode2)
return t1 tag nextil(t2);
tag = "<code>";
if(ilcode){
t1 = eschtml(t1);
tag = "</code>";
}
ilcode = !ilcode;
return t1 tag nextil(t2);
}
if(tag == "<"){
# Autolinks
if(match(t2, /^[^ ]+[\.@][^ ]+>/)){
url = eschtml(substr(t2, 1, RLENGTH - 1));
t2 = substr(t2, RLENGTH + 1);
linktext = url;
if(match(url, /@/) && !match(url, /^mailto:/))
url = "mailto:" url;
return t1 "<a href=\"" url "\">" linktext "</a>" nextil(t2);
}
# Html tags
if(match(t2, /^[A-Za-z\/!][^>]*>/)){
tag = tag substr(t2, RSTART, RLENGTH);
t2 = substr(t2, RLENGTH + 1);
return t1 tag nextil(t2);
}
return t1 "&lt;" nextil(t2);
}
# Html special entities
if(tag == "&"){
if(match(t2, /^#?[A-Za-z0-9]+;/)){
tag = tag substr(t2, RSTART, RLENGTH);
t2 = substr(t2, RLENGTH + 1);
return t1 tag nextil(t2);
}
return t1 "&amp;" nextil(t2);
}
# Images
if(tag == "!["){
if(!match(t2, /(\[.*\])|(\(.*\))/))
return t1 tag nextil(t2);
match(t2, /^[^\]]*/);
alt = substr(t2, 1, RLENGTH);
t2 = substr(t2, RLENGTH + 2);
if(match(t2, /^\(/)){
# Inline
sub(/^\(/, "", t2);
match(t2, /^[^\)]+/);
url = eschtml(substr(t2, 1, RLENGTH));
t2 = substr(t2, RLENGTH + 2);
title = "";
if(match(url, /[ ]+".*"[ ]*$/)) {
title = substr(url, RSTART, RLENGTH);
url = substr(url, 1, RSTART - 1);
match(title, /".*"/);
title = " title=\"" substr(title, RSTART + 1, RLENGTH - 2) "\"";
}
if(match(url, /^<.*>$/))
url = substr(url, 2, RLENGTH - 2);
return t1 "<img src=\"" url "\" alt=\"" alt "\"" title " />" nextil(t2);
}
else{
# Referenced
sub(/^ ?\[/, "", t2);
id = alt;
if(match(t2, /^[^\]]+/))
id = substr(t2, 1, RLENGTH);
t2 = substr(t2, RLENGTH + 2);
if(ref[id])
r = ref[id];
else{
r = "<<" id;
nr++;
}
return t1 "<img src=\"" r "\" alt=\"" alt "\" />" nextil(t2);
}
}
# Footnotes
if(tag == "[^"){
match(t2, /^[^\]]*(\[[^\]]*\][^\]]*)*/);
linktext = substr(t2, 1, RLENGTH);
t2 = substr(t2, RLENGTH + 2);
return t1 "<sup class=\"fnref\"><a href=\"#fn-" linktext "\" id=\"fnref-" linktext "\">" linktext "</a></sup>" nextil(t2);
}
# Links
if(tag == "["){
if(!match(t2, /(\[.*\])|(\(.*\))/))
return t1 tag nextil(t2);
match(t2, /^[^\]]*(\[[^\]]*\][^\]]*)*/);
linktext = substr(t2, 1, RLENGTH);
t2 = substr(t2, RLENGTH + 2);
if(match(t2, /^\(/)){
# Inline
match(t2, /^[^\)]+(\([^\)]+\)[^\)]*)*/);
url = substr(t2, 2, RLENGTH - 1);
pt2 = substr(t2, RLENGTH + 2);
title = "";
if(match(url, /[ ]+".*"[ ]*$/)) {
title = substr(url, RSTART, RLENGTH);
url = substr(url, 1, RSTART - 1);
match(title, /".*"/);
title = " title=\"" substr(title, RSTART + 1, RLENGTH - 2) "\"";
}
if(match(url, /^<.*>$/))
url = substr(url, 2, RLENGTH - 2);
url = eschtml(url);
return t1 "<a href=\"" url "\"" title ">" nextil(linktext) "</a>" nextil(pt2);
}
else{
# Referenced
sub(/^ ?\[/, "", t2);
id = linktext;
if(match(t2, /^[^\]]+/))
id = substr(t2, 1, RLENGTH);
t2 = substr(t2, RLENGTH + 2);
if(ref[id])
r = ref[id];
else{
r = "<<" id;
nr++;
}
pt2 = t2;
return t1 "<a href=\"" r "\" />" nextil(linktext) "</a>" nextil(pt2);
}
}
# Emphasis
if(match(tag, /[*_]/)){
ntag = tag;
if(sub("^" tag, "", t2)){
if(stag[ns] == tag && match(t2, "^" tag))
t2 = tag t2;
else
ntag = tag tag
}
n = length(ntag);
tag = (n == 2) ? "strong" : "em";
if(match(t1, / $/) && match(t2, /^ /))
return t1 tag nextil(t2);
if(stag[ns] == ntag){
tag = "/" tag;
ns--;
}
else
stag[++ns] = ntag;
tag = "<" tag ">";
return t1 tag nextil(t2);
}
}
function inline(t) {
ilcode = 0;
ilcode2 = 0;
ns = 0;
return nextil(t);
}
function printp(tag) {
if(!match(text, /^[ ]*$/)){
text = inline(text);
if(tag != "")
oprint("<" tag ">" text "</" tag ">");
else
oprint(text);
}
text = "";
}
BEGIN {
blank = 0;
code = 0;
hr = 0;
html = 0;
nl = 0;
nr = 0;
otext = "";
text = "";
par = "p";
}
# References
!code && /^ *\[\^![^\]]*\]:[ ]+/ {
sub(/^ *\[\^!/, "");
match($0, /\]/);
id = substr($0, 1, RSTART - 1);
sub(id "\\]:[ ]+", "");
title = "";
if(match($0, /".*"$/))
title = "\" title=\"" substr($0, RSTART + 1, RLENGTH - 2);
sub(/[ ]+".*"$/, "");
url = eschtml($0);
ref[id] = url title;
subref(id);
next;
}
!code && /^ *\[\^[^\]]*\]:[ ]+/ {
sub(/^ *\[\^/, "");
match($0, /\]/);
id = substr($0, 1, RSTART - 1);
sub(id "\\]:[ ]+", "");
sub(/[ ]+".*"$/, "");
url = eschtml($0);
fnref[id] = url;
subref(id);
next;
}
# html
!html && /^<(address|blockquote|center|dir|div|dl|fieldset|form|h[1-6r]|\
isindex|menu|noframes|noscript|ol|p|pre|table|ul|!--)/ {
if(code)
oprint("</pre></code>");
for(; !text && block[nl] == "blockquote"; nl--)
oprint("</blockquote>");
match($0, /^<(address|blockquote|center|dir|div|dl|fieldset|form|h[1-6r]|\
isindex|menu|noframes|noscript|ol|p|pre|table|ul|!--)/);
htag = substr($0, 2, RLENGTH - 1);
if(!match($0, "(<\\/" htag ">)|((^<hr ?\\/?)|(--)>$)"))
html = 1;
if(html && match($0, /^<hr/))
hr = 1;
oprint($0);
next;
}
html && (/(^<\/(address|blockquote|center|dir|div|dl|fieldset|form|h[1-6r]|\
isindex|menu|noframes|noscript|ol|p|pre|table|ul).*)|(--)>$/ ||
(hr && />$/)) {
html = 0;
hr = 0;
oprint($0);
next;
}
html {
oprint($0);
next;
}
# List and quote blocks
# Remove indentation
{
for(nnl = 0; nnl < nl; nnl++)
if((match(block[nnl + 1], /[ou]l/) && !sub(/^( | )/, "")) || \
(block[nnl + 1] == "blockquote" && !sub(/^> ?/, "")))
break;
}
nnl < nl && !blank && text && ! /^ ? ? ?([*+-]|([0-9]+\.)+)( +| )/ { nnl = nl; }
# Quote blocks
{
while(sub(/^> /, ""))
nblock[++nnl] = "blockquote";
}
# Horizontal rules
{ hr = 0; }
(blank || (!text && !code)) && /^ ? ? ?([-*_][ ]*)([-*_][ ]*)([-*_][ ]*)+$/ {
if(code){
oprint("</pre></code>");
code = 0;
}
blank = 0;
nnl = 0;
hr = 1;
}
# List items
block[nl] ~ /[ou]l/ && /^$/ {
blank = 1;
next;
}
{ newli = 0; }
!hr && (nnl != nl || !text || block[nl] ~ /[ou]l/) && /^ ? ? ?[*+-]( +| )/ {
sub(/^ ? ? ?[*+-]( +| )/, "");
nnl++;
nblock[nnl] = "ul";
newli = 1;
}
(nnl != nl || !text || block[nl] ~ /[ou]l/) && /^ ? ? ?([0-9]+\.)+( +| )/ {
sub(/^ ? ? ?([0-9]+\.)+( +| )/, "");
nnl++;
nblock[nnl] = "ol";
newli = 1;
}
newli {
if(blank && nnl == nl && !par)
par = "p";
blank = 0;
printp(par);
if(nnl == nl && block[nl] == nblock[nl])
oprint("</li><li>");
}
blank && ! /^$/ {
if(match(block[nnl], /[ou]l/) && !par)
par = "p";
printp(par);
par = "p";
blank = 0;
}
# Close old blocks and open new ones
nnl != nl || nblock[nl] != block[nl] {
if(code){
oprint("</pre></code>");
code = 0;
}
printp(par);
b = (nnl > nl) ? nblock[nnl] : block[nnl];
par = (match(b, /[ou]l/)) ? "" : "p";
}
nnl < nl || (nnl == nl && nblock[nl] != block[nl]) {
for(; nl > nnl || (nnl == nl && pblock[nl] != block[nl]); nl--){
if(match(block[nl], /[ou]l/))
oprint("</li>");
oprint("</" block[nl] ">");
}
}
nnl > nl {
for(; nl < nnl; nl++){
block[nl + 1] = nblock[nl + 1];
oprint("<" block[nl + 1] ">");
if(match(block[nl + 1], /[ou]l/))
oprint("<li>");
}
}
hr {
oprint("<hr>");
next;
}
# Code blocks
code && /^$/ {
if(blanK)
oprint("");
blank = 1;
next;
}
!text && sub(/^( | )/, "") {
if(blanK)
oprint("");
blank = 0;
if(!code)
oprint("<code><pre>");
code = 1;
$0 = eschtml($0);
oprint($0);
next;
}
code {
oprint("</pre></code>");
code = 0;
}
# Setex-style Headers
text && /^=+$/ {printp("h1"); next;}
text && /^-+$/ {printp("h2"); next;}
# Atx-Style headers
/^#+/ && (!newli || par=="p" || /^##/) {
for(n = 0; n < 6 && sub(/^# */, ""); n++)
sub(/#$/, "");
par = "h" n;
}
# Paragraph
/^$/ {
printp(par);
par = "p";
next;
}
# Add text
{ text = (text ? text " " : "") $0; }
function alen(a, ix, k) {
k = 0
for(ix in a) k++
return k
}
END {
if(code){
oprint("</pre></code>");
code = 0;
}
printp(par);
for(; nl > 0; nl--){
if(match(block[nl], /[ou]l/))
oprint("</li>");
oprint("</" block[nl] ">");
}
gsub(/<<[^"]*/, "", otext);
print(otext);
# Print footnotes
if(alen(fnref)>0) {
print "<ul class=\"fn-list\">";
for (i in fnref) print "<li id=\"fn-" i "\" class=\"fn-item\"><span class=\"fn-handle\">" i ": </span><span class=\"fn-text\">" inline(fnref[i]) " <a href=\"#fnref-" i "\" class=\"fn-backref\">↩&#xFE0E;</a></span></li>";
print "</ul>";
}
}

13
spec/do_main_spec.sh Normal file
View File

@ -0,0 +1,13 @@
# shellcheck shell=bash
Describe 'do_main()'
Include ./lib/date_version_detect.sh
Include ./lib/do_main.sh
Include ./lib/global_variables.sh
Include ./lib/usage.sh
It 'Call function do_main without paramenters'
When call do_main
The line 1 of output should eq "You're not in your blog directory. Moving you there now"
#The line 2 of output should eq "tildelog 0.1"
#The line 3 of output should eq "usage: ./tildelog.sh command [filename]"
End
End

7
spec/spec_helper.sh Normal file
View File

@ -0,0 +1,7 @@
#shellcheck shell=sh
# set -eu
# shellspec_spec_helper_configure() {
# shellspec_import 'support/custom_matcher'
# }

10
spec/tildeblog_spec.sh Normal file
View File

@ -0,0 +1,10 @@
# shellcheck shell=bash
Describe 'tildelog.sh'
It 'Call script without paramenters'
When run script ./tildelog.sh
The line 1 of output should eq "You're not in your blog directory. Moving you there now"
The line 2 of output should eq "tildelog 0.1"
The line 3 of output should eq "usage: ./tildelog.sh command [filename]"
End
End

9
spec/usage_spec.sh Normal file
View File

@ -0,0 +1,9 @@
# shellcheck shell=bash
Describe 'usage()'
Include ./lib/global_variables.sh
Include ./lib/usage.sh
It 'Call function usage'
When call usage
The line 2 of output should include "usage:"
End
End

50
tildelog.sh Executable file
View File

@ -0,0 +1,50 @@
#!/usr/bin/env bash
# TildeLog, a not-so-simple blog/gemlog/phlog system made for tilde.team
# By Josemar Lohn <j@lo.hn>
#
# Heavily based on BashBlog, by Carlos Fenollosa <carlos.fenollosa@gmail.com>
### BEGIN SOURCEFILES -> DO NOT REMOVE THIS LINE
source ./lib/all_posts.sh
source ./lib/all_tags.sh
source ./lib/create_css.sh
source ./lib/create_html_page.sh
source ./lib/create_includes.sh
source ./lib/date_version_detect.sh
source ./lib/delete_includes.sh
source ./lib/do_main.sh
source ./lib/edit.sh
source ./lib/get_html_file_content.sh
source ./lib/get_post_author.sh
source ./lib/get_post_title.sh
source ./lib/global_variables.sh
source ./lib/is_boilerplate_file.sh
source ./lib/list_posts.sh
source ./lib/list_tags.sh
source ./lib/make_gemini.sh
source ./lib/make_gophermap.sh
source ./lib/make_rss.sh
source ./lib/mrkdwn.sh
source ./lib/parse_file.sh
source ./lib/posts_with_tags.sh
source ./lib/rebuild_all_entries.sh
source ./lib/rebuild_index.sh
source ./lib/rebuild_tags.sh
source ./lib/reset.sh
source ./lib/tags_in_post.sh
source ./lib/test_markdown.sh
source ./lib/twitter_card.sh
source ./lib/usage.sh
source ./lib/write_entry.sh
### END SOURCEFILES -> DO NOT REMOVE THIS LINE
#
# MAIN
# Do not change anything here. If you want to modify the code, edit do_main()
#
do_main "$@"
# vim: set shiftwidth=4 tabstop=4 expandtab: