BBlog/bin/BBlog
Ian Jauslin c6e9cb3af4 Update to v2.2.1:
Fix: Missing quotes in filter interpreter.
2023-03-27 11:47:09 -04:00

449 lines
10 KiB
Bash
Executable File

#!/bin/bash
## Copyright Ian Jauslin 2015-2023
##
## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
## You may obtain a copy of the License at
##
## http://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.
# directory containing the engine files
enginedir=/usr/share/BBlog/engines
# version
version=2.2.1
function print_config {
echo "engine: $engine"
echo "db: $db"
echo "conf: $conf"
echo "aux: $aux"
echo "format: $format"
echo "outbib: $outbib"
echo "order: $order"
echo "aux_cmd: $aux_cmd"
echo "ref_map: $ref_map"
}
# defaults
function set_defaults {
engine="sqlite"
db=""
conf=""
aux='*.aux'
format='\bibitem[%citeref%]{%token%}%n%%author% - {\it %title%}, %journal%, %year%.%n%'
outbib=''
order="alphabetical"
aux_cmd='\\citation{'
ref_map=''
}
# read cli args
function read_cli {
flag=""
for arg in "$@"; do
case "$arg" in
"-e" )
flag="engine" ;;
"-d" )
flag="db" ;;
"-c" )
flag="conf" ;;
"-a" )
flag="aux" ;;
"-f" )
flag="format" ;;
"-b" )
flag="outbib" ;;
"-o" )
flag="order" ;;
"-A" )
flag="aux_cmd" ;;
"-F" )
flag="filter" ;;
"-r" )
flag="ref_map" ;;
# version
"-v" )
echo "$version"; exit ;;
* )
if [ -n "$flag" ]; then
# treat filters differently
if [ "$flag" != 'filter' ]; then
eval "$flag='$arg'"
# flag the variable as set
eval "set_$flag=1"
else
field="${arg%%:*}"
value="${arg#*:}"
eval "filter_$field='$value'"
eval "set_filter_$field=1"
fi
flag=""
fi
esac
done
}
# read config file
function read_config {
extra_entries_index=0
if [ -n "$conf" ]; then
[ -f "$conf" ] || { echo "error: $conf: no such file" 1>&2 ; exit -1 ; }
while read -r confline; do
flag=""
field="${confline%%:*}"
value="${confline#*:}"
# remove spaces
field=$(echo "$field" | sed -r 's/ *$//')
value=$(echo "$value" | sed -r 's/^ *//')
case "$field" in
"engine" )
flag="engine" ;;
"database" )
flag="db" ;;
"aux" )
flag="aux" ;;
"format" )
flag="format" ;;
"out_file" )
flag="outbib" ;;
"order" )
flag="order" ;;
"aux_cmd" )
flag="aux_cmd" ;;
"ref_map" )
flag="ref_map" ;;
"filter" )
# set flag to the specific filter
flag="filter_${value%%:*}"
# reset value
value="${value#*:}" ;;
"extra" )
extra[$extra_entries_index]="$value"
extra_entries_index=$((extra_entries_index+1)) ;;
esac
if [ -n "$flag" ]; then
# check it was not set on the command line
eval "[ -n \"\$set_$flag\" -a \"\$set_$flag\" = 1 ] || $flag='$value'"
fi
done <"$conf"
# sort extra entries
if [ "$extra_entries_index" -gt 0 -a "$order" = "alphabetical" ]; then
IFS=$'\n' extra=($(echo "${extra[*]}" | sort -k 1,1 -t :))
fi
fi
}
# check the configuration
function check_config {
# check whether the engine exists
[ -e "$enginedir/$engine.sh" ] || { echo "error: engine $engine not found in $enginedir" 1>&2; exit -1 ; }
# check the database and aux file
for file in "$db" "$aux"; do
[[ "$file" =~ '*' || -e "$file" ]] || { echo "error: $file: no such file" 1>&2 ; exit -1 ; }
done
# check the $order variable
[ "$order" != "alphabetical" -a "$order" != "appearance" ] && { echo "error: order should be one of 'alphabetical' or 'appearance' (read $order)" 1>&2 ; exit -1 ; }
# check for ref_map
[ -n "$ref_map" ] && [ ! -e "$ref_map" ] && { echo "error: $ref_map: no such file" 1>&2 ; exit -1 ; }
}
function replace_format {
out="$1"
# replace newlines
out="${out//\%n\%/\\n}"
# if the ref of the entry has been mapped, fetch the old citeref
if [ -n "$ref_map" ]; then
citeref=$(inverse_map_citeref "$citeref")
fi
# first search for conditionals
# while '|' is still in $out
while : ; do
# find next '|'
tmp=${out#*|}
# save text before '|'
pre=${out%%|*}
# check whether it was found
[ "$tmp" = "$out" ] && break
# find next '|'
command=${tmp%%|*}
# save text after '|'
post=${tmp#*|}
# check whether it was found
[ "$command" = "$tmp" ] && { echo "error: '|'s do not match in '$1'" 1>&2 ; exit -1 ; }
# extract field
field="${command%%\?*}"
repl="${command##*\?}"
# check whether field is empty
val=$(eval "echo \$$field")
if [ -n "$val" ]; then
out="$pre$repl$post"
else
out="$pre$post"
fi
done
# replace '%'
# while '%' is still in $out
while : ; do
# find next '%'
tmp=${out#*%}
# check whether it was found
[ "$tmp" = "$out" ] && break
# find next '%'
command=${tmp%%%*}
# check whether it was found
[ "$command" = "$tmp" ] && { echo "error: '%'s do not match in '$1'" 1>&2 ; exit -1 ; }
# the field entry may be followed by a filter
field="${command%:*}"
# filter
filter="${command##*:}"
# apply filter
if [ -n "$filter" ]; then
# replace '%' inside filter text
sed_cmd=$(replace_format "$(eval echo \$filter_$filter)")
replacement=$(eval "echo \$$field" | sed -r "$sed_cmd" )
else
replacement=$(eval "echo \$$field")
fi
# escape '%' in replacement
replacement="${replacement//\%/::iansays:percent::}"
out="${out//\%$command\%/$replacement}"
done
# finish replacing newlines
out="${out//\\n/%}"
out=$(echo "$out" | tr "%" "\n")
# un-escape '%' in replacement
out="${out//::iansays:percent::/\%}"
echo "$out"
}
function escape_string {
str="$1"
str="${str//\\/\\\\}"
str="${str//\`/\\\`}"
str="${str//\"/\\\"}"
str="${str//\$/\\\$}"
echo "$str"
}
# replace a citeref according to a ref_map
function map_citeref {
# check whether a ref_map was specified
[ -z "$ref_map" ] && { echo "$1" ; return 0 ; }
oldref="$1"
newref=$(echo "$oldref" | sed -r -f "$ref_map")
echo "$newref"
}
# find the pre-image of a citeref through the ref_map
function inverse_map_citeref {
# check whether a ref_map was specified
[ -z "$ref_map" ] && { echo "$1" ; return 0 ; }
newref="$1"
foundref=0
# sift through aux file
grep -h "$aux_cmd" $aux | while read -r refs; do
eval "refs=\${refs#$aux_cmd}"
refs="${ref%\}}"
# can be a comma separated list
for ref in $(echo -n "$refs" | tr ',' '\n'); do
# replace the ref via the ref_map
possibleref=$(map_citeref "$ref")
# check whether the ref is the right one
if [ "$possibleref" = "$newref" ]; then
echo "$ref"
foundref=1
return 1
fi
done
done && echo "$foundref$newref"
}
# format entries
function format_BBlog_entries {
# keep track of tokens to avoid repetitions
token_list=""
has_there_been_a_nonempty_line_so_far=0
while read -r line; do
# if line has 0 size, then new entry
if [ -z "$line" ]; then
# skip if it is the first line
if [ $has_there_been_a_nonempty_line_so_far = 1 ]; then
# check whether an extra entry precedes it
# alphabetical order
if [ "$order" = "alphabetical" ]; then
old_token="$token"
old_citeref="$citeref"
while [[ ${#extra} -gt 0 && "${extra[0]%%:*}" < "$prauth$year" ]]; do
reftokval="${extra[0]#*:}"
citeref="${reftokval%%:*}"
tokval="${reftokval#*:}"
token="${tokval%%:*}"
value="${tokval#*:}"
# check whether token is unique
token=$(replace_repeated_token "$token" "$token_list")
# add token to list
token_list="$token_list;$token"
replace_format "$value"
IFS=$'\n' extra=($(echo "${extra[*]:1}"))
done
token="$old_token"
citeref="$old_citeref"
# check whether token is unique
token=$(replace_repeated_token "$token" "$token_list")
# add token to list
token_list="$token_list;$token"
replace_format "$format"
# appearance order
else
foundit=0
if [ ${#extra} -gt 0 ]; then
for entry in "${extra[@]}"; do
ref="${entry#*:}"
ref="${ref%%:*}"
if [ "$ref" = "$citeref" ]; then
old_token="$token"
old_citeref="$citeref"
citeref="$ref"
token="${entry#*:*:}"
value="${token#*:}"
token="${token%%:*}"
# check whether token is unique
token=$(replace_repeated_token "$token" "$token_list")
# add token to list
token_list="$token_list;$token"
replace_format "$value"
token="$old_token"
citeref="$old_citeref"
foundit=1
break
fi
done
fi
if [ $foundit = 0 ]; then
# check whether token is unique
token=$(replace_repeated_token "$token" "$token_list")
# add token to list
token_list="$token_list;$token"
replace_format "$format"
fi
fi
fi
has_there_been_a_nonempty_line_so_far=0
else
eval "$line"
has_there_been_a_nonempty_line_so_far=1
fi
done
# add remaining extra entries (only for alphabetical order)
if [ "$order" = "alphabetical" ]; then
while [ ${#extra} -gt 0 ]; do
reftokval="${extra[0]#*:}"
citeref="${reftokval%%:*}"
tokval="${reftokval#*:}"
token="${tokval%%:*}"
value="${tokval#*:}"
# check whether token is unique
token=$(replace_repeated_token "$token" "$token_list")
# add token to list
token_list="$token_list;$token"
replace_format "$value"
IFS=$'\n' extra=($(echo "${extra[*]:1}"))
done
fi
}
# replace a token that already exists
function replace_repeated_token {
token="$1"
list="$2"
out="$token"
i=0
while $(echo "$token_list;" | grep -q ";$out;"); do
i=$((i+1))
out="$token"$(alpha_number $i)
done
echo "$out"
}
# convert an integer to base 26
function alpha_number {
num="$1"
rem="$num"
out=""
letters=(a b c d e f g h i j k l m n o p q r s t u v w x y z)
while [ "$rem" -gt 0 ]; do
digit=$((rem-26*(rem/26)))
out="${letters[$digit]}$out"
rem=$(((rem-digit)/26))
done
echo "$out"
}
set_defaults
read_cli "$@"
read_config
check_config
source "$enginedir/$engine.sh"
# check whether to order by alphabetical order or appearance
[ "$order" = "alphabetical" ] && fetch_cmd=fetch_BBlog_entries_alpha || fetch_cmd=fetch_BBlog_entries_appearance
# fetch entries
if [ -n "$outbib" ]; then
"$fetch_cmd" | format_BBlog_entries > "$outbib"
else
"$fetch_cmd" | format_BBlog_entries
fi