CLOSED: [2016-04-16 Sat 15:15]
As a person who has a lot of books, I like to be able to keep track of them. I've tried other pre-built solutions, but they've been flaky and don't do well for the types of books I have. I've also tried to use basic spreadsheets, but in the end, they've not been able to keep up with my desire to search through or produce reports. So, when I stumbled on GNU recutils, I decided that building a script around it would be useful. This is the script and system.
CLOSED: [2016-10-06 Thu 17:29]
The following defines the format of book records, which are used to describe each individual book in the library. A record contains the following:
an integer, automatically generated by the system.
A string, at most, one line long.
A string, in the format of Last, First && Second Last, First.
A string, being the LOC Classification of the given book.
A string, made up of the numbers 0-9, with an optional "X" at the end.
A string, generally unformatted.
An integer, the year the book was published, or the copyright, whichever is later.
A string denoting where the book is located.
An optional timestamp denoting when a book was withdrawn from the library.
A timestamp representing when the book was inserted into the library, this is automatically generated.
# -*- mode: rec -*- %rec: Book %doc: Foo %key: ID %unique: Title %type: ID int %type: Title line %type: Author line %type: LCCN line %type: ISBN regexp /[0-9]*X?/ %type: Publisher line %type: Copyright int %type: Location line %type: Withdrawn date %type: Inserted date %mandatory: Title Author LCCN Inserted %allowed: ISBN Publisher Copyright Location Withdrawn %auto: ID Inserted
function initialize {
OLD=`pwd`
mkdir -p ${LIBRARYDIRECTORY}
cd ${LIBRARYDIRECTORY}
[[ -ne `basename ${LIBRARYFILE}` ]] && \
cat <<EOF > `basename ${LIBRARYFILE}`
<<file-format>>
EOF
}
function do-git {
FIRST=$1
if [[ $FIRST == "init" ]] ; then
OLD=`pwd`
cd ${LIBRARYDIRECTORY}
git $@
cd ${OLD}
else
if [[ $GIT == "detect" ]] ; then
OLD=`pwd`
cd ${LIBRARYDIRECTORY}
git $@
cd ${OLD}
fi
fi
}
function run-query {
recsel -t Book $@ ${LIBRARYFILE}
}
function add-single {
echo -n "Title: "
read TITLE
echo -n "Author: "
read AUTHOR
echo -n "LCCN: "
read LCCN
echo -n "Copyright: "
read COPYRIGHT
echo -n "Publisher: "
read PUBLISHER
echo -n "ISBN: "
read ISBN
echo -n "Location: "
read LOCATION
recins -t Book \
-f Title -v "${TITLE}" \
-f Author -v "${AUTHOR}" \
-f LCCN -v "${LCCN}" \
-f Copyright -v "${COPYRIGHT}" \
-f Publisher -v "${PUBLISHER}" \
-f ISBN -v "${ISBN}" \
-f Location -v "${LOCATION}" \
${LIBRARYFILE}
do-git add `basename ${LIBRARYFILE}`
do-git commit -m "Added record for \"${TITLE}\""
}
function bulk-add {
GITOLD=${GIT}
GIT=FALSE
for i in {1..$1} ; do
echo "Adding book number ${i}"
add-single
done
GIT=${GITOLD}
do-git add `basename ${LIBRARYFILE}`
do-git commit -m "Added ${1} records"
}
function view-in-emacs {
emacsclient --alternate-editor="" -n ${LIBRARYFILE}
}
function do-report {
NAME=$1
shift
case ${NAME} in
list)
for report in ${LIBRARYDIRECTORY}/*.report ;
do
echo " - $(basename -- ${report} .report)"
done
;;
new)
REPORT=$1
shift
echo "# -*- mode: sh-script -*-" > ${LIBRARYDIRECTORY}/${REPORT}.report
emacsclient --alternate-editor="" -n ${LIBRARYDIRECTORY}/${REPORT}.report
;;
,*)
if [[ -e ${LIBRARYDIRECTORY}/${NAME}.report ]] ; then
sh ${LIBRARYDIRECTORY}/${NAME}.report $@
fi
esac
}
function do-edit {
ID=$1
shift
FIELD=$1
shift
if [[ $FIELD = "Withdrawn" ]] ; then
recins -e "ID = ${ID}" \
-f "Withdrawn" -v `date "%a, %d %b %Y %H:%M:%S %z"` \
${LIBRARYFILE}
else
VALUE=$1
shift
recins -e "ID = ${ID}" \
-f "${FIELD}" -v "${VALUE}" \
${LIBRARYFILE}
fi
do-git add `basename ${LIBRARYFILE}`
do-git commit -m "Edited record id ${ID}"
}
if [[ $# -eq 0 ]] ; then
echo "library [ help | query query-expressions | add | emacs | git [ other-args ] | bulk-add number | report [ name | list | new name ] | edit id field [ value ] | init ]"
exit
fi
function display-help {
cat <<EOF
library [ help | query query-expressions | add | emacs | git [ other-args ] | bulk-add number | report [ name | list | new name ] | edit id field [ value ] | init ]
help: Display this help message.
query: Query Library Database.
add: Add a singular book record.
emacs: View records in emacs.
edit: Edit the value of a specified field in a specified record.
git: Run a git command.
report: Run a report.
init: Initialize the database.
bulk-add: Add a specified number of records.
EOF
}
COMMAND=$1
shift
case ${COMMAND} in
help)
display-help
exit
;;
query)
run-query $@
exit
;;
add)
add-single
exit
;;
git)
do-git $@
exit
;;
bulk-add)
bulk-add $@
exit
;;
report)
do-report $@
exit
;;
emacs)
view-in-emacs
exit
;;
edit)
do-edit $@
exit
;;
init)
initialize
exit
;;
,*)
display-help
exit
esac
CLOSED: [2016-04-16 Sat 15:20]
Finally, this is what puts the application together. Placing each function in its place, making sure that everything is included is done here, fairly simply, by referencing the code block and being expanded here.
LIBRARYFILE=~/.library/library.rec LIBRARYDIRECTORY=~/.library GIT=detect <<help-message>> <<handle-reports>> <<handle-git>> <<handle-query>> <<add-book>> <<add-in-bulk>> <<edit-field>> <<initialize-database>> <<view-in-emacs>> <<process-commands>>