diff --git a/.local/share/mutt/gpg.rc b/.local/share/mutt/gpg.rc new file mode 100644 index 0000000..70e7bcc --- /dev/null +++ b/.local/share/mutt/gpg.rc @@ -0,0 +1,120 @@ +# -*-muttrc-*- +# +# Command formats for gpg. +# +# Some of the older commented-out versions of the commands use gpg-2comp from: +# http://70t.de/download/gpg-2comp.tar.gz +# +# %p The empty string when no passphrase is needed, +# the string "PGPPASSFD=0" if one is needed. +# +# This is mostly used in conditional % sequences. +# +# %f Most PGP commands operate on a single file or a file +# containing a message. %f expands to this file's name. +# +# %s When verifying signatures, there is another temporary file +# containing the detached signature. %s expands to this +# file's name. +# +# %a In "signing" contexts, this expands to the value of the +# configuration variable $pgp_sign_as, if set, otherwise +# $pgp_default_key. You probably need to +# use this within a conditional % sequence. +# +# %r In many contexts, mutt passes key IDs to pgp. %r expands to +# a list of key IDs. + + +# Section A: Key Management + +# The default key for encryption (used by $pgp_self_encrypt and +# $postpone_encrypt). +# +# It will also be used for signing unless $pgp_sign_as is set to a +# key. +# +# Unless your key does not have encryption capability, uncomment this +# line and replace the keyid with your own. +# +# set pgp_default_key="0x12345678" + +# If you have a separate signing key, or your key _only_ has signing +# capability, uncomment this line and replace the keyid with your +# signing keyid. +# +# set pgp_sign_as="0x87654321" + + +# Section B: Commands + +# Note that we explicitly set the comment armor header since GnuPG, when used +# in some localiaztion environments, generates 8bit data in that header, thereby +# breaking PGP/MIME. + +# Note from the Debian mutt maintainers: starting from 1.7.0-2 GPGME is enabled +# by default. More info in NEWS.Debian. +# THe pgp_* commands are left here for people who disable gpgme in their +# ~/.muttrc +set crypt_use_gpgme=yes + +# Note from the Debian mutt maintainers: the addition of +# "--pinentry-mode loopback" breaks gpgv1 compatiblity, if you need to use gpgv1 +# remove that statement. + +# decode application/pgp +set pgp_decode_command="gpg --status-fd=2 %?p?--passphrase-fd 0 --pinentry-mode=loopback? --no-verbose --quiet --batch --output - %f" + +# verify a pgp/mime signature +set pgp_verify_command="gpg --status-fd=2 --no-verbose --quiet --batch --output - --verify %s %f" + +# decrypt a pgp/mime attachment +set pgp_decrypt_command="gpg --status-fd=2 %?p?--passphrase-fd 0 --pinentry-mode=loopback? --no-verbose --quiet --batch --output - %f" + +# create a pgp/mime signed attachment +set pgp_sign_command="gpg --no-verbose --batch --quiet --output - %?p?--passphrase-fd 0 --pinentry-mode=loopback? --armor --detach-sign --textmode %?a?-u %a? %f" + +# create a application/pgp signed (old-style) message +set pgp_clearsign_command="gpg --no-verbose --batch --quiet --output - %?p?--passphrase-fd 0 --pinentry-mode=loopback? --armor --textmode --clearsign %?a?-u %a? %f" + +# create a pgp/mime encrypted attachment +set pgp_encrypt_only_command="/usr/lib/mutt/pgpewrap gpg --batch --quiet --no-verbose --output - --encrypt --textmode --armor --always-trust -- -r %r -- %f" + +# create a pgp/mime encrypted and signed attachment +set pgp_encrypt_sign_command="/usr/lib/mutt/pgpewrap gpg %?p?--passphrase-fd 0 --pinentry-mode=loopback? --batch --quiet --no-verbose --textmode --output - --encrypt --sign %?a?-u %a? --armor --always-trust -- -r %r -- %f" + +# import a key into the public key ring +set pgp_import_command="gpg --no-verbose --import %f" + +# export a key from the public key ring +set pgp_export_command="gpg --no-verbose --export --armor %r" + +# verify a key +set pgp_verify_key_command="gpg --verbose --batch --fingerprint --check-sigs %r" + +# read in the public key ring +# note: the second --with-fingerprint adds fingerprints to subkeys +set pgp_list_pubring_command="gpg --no-verbose --batch --quiet --with-colons --with-fingerprint --with-fingerprint --list-keys %r" + +# read in the secret key ring +# note: the second --with-fingerprint adds fingerprints to subkeys +set pgp_list_secring_command="gpg --no-verbose --batch --quiet --with-colons --with-fingerprint --with-fingerprint --list-secret-keys %r" + +# fetch keys +# set pgp_getkeys_command="pkspxycwrap %r" + +# pattern for good signature - may need to be adapted to locale! + +# set pgp_good_sign="^gpgv?: Good signature from " + +# OK, here's a version which uses gnupg's message catalog: +# set pgp_good_sign="`gettext -d gnupg -s 'Good signature from "' | tr -d '"'`" + +# This version uses --status-fd messages +set pgp_good_sign="^\\[GNUPG:\\] GOODSIG" + +# pattern to verify a decryption occurred +# This is now deprecated by pgp_check_gpg_decrypt_status_fd: +# set pgp_decryption_okay="^\\[GNUPG:\\] DECRYPTION_OKAY" +set pgp_check_gpg_decrypt_status_fd + diff --git a/.muttrc b/.muttrc new file mode 100644 index 0000000..0ea05e0 --- /dev/null +++ b/.muttrc @@ -0,0 +1,5 @@ + +source ~/.local/share/mutt/gpg.rc + + + diff --git a/bin/bb b/bin/bb new file mode 100755 index 0000000..546a066 --- /dev/null +++ b/bin/bb @@ -0,0 +1,1276 @@ +#!/usr/bin/env bash + +# BashBlog, a simple blog system written in a single bash script +# (C) Carlos Fenollosa , 2011-2016 and contributors +# https://github.com/carlesfe/bashblog/contributors +# Check out README.md for more details + +# 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=".config" + +# This function will load all the variables defined here. They might be overridden +# by the 'global_config' file contents +global_variables() { + global_software_name="BashBlog" + global_software_version="2.9" + + # Blog title + global_title="xfnw's blog" + # The typical subtitle for each blog + global_description="A blog about owens" + # The public base URL for this blog + global_url="https://xfnw.ttm.sh/blog" + + # Your name + global_author="owen" + # You can use twitter or facebook or anything for global_author_url + global_author_url="http://xfnw.ttm.sh" + # Your email + global_email="xfnw+likes+to+blog+lol@ttm.sh" + + # CC by-nc-nd is a good starting point, you can change this to "©" for Copyright + global_license="CC by-sa" + + # If you have a Google Analytics ID (UA-XXXXX) and wish to use the standard + # embedding code, put it on global_analytics + # If you have custom analytics code (i.e. non-google) or want to use the Universal + # code, leave global_analytics empty and specify a global_analytics_file + global_analytics="" + global_analytics_file="" + + # 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" + # Default search page, where tweets more than a week old are hidden + global_twitter_search="twitter" + + # Change this to your disqus username to use disqus for comments + global_disqus_username="" + + + # Blog generated files + # index page of blog (it is usually good to use "index.html" here) + index_file="index.html" + number_of_index_articles="8" + # global archive + archive_index="all_posts.html" + tags_index="all_tags.html" + + # 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") + non_blogpost_files=() + + # feed file (rss in this case) + blog_feed="feed.rss" + number_of_feed_articles="10" + # "cut" blog entry when putting it to index page. Leave blank for full articles in front page + # i.e. include only up to first '
', 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='
' + # 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 tag + # and before the actual blog content + body_begin_file="" + # extra content to add just before we close + body_end_file="" + # extra content to ONLY on the index page AFTER `body_begin_file` contents + # and before the actual content + body_begin_file_index="" + # CSS files to include on every page, f.ex. css_include=('main.css' 'blog.css') + # leave empty to use generated + css_include=() + # HTML files to exclude from index, f.ex. post_exclude=('imprint.html 'aboutme.html') + 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="View more posts" + # "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 to the index page" + # "Subscribe" (used on bottom of index page, it is link to RSS feed) + template_subscribe="Subscribe" + # "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="<Type your comment here but please leave the URL so that other people can follow the comments>" + + # 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' + [[ -f Markdown.pl ]] && markdown_bin=./Markdown.pl || markdown_bin=$(which cmark 2>/dev/null || which markdown 2>/dev/null) +} + +# 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 +} + + +# Test if the markdown script is working correctly +test_markdown() { + [[ -n $markdown_bin ]] && + ( + [[ $("$markdown_bin" <<< $'line 1\n\nline 2') == $'

line 1

\n\n

line 2

' ]] || + [[ $("$markdown_bin" <<< $'line 1\n\nline 2') == $'

line 1

\n

line 2

' ]] + ) +} + + +# Parse a Markdown file into HTML and return the generated file +markdown() { + out=${1%.md}.html + while [[ -f $out ]]; do out=${out%.html}.$RANDOM.html; done + $markdown_bin "$1" > "$out" + echo "$out" +} + + +# Prints the required google analytics code +google_analytics() { + [[ -z $global_analytics && -z $global_analytics_file ]] && return + + if [[ -z $global_analytics_file ]]; then + echo "" + else + cat "$global_analytics_file" + fi +} + +# Prints the required code for disqus comments +disqus_body() { + [[ -z $global_disqus_username ]] && return + + echo '
+ + + comments powered by Disqus' +} + +# Prints the required code for disqus in the footer +disqus_footer() { + [[ -z $global_disqus_username ]] && return + echo '' +} + +# 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
to +# note that this does not remove
line itself, +# so you can see if text was cut or not +get_html_file_content() { + awk "//, //{ + if (!// && !//) print + if (\"$3\" == \"cut\" && /$cut_line/){ + if (\"$2\" == \"text\") exit # no need to read further + while (getline > 0 && !//) { + if (\"$cut_tags\" == \"no\" && /^

$template_tags_line_header/ ) print + } + } + }" +} + +# 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() { + [[ ! -f "${1%%.*}.html" ]] && echo "Can't edit post "${1%%.*}.html", did you mean to use \"bb.sh post \"?" && 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 + test_markdown + if (($? != 0)); then + echo "Markdown is not working, please edit HTML file directly." + exit + fi + # editing markdown file + $EDITOR "$1" + TMPFILE=$(markdown "$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 "/^

$template_tags_line_header/s|\\1|\\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 [[ ! -z $relevant_tags ]]; then + relevant_posts="$(posts_with_tags $relevant_tags) $filename" + rebuild_tags "$relevant_posts" "$relevant_tags" + fi +} + +# Create a Twitter summary (twitter "card") for the post +# +# $1 the post file +# $2 the title +twitter_card() { + [[ -z $global_twitter_username ]] && return + + echo "" + echo "" + echo "" # Twitter truncates at 70 char + description=$(grep -v "^

$template_tags_line_header" "$1" | sed -e 's/<[^>]*>//g' | tr '\n' ' ' | sed "s/\"/'/g" | head -c 250) + echo "" + image=$(sed -n '2,$ d; s/.*" +} + +# Adds the code needed by the twitter button +# +# $1 the post URL +twitter() { + [[ -z $global_twitter_username ]] && return + + if [[ -z $global_disqus_username ]]; then + if [[ $global_twitter_cookieless == true ]]; then + id=$RANDOM + + search_engine="https://twitter.com/search?q=" + + echo "

$template_comments $template_twitter_button " + echo " 

" + return; + else + echo "

$template_comments "; + fi + else + echo "

$template_comments  " + fi + + echo "$template_twitter_button " + echo "

" +} + +# 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 && 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() { + 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" | "$tags_index" | "$footer_file" | "$header_file" | "$global_analytics_file" | "$prefix_tags"* ) + return 0 ;; + ( * ) # Check for excluded + for excl in "${html_exclude[@]}"; do + [[ $name == "$excl" ]] && return 0 + done + return 1 ;; + esac +} + +# Adds all the bells and whistles to format the html page +# Every blog post is marked with a and +# which is parsed afterwards in the other functions. There is also a marker +# 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() { + content=$1 + filename=$2 + index=$3 + title=$4 + timestamp=$5 + author=$6 + + # Create the actual blog post + # html, head + { + cat ".header.html" + echo "$title" + google_analytics + twitter_card "$content" "$title" + echo "" + # stuff to add before the actual body content + [[ -n $body_begin_file ]] && cat "$body_begin_file" + [[ $filename = $index_file* ]] && [[ -n $body_begin_file_index ]] && cat "$body_begin_file_index" + # body divs + echo '
' + echo '
' + # blog title + echo '
' + cat .title.html + echo '
' # title, header, headerholder + echo '
' + + file_url=${filename#./} + file_url=${file_url%.rebuilt} # Get the correct URL when rebuilding + # one blog entry + if [[ $index == no ]]; then + echo '' # marks the beginning of the whole post + echo "

" + # remove possible

's on the title because of markdown conversion + title=${title//

/} + title=${title//<\/p>/} + echo "$title" + echo '

' + if [[ -z $timestamp ]]; then + echo "" + else + echo "" + fi + if [[ -z $timestamp ]]; then + echo -n "
$(LC_ALL=$date_locale date +"$date_format")" + else + echo -n "
$(LC_ALL=$date_locale date +"$date_format" --date="$timestamp")" + fi + [[ -n $author ]] && echo -e " — \n$author" + echo "
" + echo '' # This marks the text body, after the title, date... + fi + cat "$content" # Actual content + if [[ $index == no ]]; then + echo -e '\n' + + twitter "$global_url/$file_url" + + echo '' # absolute end of the post + fi + + echo '
' # content + + # Add disqus commments except for index and all_posts pages + [[ $index == no ]] && disqus_body + + # page footer + cat .footer.html + # close divs + echo '
' # divbody and divbodyholder + disqus_footer + [[ -n $body_end_file ]] && cat "$body_end_file" + echo '' + } > "$filename" +} + +# 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() { + # Read for the title and check that the filename is ok + title="" + while IFS='' read -r line; do + if [[ -z $title ]]; then + # remove extra

and

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 == "

$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 "

$template_tags_line_header " >> "$content" + for item in "${array[@]}"; do + echo -n "$item, " + 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" +} + +# Manages the creation of the text file and the parsing to html file +# also the drafts +write_entry() { + 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 + test_markdown + if (($? != 0)); 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" +

The rest of the text file is an html blog post. The process will continue as soon +as you exit your editor.

+ +

$template_tags_line_header keep-this-tag-format, tags-are-optional, example

+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=$(markdown "$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 + relevant_posts="$(posts_with_tags $relevant_tags) $filename" + rebuild_tags "$relevant_posts" "$relevant_tags" + fi +} + +# Create an index page with all the posts +all_posts() { + echo -n "Creating an index page with all the posts " + contentfile=$archive_index.$RANDOM + while [[ -f $contentfile ]]; do + contentfile=$archive_index.$RANDOM + done + + { + echo "

$template_archive_title

" + 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 "" # Don't close ul before first header + echo "

$month

" + echo "
    " + prev_month=$month + fi + # Title + title=$(get_post_title "$i") + echo -n "
  • $title —" + # Date + date=$(LC_ALL=$date_locale date -r "$i" +"$date_format") + echo " $date
  • " + done < <(ls -t ./*.html) + echo "" 1>&3 + echo "
" + echo "" + } 3>&1 >"$contentfile" + + create_html_page "$contentfile" "$archive_index.tmp" yes "$global_title — $template_archive_title" "$global_author" + mv "$archive_index.tmp" "$archive_index" + chmod 644 "$archive_index" + rm "$contentfile" +} + +# Create an index page with all the tags +all_tags() { + echo -n "Creating an index page with all the tags " + contentfile=$tags_index.$RANDOM + while [[ -f $contentfile ]]; do + contentfile=$tags_index.$RANDOM + done + + { + echo "

$template_tags_title

" + echo "
    " + 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 "
  • $tagname — $nposts $word
  • " + done + echo "" 1>&3 + echo "
" + echo "" + } 3>&1 > "$contentfile" + + create_html_page "$contentfile" "$tags_index.tmp" yes "$global_title — $template_tags_title" "$global_author" + mv "$tags_index.tmp" "$tags_index" + chmod 644 "$tags_index" + rm "$contentfile" +} + +# Generate the index.html with the content of the latest posts +rebuild_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 \"

$template_read_more

\" ; 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 "" + } 3>&1 >"$contentfile" + + echo "" + + create_html_page "$contentfile" "$newindexfile" yes "$global_title" "$global_author" + rm "$contentfile" + mv "$newindexfile" "$index_file" + chmod 644 "$index_file" +} + +# 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() { + sed -n "/^

$template_tags_line_header/{s/^

$template_tags_line_header//;s/<[^>]*>//g;s/[ ,]\+/ /g;p;}" "$1" | tr ', ' ' ' +} + +# 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 '/^

/{s/.*href="\([^"]*\)">.*/\1/;p;}' "$@" 2> /dev/null +} + +# 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() { + 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) + 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 \"

$template_read_more

\" ; 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 — $template_tag_title \"$tagname\"" "$global_author" + rm "$i" + done < <(ls -t ./"$prefix_tags"*.tmp.html 2>/dev/null) + echo +} + +# Return the post title +# +# $1 the html file +get_post_title() { + awk '/

/, /<\/a><\/h3>/{if (!/

/ && !/<\/a><\/h3>/) print}' "$1" +} + +# Return the post author +# +# $1 the html file +get_post_author() { + awk '/
.+/, //{if (!/
.+/ && !//) print}' "$1" | sed 's/<\/div>//g' +} + +# Displays a list of the tags +# +# $2 if "-n", tags will be sorted by number of posts +list_tags() { + if [[ $2 == -n ]]; then do_sort=1; else do_sort=0; fi + + ls ./$prefix_tags*.html &> /dev/null + (($? != 0)) && echo "No posts yet. Use 'bb.sh post' to create one" && return + + 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 +} + +# Displays a list of the posts +list_posts() { + ls ./*.html &> /dev/null + (($? != 0)) && echo "No posts yet. Use 'bb.sh post' to create one" && return + + 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 "#" +} + +# Generate the feed file +make_rss() { + 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 '' + echo '' + echo "$global_title$global_url/$index_file" + echo "$global_descriptionen" + echo "$pubdate" + echo "$pubdate" + echo "" + + 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 '' + get_post_title "$i" + echo '$global_url/${i#./}" + echo "$global_url/$i" + echo "$(get_post_author "$i")" + echo "$(LC_ALL=C date -r "$i" +"$date_format_full")" + + n=$(( n + 1 )) + done < <(ls -t ./*.html) + + echo '' + } 3>&1 >"$rssfile" + echo "" + + mv "$rssfile" "$blog_feed" + chmod 644 "$blog_feed" +} + +# generate headers, footers, etc +create_includes() { + { + echo "

$global_title

" + echo "
$global_description
" + } > ".title.html" + + if [[ -f $header_file ]]; then cp "$header_file" .header.html + else { + echo '' + echo '' + echo '' + echo '' + printf '\n' "${css_include[@]}" + if [[ -z $global_feedburner ]]; then + echo "" + else + echo "" + fi + } > ".header.html" + fi + + if [[ -f $footer_file ]]; then cp "$footer_file" .footer.html + else { + protected_mail=${global_email//@/@} + protected_mail=${protected_mail//./.} + echo "
$global_license $global_author$protected_mail
" + echo 'Generated with bashblog, a single bash script to easily create blogs like this one
' + } >> ".footer.html" + fi +} + +# Delete the temporarily generated include files +delete_includes() { + rm ".title.html" ".footer.html" ".header.html" +} + +# 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-weight: 100;} + a.ablack{color:#cdcdcd !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: sans-serif; + margin: 0; + padding: 0; + background-color: #141415; + color: #cdcdcd; +} + +#divbodyholder { + padding: 5px; + width: 100%; + max-width: 874px; + margin: 24px auto; +} + +#divbody { + padding: 0px 48px 24px 48px; + top: 0; +} + +.headerholder { +} + +.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: 100; + margin-top: 32px; + margin-bottom: 0; +} + +.clear { + clear: both; +} + +#footer { + padding-top: 10px; + text-align: center; + font-size: small; + font-family: "Courier New","Courier",monospace; +} + +a { + text-decoration: none; + color: #acc !important; +} + +a:hover { + text-decoration: underline; +} + +a:visited { + color: #acc !important; +} + +blockquote { + border-left: solid 4px #cdcdcd; + margin-left: 12px; + padding: 12px 12px 12px 24px; +} + +blockquote img { + margin: 12px 0px; +} + +blockquote iframe { + margin: 12px 0px; +} + + +' > main.css + fi +} + +# Regenerates all the single post entries, keeping the post content but modifying +# the title, html structure, etc +rebuild_all_entries() { + 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 '// { 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 "" +} + +# Displays the help +usage() { + echo "$global_software_name v$global_software_version" + echo "Usage: $0 command [filename]" + echo "" + echo "Commands:" + echo " post [-html] [filename] insert a new blog post, or the filename of a draft to continue editing it" + echo " it tries to use markdown by default, and falls back to HTML if it's not available." + echo " use '-html' to override it and edit the post as HTML even when markdown is available" + echo " edit [-n|-f] [filename] edit an already published .html or .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 " use '-f' to edit full html file, instead of just text part (also preserves name)" + 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 open $0 in a code editor and read the header and comments" +} + +# 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 +} + +# Detects if GNU date is installed +date_version_detect() { + date --version >/dev/null 2>&1 + if (($? != 0)); then + # date utility is BSD. Test if gdate is installed + if gdate --version >/dev/null 2>&1 ; then + date() { + gdate "$@" + } + else + # BSD date + 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 +} + +# Main function +# Encapsulated on its own function for readability purposes +# +# $1 command to run +# $2 file name of a draft to continue editing (optional) +do_main() { + # Detect if using BSD date or GNU date + date_version_detect + # Load default configuration, then override settings with the config file + global_variables + [[ -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 && exit + + [[ $1 == list ]] && + list_posts && exit + + [[ $1 == tags ]] && + list_tags "$@" && exit + + 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" + exit + 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 && exit + + 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 + delete_includes +} + + +# +# 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: diff --git a/bin/bb-light b/bin/bb-light new file mode 100644 index 0000000..bf08605 --- /dev/null +++ b/bin/bb-light @@ -0,0 +1,1277 @@ +#!/usr/bin/env bash + +# BashBlog, a simple blog system written in a single bash script +# (C) Carlos Fenollosa , 2011-2016 and contributors +# https://github.com/carlesfe/bashblog/contributors +# Check out README.md for more details + +# 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=".config" + +# This function will load all the variables defined here. They might be overridden +# by the 'global_config' file contents +global_variables() { + global_software_name="BashBlog" + global_software_version="2.9" + + # Blog title + global_title="xfnw on institute" + # The typical subtitle for each blog + global_description="very educational" + # The public base URL for this blog + global_url="https://xfnw.tilde.institute" + + # Your name + global_author="owen" + # You can use twitter or facebook or anything for global_author_url + global_author_url="http://xfnw.ttm.sh" + # Your email + global_email="xfnw+likes+to+blog+lol@ttm.sh" + + # CC by-nc-nd is a good starting point, you can change this to "©" for Copyright + global_license="CC by-sa" + + # If you have a Google Analytics ID (UA-XXXXX) and wish to use the standard + # embedding code, put it on global_analytics + # If you have custom analytics code (i.e. non-google) or want to use the Universal + # code, leave global_analytics empty and specify a global_analytics_file + global_analytics="" + global_analytics_file="" + + # 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" + # Default search page, where tweets more than a week old are hidden + global_twitter_search="twitter" + + # Change this to your disqus username to use disqus for comments + global_disqus_username="" + + + # Blog generated files + # index page of blog (it is usually good to use "index.html" here) + index_file="index.html" + number_of_index_articles="8" + # global archive + archive_index="all_posts.html" + tags_index="all_tags.html" + + # 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") + non_blogpost_files=() + + # feed file (rss in this case) + blog_feed="feed.rss" + number_of_feed_articles="10" + # "cut" blog entry when putting it to index page. Leave blank for full articles in front page + # i.e. include only up to first '
', 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='
' + # 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 tag + # and before the actual blog content + body_begin_file="" + # extra content to add just before we close + body_end_file="" + # extra content to ONLY on the index page AFTER `body_begin_file` contents + # and before the actual content + body_begin_file_index="" + # CSS files to include on every page, f.ex. css_include=('main.css' 'blog.css') + # leave empty to use generated + css_include=() + # HTML files to exclude from index, f.ex. post_exclude=('imprint.html 'aboutme.html') + 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="View more posts" + # "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 to the index page" + # "Subscribe" (used on bottom of index page, it is link to RSS feed) + template_subscribe="Subscribe" + # "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="<Type your comment here but please leave the URL so that other people can follow the comments>" + + # 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' + [[ -f Markdown.pl ]] && markdown_bin=./Markdown.pl || markdown_bin=$(which cmark 2>/dev/null || which markdown 2>/dev/null) +} + +# 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 +} + + +# Test if the markdown script is working correctly +test_markdown() { + [[ -n $markdown_bin ]] && + ( + [[ $("$markdown_bin" <<< $'line 1\n\nline 2') == $'

line 1

\n\n

line 2

' ]] || + [[ $("$markdown_bin" <<< $'line 1\n\nline 2') == $'

line 1

\n

line 2

' ]] + ) +} + + +# Parse a Markdown file into HTML and return the generated file +markdown() { + out=${1%.md}.html + while [[ -f $out ]]; do out=${out%.html}.$RANDOM.html; done + $markdown_bin "$1" > "$out" + echo "$out" +} + + +# Prints the required google analytics code +google_analytics() { + [[ -z $global_analytics && -z $global_analytics_file ]] && return + + if [[ -z $global_analytics_file ]]; then + echo "" + else + cat "$global_analytics_file" + fi +} + +# Prints the required code for disqus comments +disqus_body() { + [[ -z $global_disqus_username ]] && return + + echo '
+ + + comments powered by Disqus' +} + +# Prints the required code for disqus in the footer +disqus_footer() { + [[ -z $global_disqus_username ]] && return + echo '' +} + +# 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
to +# note that this does not remove
line itself, +# so you can see if text was cut or not +get_html_file_content() { + awk "//, //{ + if (!// && !//) print + if (\"$3\" == \"cut\" && /$cut_line/){ + if (\"$2\" == \"text\") exit # no need to read further + while (getline > 0 && !//) { + if (\"$cut_tags\" == \"no\" && /^

$template_tags_line_header/ ) print + } + } + }" +} + +# 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() { + [[ ! -f "${1%%.*}.html" ]] && echo "Can't edit post "${1%%.*}.html", did you mean to use \"bb.sh post \"?" && 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 + test_markdown + if (($? != 0)); then + echo "Markdown is not working, please edit HTML file directly." + exit + fi + # editing markdown file + $EDITOR "$1" + TMPFILE=$(markdown "$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 "/^

$template_tags_line_header/s|\\1|\\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 [[ ! -z $relevant_tags ]]; then + relevant_posts="$(posts_with_tags $relevant_tags) $filename" + rebuild_tags "$relevant_posts" "$relevant_tags" + fi +} + +# Create a Twitter summary (twitter "card") for the post +# +# $1 the post file +# $2 the title +twitter_card() { + [[ -z $global_twitter_username ]] && return + + echo "" + echo "" + echo "" # Twitter truncates at 70 char + description=$(grep -v "^

$template_tags_line_header" "$1" | sed -e 's/<[^>]*>//g' | tr '\n' ' ' | sed "s/\"/'/g" | head -c 250) + echo "" + image=$(sed -n '2,$ d; s/.*" +} + +# Adds the code needed by the twitter button +# +# $1 the post URL +twitter() { + [[ -z $global_twitter_username ]] && return + + if [[ -z $global_disqus_username ]]; then + if [[ $global_twitter_cookieless == true ]]; then + id=$RANDOM + + search_engine="https://twitter.com/search?q=" + + echo "

$template_comments $template_twitter_button " + echo " 

" + return; + else + echo "

$template_comments "; + fi + else + echo "

$template_comments  " + fi + + echo "$template_twitter_button " + echo "

" +} + +# 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 && 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() { + 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" | "$tags_index" | "$footer_file" | "$header_file" | "$global_analytics_file" | "$prefix_tags"* ) + return 0 ;; + ( * ) # Check for excluded + for excl in "${html_exclude[@]}"; do + [[ $name == "$excl" ]] && return 0 + done + return 1 ;; + esac +} + +# Adds all the bells and whistles to format the html page +# Every blog post is marked with a and +# which is parsed afterwards in the other functions. There is also a marker +# 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() { + content=$1 + filename=$2 + index=$3 + title=$4 + timestamp=$5 + author=$6 + + # Create the actual blog post + # html, head + { + cat ".header.html" + echo "$title" + google_analytics + twitter_card "$content" "$title" + echo "" + # stuff to add before the actual body content + [[ -n $body_begin_file ]] && cat "$body_begin_file" + [[ $filename = $index_file* ]] && [[ -n $body_begin_file_index ]] && cat "$body_begin_file_index" + # body divs + echo '
' + echo '
' + # blog title + echo '
' + cat .title.html + echo '
' # title, header, headerholder + echo '
' + + file_url=${filename#./} + file_url=${file_url%.rebuilt} # Get the correct URL when rebuilding + # one blog entry + if [[ $index == no ]]; then + echo '' # marks the beginning of the whole post + echo "

" + # remove possible

's on the title because of markdown conversion + title=${title//

/} + title=${title//<\/p>/} + echo "$title" + echo '

' + if [[ -z $timestamp ]]; then + echo "" + else + echo "" + fi + if [[ -z $timestamp ]]; then + echo -n "
$(LC_ALL=$date_locale date +"$date_format")" + else + echo -n "
$(LC_ALL=$date_locale date +"$date_format" --date="$timestamp")" + fi + [[ -n $author ]] && echo -e " — \n$author" + echo "
" + echo '' # This marks the text body, after the title, date... + fi + cat "$content" # Actual content + if [[ $index == no ]]; then + echo -e '\n' + + twitter "$global_url/$file_url" + + echo '' # absolute end of the post + fi + + echo '
' # content + + # Add disqus commments except for index and all_posts pages + [[ $index == no ]] && disqus_body + + # page footer + cat .footer.html + # close divs + echo '
' # divbody and divbodyholder + disqus_footer + [[ -n $body_end_file ]] && cat "$body_end_file" + echo '' + } > "$filename" +} + +# 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() { + # Read for the title and check that the filename is ok + title="" + while IFS='' read -r line; do + if [[ -z $title ]]; then + # remove extra

and

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 == "

$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 "

$template_tags_line_header " >> "$content" + for item in "${array[@]}"; do + echo -n "$item, " + 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" +} + +# Manages the creation of the text file and the parsing to html file +# also the drafts +write_entry() { + 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 + test_markdown + if (($? != 0)); 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" +

The rest of the text file is an html blog post. The process will continue as soon +as you exit your editor.

+ +

$template_tags_line_header keep-this-tag-format, tags-are-optional, example

+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=$(markdown "$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 + relevant_posts="$(posts_with_tags $relevant_tags) $filename" + rebuild_tags "$relevant_posts" "$relevant_tags" + fi +} + +# Create an index page with all the posts +all_posts() { + echo -n "Creating an index page with all the posts " + contentfile=$archive_index.$RANDOM + while [[ -f $contentfile ]]; do + contentfile=$archive_index.$RANDOM + done + + { + echo "

$template_archive_title

" + 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 "" # Don't close ul before first header + echo "

$month

" + echo "
    " + prev_month=$month + fi + # Title + title=$(get_post_title "$i") + echo -n "
  • $title —" + # Date + date=$(LC_ALL=$date_locale date -r "$i" +"$date_format") + echo " $date
  • " + done < <(ls -t ./*.html) + echo "" 1>&3 + echo "
" + echo "" + } 3>&1 >"$contentfile" + + create_html_page "$contentfile" "$archive_index.tmp" yes "$global_title — $template_archive_title" "$global_author" + mv "$archive_index.tmp" "$archive_index" + chmod 644 "$archive_index" + rm "$contentfile" +} + +# Create an index page with all the tags +all_tags() { + echo -n "Creating an index page with all the tags " + contentfile=$tags_index.$RANDOM + while [[ -f $contentfile ]]; do + contentfile=$tags_index.$RANDOM + done + + { + echo "

$template_tags_title

" + echo "
    " + 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 "
  • $tagname — $nposts $word
  • " + done + echo "" 1>&3 + echo "
" + echo "" + } 3>&1 > "$contentfile" + + create_html_page "$contentfile" "$tags_index.tmp" yes "$global_title — $template_tags_title" "$global_author" + mv "$tags_index.tmp" "$tags_index" + chmod 644 "$tags_index" + rm "$contentfile" +} + +# Generate the index.html with the content of the latest posts +rebuild_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 \"

$template_read_more

\" ; 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 "" + } 3>&1 >"$contentfile" + + echo "" + + create_html_page "$contentfile" "$newindexfile" yes "$global_title" "$global_author" + rm "$contentfile" + mv "$newindexfile" "$index_file" + chmod 644 "$index_file" +} + +# 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() { + sed -n "/^

$template_tags_line_header/{s/^

$template_tags_line_header//;s/<[^>]*>//g;s/[ ,]\+/ /g;p;}" "$1" | tr ', ' ' ' +} + +# 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 '/^

/{s/.*href="\([^"]*\)">.*/\1/;p;}' "$@" 2> /dev/null +} + +# 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() { + 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) + 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 \"

$template_read_more

\" ; 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 — $template_tag_title \"$tagname\"" "$global_author" + rm "$i" + done < <(ls -t ./"$prefix_tags"*.tmp.html 2>/dev/null) + echo +} + +# Return the post title +# +# $1 the html file +get_post_title() { + awk '/

/, /<\/a><\/h3>/{if (!/

/ && !/<\/a><\/h3>/) print}' "$1" +} + +# Return the post author +# +# $1 the html file +get_post_author() { + awk '/
.+/, //{if (!/
.+/ && !//) print}' "$1" | sed 's/<\/div>//g' +} + +# Displays a list of the tags +# +# $2 if "-n", tags will be sorted by number of posts +list_tags() { + if [[ $2 == -n ]]; then do_sort=1; else do_sort=0; fi + + ls ./$prefix_tags*.html &> /dev/null + (($? != 0)) && echo "No posts yet. Use 'bb.sh post' to create one" && return + + 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 +} + +# Displays a list of the posts +list_posts() { + ls ./*.html &> /dev/null + (($? != 0)) && echo "No posts yet. Use 'bb.sh post' to create one" && return + + 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 "#" +} + +# Generate the feed file +make_rss() { + 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 '' + echo '' + echo "$global_title$global_url/$index_file" + echo "$global_descriptionen" + echo "$pubdate" + echo "$pubdate" + echo "" + + 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 '' + get_post_title "$i" + echo '$global_url/${i#./}" + echo "$global_url/$i" + echo "$(get_post_author "$i")" + echo "$(LC_ALL=C date -r "$i" +"$date_format_full")" + + n=$(( n + 1 )) + done < <(ls -t ./*.html) + + echo '' + } 3>&1 >"$rssfile" + echo "" + + mv "$rssfile" "$blog_feed" + chmod 644 "$blog_feed" +} + +# generate headers, footers, etc +create_includes() { + { + echo "

$global_title

" + echo "
$global_description
" + } > ".title.html" + + if [[ -f $header_file ]]; then cp "$header_file" .header.html + else { + echo '' + echo '' + echo '' + echo '' + printf '\n' "${css_include[@]}" + if [[ -z $global_feedburner ]]; then + echo "" + else + echo "" + fi + } > ".header.html" + fi + + if [[ -f $footer_file ]]; then cp "$footer_file" .footer.html + else { + protected_mail=${global_email//@/@} + protected_mail=${protected_mail//./.} + echo "
$global_license $global_author$protected_mail
" + echo 'Generated with bashblog, a single bash script to easily create blogs like this one
' + } >> ".footer.html" + fi +} + +# Delete the temporarily generated include files +delete_includes() { + rm ".title.html" ".footer.html" ".header.html" +} + +# 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-weight: 100;} + a.ablack{color:#141415 !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: serif; + margin: 0; + padding: 0; + background-color: #cdcdcd; + color: #141415; +} + +#divbodyholder { + padding: 5px; + width: 100%; + max-width: 874px; + margin: 24px auto; +} + +#divbody { + padding: 0px 48px 24px 48px; + top: 0; +} + +.headerholder { +} + +.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: 100; + margin-top: 32px; + margin-bottom: 0; +} + +.clear { + clear: both; +} + +#footer { + padding-top: 10px; + text-align: center; + font-size: small; + font-family: "Courier New","Courier",monospace; +} + +a { + text-decoration: none; + color: #66c !important; +} + +a:hover { + text-decoration: underline; +} + +a:visited { + color: #acc !important; +} + +blockquote { + background-color: #151516; + border-left: solid 4px #cdcdcd; + margin-left: 12px; + padding: 12px 12px 12px 24px; +} + +blockquote img { + margin: 12px 0px; +} + +blockquote iframe { + margin: 12px 0px; +} + + +' > main.css + fi +} + +# Regenerates all the single post entries, keeping the post content but modifying +# the title, html structure, etc +rebuild_all_entries() { + 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 '// { 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 "" +} + +# Displays the help +usage() { + echo "$global_software_name v$global_software_version" + echo "Usage: $0 command [filename]" + echo "" + echo "Commands:" + echo " post [-html] [filename] insert a new blog post, or the filename of a draft to continue editing it" + echo " it tries to use markdown by default, and falls back to HTML if it's not available." + echo " use '-html' to override it and edit the post as HTML even when markdown is available" + echo " edit [-n|-f] [filename] edit an already published .html or .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 " use '-f' to edit full html file, instead of just text part (also preserves name)" + 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 open $0 in a code editor and read the header and comments" +} + +# 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 +} + +# Detects if GNU date is installed +date_version_detect() { + date --version >/dev/null 2>&1 + if (($? != 0)); then + # date utility is BSD. Test if gdate is installed + if gdate --version >/dev/null 2>&1 ; then + date() { + gdate "$@" + } + else + # BSD date + 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 +} + +# Main function +# Encapsulated on its own function for readability purposes +# +# $1 command to run +# $2 file name of a draft to continue editing (optional) +do_main() { + # Detect if using BSD date or GNU date + date_version_detect + # Load default configuration, then override settings with the config file + global_variables + [[ -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 && exit + + [[ $1 == list ]] && + list_posts && exit + + [[ $1 == tags ]] && + list_tags "$@" && exit + + 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" + exit + 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 && exit + + 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 + delete_includes +} + + +# +# 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: