#! /bin/bash
############################################################
#  NanoBlogger 3.1 Copyright 2004 n1xt3r (Kevin R. Wood)   #
############################################################

# nanoblogger's version.
VERSION="3.1"

# nanoblogger's install directory.
BASE_DIR=`dirname $0`

# create a semi ISO 8601 formatted timestamp for archives
# used explicitly, please don't edit unless you know what you're doing.
NB_TimeStamp(){ date "+%Y-%m-%dT%H_%M_%S"; }

# prompt to use when asking something.
NB_PROMPT=": "

# set current path
CURR_PATH="$PWD"

# directory to store archives of weblog
ARCHIVES_DIR=archives

# directory to store cached data of weblog
CACHE_DIR=cache

# directory to store data of weblog
DATA_DIR=data

# directory to store parts of weblog
PARTS_DIR=parts

# the templates directory name
TEMPLATE_DIR=templates

# the temp directory
TEMP_DIR=/tmp

# default verbosity, 0 = silent
VERBOSE=1

# automatically set time zone using GNU specific, 'date +%z'
tzd_mm=`date +%z |cut -c4-5`
AUTO_TZD=`date +%z |sed 's/..$/\:'$tzd_mm'/'`

# letter to prepend to entry's html id tag
x_id=e

nb_msg(){
[ "$VERBOSE" = 0 ] ||
	cat <<-EOF
		$@
	EOF
}

# function to die with a message
die(){
cat <<-EOF
	$@
EOF
exit 1
}

# die without the base directory
[ -d "$BASE_DIR" ] ||
	die "`basename $0`: $BASE_DIR doesn't exist! goodbye."

# cleanup for special temp files
SCRATCH_FILE="$TEMP_DIR/nb_scratch$$"
tmp_files="$TEMP_DIR/nb_entry$$.* $SCRATCH_FILE*"
[ -z "$tmp_files" ] || trap "rm -fr $tmp_files; exit" 0 1 2 3 15

# loads global and user configurations
load_config(){
# USR_BLOGDIR overrides BLOG_DIR
[ ! -z "$USR_BLOGDIR" ] && BLOG_DIR="$USR_BLOGDIR"
# auto-detect blog.conf from our CWD
[ -f "$PWD/blog.conf" ] && BLOG_DIR="$PWD"
: ${BLOG_CONF:="$BLOG_DIR/blog.conf"}
# USR_BLOGCONF overrides BLOG_CONF
[ -f "$USR_BLOGCONF" ] && BLOG_CONF="$USR_BLOGCONF"
# load weblog config file
[ -f "$BLOG_CONF" ] && . "$BLOG_CONF"
# set data directory
[ -d "$BLOG_DIR/$DATA_DIR" ] && NB_DATA_DIR="$BLOG_DIR/$DATA_DIR"
# USR_DATADIR overrides NB_DATA_DIR, DATA_DIR
[ -d "$USR_DATADIR" ] && NB_DATA_DIR="$USR_DATADIR"
# set template directory
[ -d "$BLOG_DIR/$TEMPLATE_DIR" ] && NB_TEMPLATE_DIR="$BLOG_DIR/$TEMPLATE_DIR"
# BLOG_TEMPLATE_DIR overrides NB_TEMPLATE_DIR
[ -d "$BLOG_TEMPLATE_DIR" ] && NB_TEMPLATE_DIR="$BLOG_TEMPLATE_DIR"
# USR_TEMPLATE_DIR overrides NB_TEMPLATE_DIR, BLOG_TEMPLATE_DIR
[ -d "$USR_TEMPLATE_DIR" ] && NB_TEMPLATE_DIR="$USR_TEMPLATE_DIR"
# where plugins are located and run by default
: ${PLUGINS_DIR:=$BASE_DIR/plugins}
# default to $USER for author
: ${BLOG_AUTHOR:=$USER}
# default to lynx for browser
: ${BROWSER:=lynx}
# default to vi for editor
: ${EDITOR:=vi}
# default to txt for datatype suffix
: ${NB_DATATYPE:=txt}
# default to db for database suffix
: ${NB_DBTYPE:=db}
# default to html for page suffix
: ${NB_FILETYPE:=html}
# default to xml for feed suffix
: ${NB_SYND_FILETYPE:=xml}
# default to AUTO_TZD for iso dates
: ${BLOG_TZD:=$AUTO_TZD}
# check if we want absolute links
if [ "$ABSOLUTE_LINKS" = 1 ]; then
	BASE_URL="$BLOG_URL/"; ARCHIVES_PATH="${BASE_URL}$ARCHIVES_DIR/"
else
	BASE_URL="../"
fi
# default to max filter for query mode
: ${QUERY_MODE:=max}
# defaults for maximum entries to display on each page
: ${MAX_ENTRIES:=10}; : ${MAX_PAGE_ENTRIES:=$MAX_ENTRIES}
# default for previous and next page symbols, using html entities
: ${NB_NextPage:=[&#62;&#62;]}
: ${NB_PrevPage:=[&#60;&#60;]}
}

# deconfigure, clear some auto-default variables
deconfig(){ PLUGINS_DIR=; BLOG_AUTHOR=; NB_DATATYPE=; NB_DBTYPE=; NB_FILETYPE=; NB_SYND_FILETYPE=; BLOG_TZD=; QUERY_MODE=; }

# filter custom date format for a new entry
filter_dateformat(){
FILTER_VAR="$1"
# use date's defaults, when no date format is specified
if [ ! -z "$FILTER_VAR" ]; then
	[ ! -z "$DATE_LOCALE" ] && LC_ALL="$DATE_LOCALE" date +"$FILTER_VAR"
	[ -z "$DATE_LOCALE" ] && date +"$FILTER_VAR"
else
	[ ! -z "$DATE_LOCALE" ] && LC_ALL="$DATE_LOCALE" date
	[ -z "$DATE_LOCALE" ] && date
fi
}

# change suffix of file
chg_suffix(){
filename="$1"
suffix="$2"
old_suffix=`echo $filename |cut -d"." -f2`
[ ! -z "$suffix" ] && NB_FILETYPE="$suffix"
echo "$filename" |sed -e '{$ s/\.'$old_suffix'$/\.'$NB_FILETYPE'/g; }'
}

# insure a sane configuration or die
check_config(){
load_config
[ -z "$BLOG_DIR" ] && die "no weblog directory specified! goodbye."
[ ! -z "$USR_BLOGCONF" ] &&
	[ ! -f "$USR_BLOGCONF" ] && die "weblog config file '$USR_BLOGCONF' doesn't exist! goodbye."
[ ! -d "$BLOG_DIR" ] && die "weblog directory '$BLOG_DIR' doesn't exist! goodbye."
[ ! -d "$NB_DATA_DIR" ] && die "weblog's data directory '$NB_DATA_DIR' doesn't exist! goodbye."
[ ! -d "$BLOG_DIR/$CACHE_DIR" ] && die "weblog's cache directory '$BLOG_DIR/$CACHE_DIR' doesn't exist! goodbye."
[ ! -d "$NB_TEMPLATE_DIR" ] && die "weblog's templates directory '$NB_TEMPLATE_DIR' doesn't exist!. goodbye."
}

# create list of entries based on a month or interval
query_db(){
db_query="$1"
db_catquery="$2"
db_limit="$3"
db_offset="$4"
# sanitize db_limit and db_offset
db_limit=`echo "$db_limit" |sed -e '/[A-Z,a-z,\-]/d'`
db_offset=`echo "$db_offset" |sed -e '/[A-Z,a-z,\-]/d'`
: ${db_limit:=$MAX_ENTRIES}
: ${db_limit:=0}; : ${db_offset:=1}
current_month=`date "+%Y-%m"`
cd "$NB_DATA_DIR"
# get list of categories or accept a user specified list
if [ -z "$db_catquery" ] || [ "$db_catquery" = nocat ]; then
	db_catquery=
	db_categories=`for cat_db in cat_*.$NB_DBTYPE; do echo "$cat_db"; done`
else
	db_categories="$db_catquery"
fi
if [ "$db_categories" = "cat_*.$NB_DBTYPE" ]; then db_categories=; fi
	# list amount of entries based on db_limit
	filter_limit(){
		[ "$db_limit" = 0 ] && grep "." # regex hack for non-GNU versions
		[ ! -z "$db_limit" ] && sed -n "$db_offset,$db_limit"p
		db_limit=; db_offset=
		}
        filter_query(){ grep "$db_query." |sort -ru; } # allow for empty $db_query
	# list all entries
        list_db(){ for files in *.$NB_DATATYPE; do echo "$files"; done; }
	# include categorized entries
	cat_db(){
		[ -z "$db_catquery" ] && list_db
		for cat_db in $db_categories; do
			[ ! -z "$cat_db" ] &&
				grep "[\.]$NB_DATATYPE" "$cat_db"
		done
		}
if [ "$db_query" = all ] || [ -z "$db_limit" ]; then
	db_query=; DB_RESULTS=`cat_db |filter_query`
elif [ "$db_query" = current ]; then
	db_query="$current_month"; DB_RESULTS=`cat_db |filter_query`
elif [ "$db_query" = max ]; then
	db_query=; DB_RESULTS=`cat_db |filter_query |filter_limit`
else
	DB_RESULTS=`cat_db |filter_query`
fi
# try to clear bad data
if [ "$DB_RESULTS" = "$NB_DATA_DIR/*.$NB_DATATYPE" ]; then
	DB_RESULTS=
fi
db_query=; cd "$CURR_PATH"
}

# convert category number to existing category database
cat_id(){
cat_query=`echo "$cat_num" |grep '[0-9]' |sed -e '/,/ s// /g; /[A-Z,a-z\)\.-]/d'`
query_db
if [ ! -z "$cat_query" ]; then
	for cat_id in $cat_query; do
		cat_valid=`echo "$db_categories" |grep cat_$cat_id.$NB_DBTYPE`
		echo "$cat_valid"
		[ -z "$cat_valid" ] &&
			echo "bad id(s)!"
	done
fi
}

# validate category's id number
check_catid(){
cat_list=`cat_id`
for cat_db in $cat_list; do
	[ ! -f "$NB_DATA_DIR/$cat_db" ] &&
		die "invalid category id(s): $cat_num"
done
[ ! -z "$cat_num" ] && [ -z "$cat_list" ] && die "must specify a valid category id!"
}

# filter content through a template
load_template(){
BLOG_FILE="$1"
if [ -f "$BLOG_FILE" ]; then
	# prefix text/html with an X in front of each line
	BLOG_TEMPLATE=`sed -e '/^/ s//X/' < "$BLOG_FILE"`
	# remove X's and source variables into a temp file
	cat > "$SCRATCH_FILE" <<-EOF
		sed -e '/^X/ s///' <<TMPL
			$BLOG_TEMPLATE
		TMPL
	EOF
	BLOG_HTML=$(. "$SCRATCH_FILE")
else
	die "template file, '$BLOG_FILE' doesn't exist! goodbye."
fi
}

# load plugins from $PLUGINS_DIR
load_plugins(){
PLUGIN_DIR="$1"
[ ! -z "$PLUGIN_DIR" ] && PLUGIN_DIR="/$1"
# loads plugins in alpha-numeric order (0-9, a-z)
[ -d "$PLUGINS_DIR${PLUGIN_DIR}" ] &&
	for nb_plugin in "$PLUGINS_DIR${PLUGIN_DIR}"/*.sh; do
		[ -f "$nb_plugin" ] && . "$nb_plugin"
	done
}

# read file's metadata
read_metadata(){
MTAG="$1"
META_FILE="$2"
NB_Metadata=`sed -e '/^'$MTAG'[\:]/!d; /^'$MTAG'[\:][ ]/ s///' "$META_FILE"`
[ "$MTAG" = BODY ] &&
NB_Metadata=`sed -e '/^'$MTAG'[\:]/,/^[\-][\-][\-][\-][\-]/!d; /^'$MTAG'[\:]/d; /^[\-][\-][\-][\-][\-]/d' "$META_FILE"`
}

# read an entry
read_entry(){
ENTRY_FILE="$1"
if [ -f "$ENTRY_FILE" ]; then
	month=`echo "$entry" |cut -c1-7`
	NB_EntryID="$x_id$entry"
	read_metadata TITLE "$ENTRY_FILE"; NB_EntryTitle="$NB_Metadata"
	read_metadata AUTHOR "$ENTRY_FILE"; NB_EntryAuthor="$NB_Metadata"
	read_metadata DATE "$ENTRY_FILE"; NB_EntryDate="$NB_Metadata"
	read_metadata DESC "$ENTRY_FILE"; NB_EntryDescription="$NB_Metadata"
	if [ "$ENTRY_ARCHIVES" = 1 ]; then
		permalink_entry=`chg_suffix $entry`
		NB_EntryPermalink="${ARCHIVES_PATH}$permalink_entry"
	else
		NB_EntryPermalink="${ARCHIVES_PATH}$month.$NB_FILETYPE#$NB_EntryID"
	fi
	if [ "$ENTRY_FILE" -nt "$BLOG_DIR/$CACHE_DIR/$entry.body" ]; then
		read_metadata BODY "$ENTRY_FILE"; NB_EntryBody="$NB_Metadata"
		load_plugins entry/body
		echo "$NB_EntryBody" > "$BLOG_DIR/$CACHE_DIR/$entry.body"
	else
		NB_EntryBody=$(< "$BLOG_DIR/$CACHE_DIR/$entry.body")
	fi
	load_plugins entry
fi
}

# write entry to file
write_entry(){
ENTRY_FILE="$1"
[ -z "$NB_EntryBody" ] &&
	die "NB_EntryBody contains no data! ...aborting."
cat > "$ENTRY_FILE" <<-EOF
	TITLE: $NB_EntryTitle
	AUTHOR: $NB_EntryAuthor
	DATE: $NB_EntryDate
	DESC: $NB_EntryDescription
	-----
	BODY:
	$NB_EntryBody
	-----
EOF
}

# create page from source and template files
make_page(){
MKPAGE_SRCFILE="$1"
MKPAGE_TMPLFILE="$2"
MKPAGE_OUTFILE="$3"
[ ! -z "$usr_title" ] && MKPAGE_TITLE="$usr_title"
[ ! -z "$MKPAGE_TITLE" ] && NB_EntryTitle="$MKPAGE_TITLE"
if [ ! -z "$usr_srcfile" ]; then
	MKPAGE_SRCFILE="$usr_srcfile"; MKPAGE_OUTFILE="$usr_outputfile"; MKPAGE_TMPLFILE="$usr_template"
	[ -z "$usr_template" ] && MKPAGE_TMPLFILE="$NB_TEMPLATE_DIR/$MAKEPAGE_TEMPLATE"
fi
[ ! -f "$MKPAGE_SRCFILE" ] && die "source file, '$MKPAGE_SRCFILE' doesn't exist! goodbye."
[ -z "$MKPAGE_OUTFILE" ] && die "output file not specified."
[ ! -f "$MKPAGE_TMPLFILE" ] && die "template file, '$MKPAGE_TMPLFILE' doesn't exist! goodbye."
# make sure the output directory is present before writing to it
mkdir -p `dirname "$MKPAGE_OUTFILE"`
if [ "$weblog_update" = all ] || [ "$MKPAGE_SRCFILE" -nt "$MKPAGE_OUTFILE" ]; then
	# preserve content of the source, but let plugins modify it
	: ${MKPAGE_CONTENT:=$(< "$MKPAGE_SRCFILE")}
	# escape content of $NB_Entries so no execution takes place
	NB_Entries="\$MKPAGE_CONTENT"
	load_template "$MKPAGE_TMPLFILE"
	echo "$BLOG_HTML" > "$SCRATCH_FILE"
	load_template "$SCRATCH_FILE"
	echo "$BLOG_HTML" > "$MKPAGE_OUTFILE"
	nb_msg "$MKPAGE_OUTFILE"
	# load postformat plugins, but with reusable functionality
	for mkp_plugin in "$PLUGINS_DIR"/postformat/*.sh; do
		[ -f "$mkp_plugin" ] && . "$mkp_plugin"
	done
fi
MKPAGE_CONTENT=; MKPAGE_TITLE=; NB_EntryTitle=
}

# create individual page for an entry
build_permalink(){
permalink_entry=`chg_suffix $entry`
if [ "$ENTRY_ARCHIVES" = 1 ]; then
	if [ "$NB_DATA_DIR/$entry" -nt "$BLOG_DIR/$ARCHIVES_DIR/$permalink_entry" ]; then
		echo "$BLOG_HTML" > "$BLOG_DIR/$PARTS_DIR/$entry"
		make_page "$BLOG_DIR/$PARTS_DIR/$entry" "$NB_TEMPLATE_DIR/$PERMALINK_TEMPLATE" \
		"$BLOG_DIR/$ARCHIVES_DIR/$permalink_entry"
	fi
fi
}

# generate archive content
make_archive(){
query_type="$1"
db_catquery="$2"
archive_template="$3"
PARTS_FILE="$4"
db_limit="$5"
db_offset="$6"
query_db "$query_type" "$db_catquery" "$db_limit" "$db_offset"
ARCHIVE_LIST="$DB_RESULTS"
> "$PARTS_FILE"
for entry in $ARCHIVE_LIST; do
	read_entry "$NB_DATA_DIR/$entry"
	load_template "$NB_TEMPLATE_DIR/$archive_template"
	if [ ! -z "$BLOG_HTML" ]; then
		echo "$BLOG_HTML" >> "$PARTS_FILE"
		build_permalink
		BLOG_HTML=
	fi
done
}

# create archive of current month
build_monthlyarchive(){
	if [ ! -z "$DB_RESULTS" ]; then
		make_archive "$month" nocat "$ENTRY_TEMPLATE" "$BLOG_DIR/$PARTS_DIR/$month".$NB_FILETYPE
		NB_ArchiveTitle="$month"
		load_plugins archive/monthly
		make_page "$BLOG_DIR/$PARTS_DIR/$month".$NB_FILETYPE "$NB_TEMPLATE_DIR/$MONTH_TEMPLATE" \
		"$BLOG_DIR/$ARCHIVES_DIR/$month.$NB_FILETYPE"
	fi
}

# create all the monthly archives
cycle_months_for(){
	which_part="$1"
	query_db all
	MASTER_LIST="$DB_RESULTS"
	curr_year=`date +%Y`
	curr_month=`date +%m`
	# build archives seperated monthly and yearly
	ENTRY_YEARS=`echo "$MASTER_LIST" |cut -c1-4 |sort -ru`
	for yearn in $ENTRY_YEARS; do
		for monthn in 12 11 10 09 08 07 06 05 04 03 02 01; do
			ENTRY_LIST=`echo "$MASTER_LIST" |grep $yearn'[-]'$monthn'[-]' |sed 1q`
			for entry_month in $ENTRY_LIST; do
				month="$yearn-$monthn"
				query_db "$month"
				[ ! -z "$DB_RESULTS" ] && $which_part
			done
		done
	done
}

# helps update relative categories
find_categories(){
UPDATE_CATLIST="$1"
	build_catlist(){
	if [ ! -z "$cat_var" ]; then
		CAT_LIST="$cat_db"
		[ "$CAT_LIST" != "$OLD_CATLIST" ] && CAT_LIST="$OLD_CATLIST $cat_db"
		OLD_CATLIST="$CAT_LIST"
	fi
	}
# find related categories for a given set of entries
for relative_entry in $UPDATE_CATLIST; do
	query_db "$db_query"
	for cat_db in $db_categories; do
		cat_var=`grep "$relative_entry" "$NB_DATA_DIR/$cat_db"`
		build_catlist
	done
done
[ -z "$CAT_LIST" ] && CAT_LIST="$db_catquery"
if [ "$weblog_update" = all ]; then query_db; CAT_LIST="$db_categories"; fi
CAT_LIST=`for cat_id in $CAT_LIST; do echo "$cat_id"; done |sort -u`
}

# divide larger archives into multiple pages
paginate(){
page_query="$1"
page_catquery="$2"
page_items="$3"
page_template="$4"
page_outdir="$5"
page_outfile="$6"
	update_pages(){
		build_pagelist(){
		if [ ! -z "$page_num" ]; then
			[ -z "$PAGE_LIST" ] && PAGE_LIST="page$page_num"
			[ "$PAGE_LIST" != "$OLD_PAGELIST" ] && PAGE_LIST="$OLD_PAGELIST page$page_num"
			OLD_PAGELIST="$PAGE_LIST"
		fi
		}
		query_db max "$page_catquery" "$end" "$begin"
		for entry_mod in $UPDATE_LIST; do
			page_match=`echo "$DB_RESULTS" |grep "$entry_mod"`
			[ ! -z "$page_match" ] && build_pagelist
		done
	PAGE_LIST=`for page_n in $PAGE_LIST; do echo "$page_n"; done |sort -u`
	}
	page_bynumber(){
		make_archive max "$page_catquery" "$ENTRY_TEMPLATE" "$BLOG_DIR/$PARTS_DIR/$arch_page" "$end" "$begin"
		make_page "$BLOG_DIR/$PARTS_DIR/$arch_page" "$NB_TEMPLATE_DIR/$page_template" "$page_outdir/$arch_page"
	}
query_db "$page_query" "$page_catquery"
total_items=`echo "$DB_RESULTS" |grep -c "[\.]$NB_DATATYPE"`
if [ "$total_items" -gt "$page_items" -a "$page_items" != 0 ]; then
	nb_msg "paginating $page_outfile ..."
	get_pages(){ y=0; while [ "$y" -lt "$total_items" ]; do
		y=`expr "$page_items" + "$y"`; echo "$y"; done |grep -c "."; }
	total_pages=`get_pages`
	end=0; page_num=0
	while [ "$end" -lt "$total_items" ]; do
		begin=`expr "$end" + 1`; end=`expr "$page_items" + "$end"`
		page_num=`expr "$page_num" + 1`
		prev_num=`expr "$page_num" - 1`
		next_num=`expr "$page_num" + 1`
		arch_page="$page_outfile"
		arch_name=`echo "$page_outfile" |cut -d"." -f 1`
		prev_page=`chg_suffix "$arch_name-page$prev_num".no`
		next_page=`chg_suffix "$arch_name-page$next_num".no`
		[ "$page_num" -gt 1 ] &&
			arch_page=`chg_suffix "$arch_name-page$page_num".no`
		> "$SCRATCH_FILE"
		echo '<br />' >> "$SCRATCH_FILE"
		[ "$prev_num" = 1 ] &&
			echo '<a href="'$page_outfile'">'$NB_PrevPage'</a>' >> "$SCRATCH_FILE"
		[ "$prev_num" -gt 1 ] &&
			echo '<a href="'$prev_page'">'$NB_PrevPage'</a>' >> "$SCRATCH_FILE"
		for ((i=1; i <= $total_pages; i++)); do
			[ "$i" = 1 ] && page="$page_outfile" ||
				page=`chg_suffix "$arch_name-page$i".no`
			if [ "$i" = "$page_num" ]; then
				echo '['$i']' >> "$SCRATCH_FILE"
			else
				echo '<a href="'$page'">['$i']</a>' >> "$SCRATCH_FILE"
			fi
		done
		! [ "$next_num" -gt "$total_pages" ] &&
				echo '<a href="'$next_page'">'$NB_NextPage'</a>' >> "$SCRATCH_FILE"
		NB_PageLinks=$(< "$SCRATCH_FILE")
		if [ ! -z "$UPDATE_LIST" -a ! -z "$edit_num" ]; then
			update_pages
			for page_mod in $PAGE_LIST; do
				[ "page$page_num" = "$page_mod" ] && page_bynumber
			done
		else
		page_bynumber
		fi
		NB_PageLinks=
	done
else
	make_archive "$page_query" "$db_catquery" "$ENTRY_TEMPLATE" "$BLOG_DIR/$PARTS_DIR/$page_outfile"
	make_page "$BLOG_DIR/$PARTS_DIR/$page_outfile" "$NB_TEMPLATE_DIR/$page_template" \
	"$page_outdir/$page_outfile"
fi
}

# build category archives
build_catarchives(){
db_categories="$CAT_LIST"
if [ ! -z "$db_categories" ]; then
	for cat_arch in $db_categories; do
		if [ -f "$NB_DATA_DIR/$cat_arch" ]; then
			NB_ArchiveTitle=`sed 1q "$NB_DATA_DIR/$cat_arch"`
			NB_ArchivePrefix=`echo "$cat_arch" |cut -d"." -f 1`
			paginate all "$cat_arch" "$MAX_PAGE_ENTRIES" "$CATEGORY_TEMPLATE" "$BLOG_DIR/$ARCHIVES_DIR" "$NB_ArchivePrefix.$NB_FILETYPE"
		fi
	done
fi
}

# create/update archives
build_archives(){
load_plugins archive
nb_msg "generating archives ..."
build_catarchives
if [ "$weblog_update" = all ]; then
	cycle_months_for build_monthlyarchive
else
	# update cache
	query_db all
	> "$SCRATCH_FILE".cache_list
	for entry in $DB_RESULTS; do
		[ "$NB_DATA_DIR/$entry" -nt "$BLOG_DIR/$CACHE_DIR/$entry.body" ] &&
			echo "$entry" >> "$SCRATCH_FILE".cache_list
	done
	[ ! -z "$UPDATE_LIST" ] && echo "$UPDATE_LIST" >> "$SCRATCH_FILE".cache_list
	UPDATE_LIST=$(< "$SCRATCH_FILE.cache_list")
	# rebuild current month archive
	month=`date "+%Y-%m"`; this_month="$month"
	query_db "$month"; build_monthlyarchive

	# update relative monthly archives
	MOD_MONTHS=`echo "$UPDATE_LIST" |sed -e '/^[ ]/ s///g' |cut -c1-7 |sort -ru`
	for mod_month in $MOD_MONTHS; do
		if [ "$mod_month" != "$this_month" ]; then
			month="$mod_month"
			query_db "$month"; build_monthlyarchive
		fi
	done
	# update relative entry archives
	for mod_entry in $UPDATE_LIST; do
		if [ "$ENTRY_ARCHIVES" = 1 ] && [ -f "$NB_DATA_DIR/$mod_entry" ]; then
			entry="$mod_entry"
			read_entry "$NB_DATA_DIR/$entry"
			load_template "$NB_TEMPLATE_DIR/$PERMALINKENTRY_TEMPLATE"
			if [ ! -z "$BLOG_HTML" ]; then
				echo "$BLOG_HTML" > "$BLOG_DIR/$PARTS_DIR/$entry"
				BLOG_HTML=
			fi
			build_permalink
		fi
	done
fi
}

# build the weblog
build_weblog(){
# set base for relative links
weblog_update=`echo "$1" |sed -e '/\,/d; /[\)\.\-]/d'`
[ -z "$weblog_update" ] && weblog_update="current"
# query database and check for categories
db_catquery=`cat_id`; check_catid
find_categories "$UPDATE_LIST"
if [ "$weblog_update" = all ]; then
	# remove all generated files for a clean build
	rm -fr "$BLOG_DIR/$ARCHIVES_DIR/"*."$NB_FILETYPE"
	rm -fr "$BLOG_DIR/$PARTS_DIR"/*
fi
load_plugins
# build the archives
nb_msg "generating $NB_FILETYPE pages ..."
[ "$weblog_update" != main ] && build_archives
if [ "$weblog_update" != main ] && [ "$ENTRY_ARCHIVES" = 1 ]; then
	make_archive "$weblog_update" nocat "$PERMALINKENTRY_TEMPLATE" "$BLOG_DIR/$PARTS_DIR"/permalinks.tmp
	rm -f "$BLOG_DIR/$PARTS_DIR"/permalinks.tmp
fi
# build main index
[ "$ABSOLUTE_LINKS" != "1" ] && BASE_URL="./"
nb_msg "generating main index page(s) ..."
ARCHIVES_PATH="${BASE_URL}$ARCHIVES_DIR/"
paginate "$QUERY_MODE" nocat "$MAX_ENTRIES" "$MAIN_TEMPLATE" "$BLOG_DIR" index."$NB_FILETYPE"
}

# add a new entry
add_entry(){
New_EntryFile=$(NB_TimeStamp).$NB_DATATYPE
NB_EntryDate=$(filter_dateformat "$DATE_FORMAT")
nb_msg "processing new weblog entry ..."
write_entry "$NB_DATA_DIR/$New_EntryFile"
if [ ! -z "$cat_num" ]; then
	db_catquery=`cat_id`; check_catid
	for cat_db in $db_catquery ; do 
		echo "$New_EntryFile" >> "$NB_DATA_DIR/$cat_db"
	done
fi
[ "$build_newblog" = 1 ] && weblog_update=all
build_weblog "$weblog_update"
nb_msg "new weblog entry added to '$BLOG_TITLE'."
}

# edit $BLOG_CONF
config_weblog(){
$EDITOR "$BLOG_CONF"
# check if file's been modified since opened
[ ! -N "$BLOG_CONF" ] && die "no changes were made! goodbye."
deconfig; load_config
}

# edit entry or category by id number
edit_weblog(){
if [ ! -z "$cat_num" ] && [ "$edit_num" = cat ]; then
	cat_var=`echo "$cat_num" |sed -e '/,/d'`
	[ -z "$cat_var" ] && die "must specify one category at a time when editing a title!"
	db_catquery=`cat_id`; check_catid
	query_db "$db_query" "$db_catquery"
	if [ ! -z "$usr_title" ]; then
		nb_msg "changing title to '$usr_title' for category id: $cat_num ..."
		echo "$usr_title" > "$NB_DATA_DIR/$db_catquery"
		[ ! -z "$DB_RESULTS" ] && echo "$DB_RESULTS" >> "$NB_DATA_DIR/$db_catquery"
		UPDATE_LIST="$DB_RESULTS"
		build_weblog; exit 0
	else
		die "no changes were made! goodbye."
	fi
fi
NUMVAR=$(echo "$edit_num" |grep '[0-9]' |sed -e '/\,/ s// /g; /[A-Z,a-z\)\.\-]/d')
[ -z "$NUMVAR" ] && die "must specify a valid entry id."
db_catquery=`cat_id`; check_catid
query_db "$db_query" "$db_catquery"; ENTRY_LIST="$DB_RESULTS"
for entry_id in $NUMVAR; do
	Edit_EntryFile=`echo "$ENTRY_LIST" |sed -n "$entry_id"p`
	[ ! -f "$NB_DATA_DIR/$Edit_EntryFile" ] && die "invalid entry id(s): $edit_num"
done
> "$SCRATCH_FILE"
for entry_id in $NUMVAR; do
	Edit_EntryFile=`echo "$ENTRY_LIST" |sed -n "$entry_id"p`
	$EDITOR "$NB_DATA_DIR/$Edit_EntryFile"
	if [ -N "$NB_DATA_DIR/$Edit_EntryFile" ]; then
		echo "$Edit_EntryFile" >> "$SCRATCH_FILE"
	fi
done
UPDATE_LIST=$(< "$SCRATCH_FILE")
if [ ! -z "$UPDATE_LIST" ]; then
	nb_msg "processing modified weblog entries ..."
	find_categories "$UPDATE_LIST"; build_weblog
else
	die "no changes were made! goodbye."
fi
}

# delete entry or category by id number
delete_weblog(){
db_catquery=`cat_id`; check_catid
if [ ! -z "$cat_num" ]; then
	cat_list="$db_catquery"
	cat_msg=", from category id(s): $cat_num"
fi
if [ ! -z "$cat_num" ] && [ "$delete_num" = cat ]; then
	nb_msg "deleting category id(s): $cat_num ..."
	query_db "$db_query" "$cat_list"; UPDATE_LIST="$DB_RESULTS"
	for cat_db in $cat_list; do
		rm -f "$NB_DATA_DIR/$cat_db" "$BLOG_DIR/$ARCHIVES_DIR"/`chg_suffix "$cat_db"`
	done
	cat_num=; build_weblog; exit 0
fi
NUMVAR=$(echo "$delete_num" |grep '[0-9]' |sed -e '/\,/ s// /g; /[A-Z,a-z\)\.\-]/d')
[ -z "$NUMVAR" ] && die "must specify a valid entry id!"
if [ ! -z "$cat_list" ]; then
	CATNUMVAR=$(echo "$cat_num" |grep '[0-9]' |sed -e '/\,/ s// /g; /[A-Z,a-z\)\.\-]/d')
	[ -z "$CATNUMVAR" ] &&
		die "must specify a single category when deleting entries from a category!"
fi
query_db "$db_query" "$cat_list"; ENTRY_LIST="$DB_RESULTS"
for entry_id in $NUMVAR; do
	Delete_EntryFile=`echo "$ENTRY_LIST" |sed -n "$entry_id"p`
	[ ! -f "$NB_DATA_DIR/$Delete_EntryFile" ] && die "invalid entry id(s): $delete_num"
done
nb_msg "deleting weblog entry id(s): $delete_num$cat_msg ..."
UPDATE_LIST=$(for entry_id in $NUMVAR; do echo "$DB_RESULTS" |sed -n "$entry_id"p; done)
find_categories "$UPDATE_LIST"
for entry_id in $NUMVAR; do
	Delete_EntryFile=`echo "$ENTRY_LIST" |sed -n "$entry_id"p`
	if [ -f "$NB_DATA_DIR/$Delete_EntryFile" ]; then
		if [ ! -z "$cat_list" ]; then
			for cat_db in $cat_list; do
				cat_mod=`grep "$Delete_EntryFile" "$NB_DATA_DIR/$cat_db"`
				if [ ! -z "$cat_mod" ]; then
					sed -e '/'$Delete_EntryFile'/d' "$NB_DATA_DIR/$cat_db" \
					> "$NB_DATA_DIR/$cat_db".tmp
					mv "$NB_DATA_DIR/$cat_db".tmp "$NB_DATA_DIR/$cat_db"
				fi
			done
		else
			for cat_db in $db_categories; do
				cat_mod=`grep "$Delete_EntryFile" "$NB_DATA_DIR/$cat_db"`
				if [ ! -z "$cat_mod" ]; then
					sed -e '/'$Delete_EntryFile'/d' "$NB_DATA_DIR/$cat_db" \
					> "$NB_DATA_DIR/$cat_db".tmp
					mv "$NB_DATA_DIR/$cat_db".tmp "$NB_DATA_DIR/$cat_db"
				fi
			done
			rm -f "$NB_DATA_DIR/$Delete_EntryFile"
			Delete_PermalinkFile=`chg_suffix "$Delete_EntryFile"`
			if [ -f "$BLOG_DIR/$ARCHIVES_DIR/$Delete_PermalinkFile" ]; then
				rm -f "$BLOG_DIR/$ARCHIVES_DIR/$Delete_PermalinkFile"
			fi
			rm -f "$BLOG_DIR/$CACHE_DIR/$Delete_EntryFile".*
		fi
	fi
done
build_weblog
}

# list entries and categories
list_weblog(){
query_db; db_query="$1"
db_catquery=`cat_id`; check_catid
if [ "$db_query" = cat ]; then
	[ -z "$db_categories" ] && die "no categories exist yet! goodbye."
	nb_msg "ID, Title"
	id=0
	cat_total=`echo "$db_categories" |grep -c "[\.]$NB_DBTYPE"`
	while [ "$id" != "$cat_total" ]; do
		id=`expr 1 + $id`
		if [ -f "$NB_DATA_DIR/cat_$id.$NB_DBTYPE" ]; then
			echo " $id, `sed 1q "$NB_DATA_DIR"/cat_$id.$NB_DBTYPE`"
		else
			cat_total=`expr 1 + $cat_total`
		fi
	done; exit 0
fi
# default to entries listed by limit
[ -z "$db_query" ] && db_query="$QUERY_MODE"
list_query="$db_query"
query_db "$db_query" "$db_catquery"
[ -z "$DB_RESULTS" ] && die "nothing matched your query: '$list_query'! goodbye."
nb_msg "ID, Title - [Category] - Date"
id=0
for entry in $DB_RESULTS; do
	for cat_db in $db_categories; do
		cat_var=`grep "$entry" "$NB_DATA_DIR/$cat_db"`
		if [ ! -z "$cat_var" ]; then
			cat_title=`sed 1q "$NB_DATA_DIR/$cat_db"`
			[ "$cat_title" != "$oldcat_title" ] && cat_title="$oldcat_title $cat_title"
			oldcat_title="$cat_title,"
		fi
	done
	cat_title=`echo $cat_title |sed -e '{$ s/\,[ ]$//g; }'`
	[ ! -z "$cat_title" ] && NB_Category="- [$cat_title] -" || NB_Category="-"
	read_metadata TITLE "$NB_DATA_DIR/$entry"; NB_EntryTitle="`echo "$NB_Metadata" |cut -c1-32`..."
	read_metadata DATE "$NB_DATA_DIR/$entry"; NB_EntryDate="$NB_Metadata"
	id=`expr 1 + $id`
	echo " $id, $NB_EntryTitle $NB_Category $NB_EntryDate"
	oldcat_title=; cat_title=; NB_Category=
done
}

# move entries into other categories
move_entry(){
NUMVAR=$(echo "$move_num" |grep '[0-9]' |sed -e '/\,/ s// /g; /[A-Z,a-z\)\.\-]/d')
[ -z "$NUMVAR" ] && die "must specify a valid entry id!"
query_db
for entry_id in $NUMVAR; do
	Move_EntryFile=`echo "$DB_RESULTS" |sed -n "$entry_id"p`
	[ ! -f "$NB_DATA_DIR/$Move_EntryFile" ] && die "invalid entry id(s): $move_num"
done
db_catquery=`cat_id`; check_catid; [ -z "$cat_num" ] && die "must specify category before entry!"
nb_msg "moving weblog entry id(s): $move_num, to category id(s): $cat_num ..."
UPDATE_LIST=$(for entry_id in $NUMVAR; do echo "$DB_RESULTS" |sed -n "$entry_id"p; done)
query_db "$db_query" "$db_catquery"
for entry_id in $UPDATE_LIST; do
	Move_EntryFile="$entry_id"
	if [ -f "$NB_DATA_DIR/$Move_EntryFile" ]; then
		if [ ! -z "$db_catquery" ]; then
			for cat_db in $db_categories; do
				cat_mod=`grep "$Move_EntryFile" "$NB_DATA_DIR/$cat_db"`
				if [ -z "$cat_mod" ]; then
					echo "$Move_EntryFile" >> "$NB_DATA_DIR/$cat_db"
				fi
			done
		fi
	fi
done
build_weblog
}

preview_weblog(){
[ -z "$BLOG_PREVIEW_CMD" ] && die "BLOG_PREVIEW_CMD is not set! goodbye."
nb_msg "previewing weblog ..."
$BLOG_PREVIEW_CMD
}

preview_ask(){
echo "would you like to preview your weblog now? [y/N]"
read -p "$NB_PROMPT" choice
case $choice in
	[Yy])
		preview_weblog;;
	[Nn]|"")
	;;
esac
}

publish_weblog(){
[ -z "$BLOG_PUBLISH_CMD" ] && die "BLOG_PUBLISH_CMD is not set! goodbye."
nb_msg "publishing weblog ..."
$BLOG_PUBLISH_CMD
}

publish_ask(){
echo "would you like to publish the weblog now? [y/N]"
read -p "$NB_PROMPT" choice
case $choice in
	[Yy])
		publish_weblog;;
	[Nn]|"")
		;;
esac
}

# create a new entry, category or weblog directory
create_weblog(){
[ -z "$BLOG_DIR" ] && die "no weblog directory specified! goodbye."
# automatically create new weblog directory.
if [ ! -d "$BLOG_DIR" ]; then
	build_newblog=1
	nb_msg "creating weblog directory '$BLOG_DIR' ..."
	mkdir -p "$BLOG_DIR"
	nb_msg "copying default weblog files ..."
	cp -R "$BASE_DIR"/default/* "$BLOG_DIR"
	# but prompt for configuration.
	echo "would you like to configure the new weblog now? [Y/n]"
	read -p "$NB_PROMPT" choice
	case $choice in
	[Yy]|"")
		nb_msg "configuring new weblog ..."
		$EDITOR "$BLOG_DIR"/blog.conf;;
	[Nn])
		nb_msg "weblog not configured! use '`basename $0` -b $BLOG_DIR --configure -u all' to configure later."
	esac
fi
check_config
# create a new category 
if [ ! -z "$cat_num" ]; then
	if [ "$cat_num" = new ]; then
		query_db; id=0
		cat_total=`echo "$db_categories" |grep -c "[\.]$NB_DBTYPE"`
		while [ "$id" != "$cat_total" ] || [ "$cat_total" = "0" ]; do
			id=`expr 1 + $id`
			if [ ! -f "$NB_DATA_DIR/cat_$id.$NB_DBTYPE" ]; then
				nb_msg "creating new category id: $id, for '$BLOG_TITLE' ..."
				if [ ! -z "$usr_title" ]; then
					cat_title=$usr_title; usr_title=
				else
					echo "enter the new category's title [Untitled]"
					read -p "$NB_PROMPT" cat_title
					[ -z "$cat_title" ] && cat_title=Untitled
				fi
				echo "$cat_title" > "$NB_DATA_DIR"/cat_$id.$NB_DBTYPE
				cat_num="$id"; db_catquery=`cat_id`; check_catid; cat_total="$id"
			else
				cat_total=`expr 1 + $cat_total`
			fi
		done
		nb_msg "category database created for '$cat_title'."
		echo "would you like to add a new entry to this category now? [Y/n]"
		read -p "$NB_PROMPT" choice
		case $choice in
			[Yy]|"")
				# continue
				;;
			[Nn])
				build_weblog "$weblog_update"; exit 0
				;;
		esac
	else
		db_catquery=`cat_id`; check_catid
	fi
fi
[ ! -z "$db_catquery" ] && cat_msg=", using category id(s): $cat_num"
nb_msg "creating new entry in '$BLOG_DIR'$cat_msg ..."
# read user specified attributes for entry
[ ! -z "$usr_author" ] && NB_EntryAuthor=$usr_author
[ ! -z "$usr_title" ] && NB_EntryTitle=$usr_title; usr_title=
if [ -z "$NB_EntryAuthor" ]; then
	echo "enter author's name [$BLOG_AUTHOR]"
	read -p "$NB_PROMPT" NB_EntryAuthor
	[ -z "$NB_EntryAuthor" ] && NB_EntryAuthor="$BLOG_AUTHOR"
fi
if [ -z "$NB_EntryTitle" ]; then
	echo "enter a title"
	read -p "$NB_PROMPT" NB_EntryTitle
fi
if [ -z "$usr_body" ]; then
	echo "editing body ..."
	# entry's new file for editing
	NB_EditFile="$TEMP_DIR/nb_entry$$.$NB_DATATYPE"
	$EDITOR "$NB_EditFile"
	# load content of edited file into entry variable NB_EntryBody
	if [ -f "$NB_EditFile" ]; then
		NB_EntryBody=$(< "$NB_EditFile")
	fi
else
	NB_EntryBody="$usr_body"
fi
[ ! -z "$usr_description" ] && NB_EntryDescription="$usr_description"
# prompt for descritption
if [ -z "$NB_EntryDescription" ] && [ "$desc_var" != 1 ]; then
	echo "enter a short descriptive comment"
	read -p "$NB_PROMPT" NB_EntryDescription
fi
# generate date format for entry's content
NB_EntryDate=$(filter_dateformat "$DATE_FORMAT")
add_entry
[ ! -z "$BLOG_PREVIEW_CMD" ] && preview_ask
[ ! -z "$BLOG_PUBLISH_CMD" ] && publish_ask
}

show_help(){
cat <<-EOF
	NanoBlogger - Console weblog engine.
	Version $VERSION, by Kevin Wood <un1xt3r@fastmail.fm>

	Usage:
	 `basename $0` [-b blog_dir] [options]

	Options:
	 -a, --add				create new entry, category, or weblog
	 					(directory).
	 -b, --blogdir <directory>		specify weblog directory.
	 -B, --body <text>			set body of entry (for '--add').
	 -c, --category	<ID,cat>		specify category (for '--add', '--delete',
	 					'--edit', '--list' and '--update').
	 --configure				configure weblog (for '--update').
	 --datadir <directory>			specify weblog's data directory.
	 -d, --delete <ID,cat>			delete an entry or category.
	 -D, --desc <text>			set description of entry (for '--add').
	 -e, --edit <ID,cat>			edit an entry or category.
	 -f, --blogconf <file>			specify an alternate configuration file.
	 -h, --help				show this help message.
	 -l, --list <all,cat,current,max>	list entries or categories
	 					(defaults to max).
	 --makepage <source> <output>		specify source and output file to create
	 					web page.
	 --manual				view the manual.
	 -m, --move <ID>			move an entry to a specified category (for
	 					'--category').
	 -n, --author <text>			set author of entry (for '--add').
	 -p, --preview				run command to preview weblog.
	 -P, --publish				run command to publish weblog.
	 --template <file>			specify file to load as template (for
	 					'--makepage').
	 --templatedir <directory>		specify weblog's template directory.
	 -t, --title <text>			set title of entry/category/page
	 					(for '--add', '--category', and
	 					'--makepage').
	 -u, --update <all,current,main,max>	force update of weblog (defaults to
	 					current).
	 -v, --verbose <1=on/0=off>		toggle level of verbosity.
	 -V, --version				display version information.

	 -c,-d,-e,-m accepts multiple ID numbers seperated by commas (e.g. 1,2,3).

	Examples:
	
	 specify the weblog directory to create or add new entry
	 	nb -b ~/public_html/weblog -a

	 create new category and title it "News"
	 	nb -b ~/public_html/weblog -t "News" -c new -a

	 add new entry to category 1
	 	nb -b ~/public_html/weblog -c 1 -a

	 remove entry 2 from category 1
	 	nb -b ~/public_html/weblog -c 1 -d 2

	 set author, title, and body for new entry
	 	nb -b ~/public_html/weblog -n myname -t 'My Title' -B 'My message!' -a

	More info:
	 URL: http://nanoblogger.sourceforge.net
EOF
}

argument=$@
[ $# -lt 1 ] && show_help
check_arg(){
if [ -z "$argument" ]; then
	echo "$bad_argument option requires an argument!"
	echo "Try '`basename $0` --help' for more information."
	exit 1
fi
}
sanity_check(){
invalid_opt=`echo "$argument" |grep '^[--]$*'`
[ ! -z "$invalid_opt" ] && argument=
}
while [ $# -gt 0 ]; do
	[ $# -gt 1 ] && argument=$2
	bad_argument=$1
	# always load global configs
	. "$BASE_DIR"/nb.conf
	# check for user's .nb.conf in their home directory
	[ -f "$HOME/.nb.conf" ] && . "$HOME/.nb.conf"
	case "$1" in
		-a|--add)		load_config; create_weblog;;
		-b|--blogdir)		check_arg; USR_BLOGDIR="$2"; shift;;
		-B|--body)		check_arg; usr_body="$2"; shift;;
		-c|--category)		check_arg; cat_num="$2"; shift;;
		--configure)		check_config; config_weblog;;
		--datadir)		check_arg; USR_DATADIR="$2"; shift;;
		-d|--delete)		check_arg; delete_num="$2"; shift
					check_config; delete_weblog;;
		-D|--desc)		desc_var=1; usr_description="$2"; shift;;
		-e|--edit)		check_arg; edit_num="$2"; shift
					check_config; edit_weblog;;
		-f|--blogconf)		USR_BLOGCONF="$2"; shift;;
		-h|--help)		show_help; exit 0;;
		-l|--list)		db_query="$2"; shift
					check_config; list_weblog "$db_query";;
		-n|--author)		check_arg; usr_author="$2"; shift;;
		--makepage)		check_arg; usr_srcfile="$2"; usr_outputfile="$3"; shift 2
					check_config; make_page;;
		--manual)		load_config; $BROWSER "$BASE_DIR/docs/nanoblogger.html";;
		-m|--move)		check_arg; move_num="$2"; shift
					check_config; move_entry;;
		-p|--preview)		check_config; preview_weblog;;
		-P|--publish)		check_config; publish_weblog;;
		--template)		check_arg; usr_template="$2"; shift;;
		--templatedir)		check_arg; USR_TEMPLATE_DIR="$2"; shift;;
		-t|--title)		check_arg; usr_title="$2"; shift;;
		-u|--update)		weblog_update="$2"; shift
					check_config; build_weblog "$weblog_update";; 
		-v|--verbose)		check_arg; VERBOSE="$2"; shift;;
		-V|--version)		echo "NanoBlogger $VERSION"; exit 0;;
		--)			shift; break;;
		*)
					sanity_check
					echo "invalid option: $@"
					echo "Try '`basename $0` --help' for more information."
					exit 1
					;;
	esac
	shift
done

exit 0

#
# End of script
#
