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-26 Wed 18:02]
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.
A string, who the book is currently loaned to. Non-existent if not on loan.
A timestamp, when the book was loaned. Non-existent if not on loan.
# -*- 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 %type: LoanTo line %type: LoanOn date %mandatory: Title Author LCCN Inserted %allowed: ISBN Publisher Copyright Location Withdrawn LoanTo LoanOn %auto: ID Inserted
CLOSED: [2016-10-23 Sun 15:01]
The initialization of the database is accomplished by creating the containing directory, and within it, creating the database file, which defines the structure of the database. If git is to be initialized for the database, this must be done manually.
function initialize { OLD=`pwd` mkdir -p ${LIBRARYDIRECTORY} cd ${LIBRARYDIRECTORY} [[ -ne `basename ${LIBRARYFILE}` ]] && \ cat <<EOF > `basename ${LIBRARYFILE}` <<file-format>> EOF }
CLOSED: [2016-10-23 Sun 15:06]
This handles git as needed, by first checking to see if the first argument is init
, and if so, initializing the library directory as a repository. Otherwise, if the GIT
variable is detect, it runs the specified git command within the library directory if possible.
function do-git { if [[ $# -lt 1 ]] ; then echo "library git args*" exit 1 fi 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 { if [[ $@ -lt 1 ]] ; then echo "library bulk-add number" exit 1 fi 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 { if [[ $# -lt 1 ]] ; then echo "library report name args*" exit 1 fi NAME=$1 shift case ${NAME} in list) for report in ${LIBRARYDIRECTORY}/*.report ; do echo " - $(basename -- ${report} .report)" done ;; new) if [[ $# -lt 1 ]] ; then echo "library report new name" exit 1 fi REPORT=$1 shift echo "# -*- mode: shell-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 { if [[ $# -lt 2 ]] ; then echo "ledger edit id field [ value ]" exit 1 fi ID=$1 shift FIELD=$1 shift if [[ $FIELD = "Withdrawn" ]] ; then recset -e "ID = ${ID}" \ -f "Withdrawn" -S "`date +"%a, %d %b %Y %H:%M:%S %z"`" \ ${LIBRARYFILE} else VALUE=$1 shift recset -e "ID = ${ID}" \ -f "${FIELD}" -s "${VALUE}" \ ${LIBRARYFILE} fi do-git add `basename ${LIBRARYFILE}` do-git commit -m "Edited record id ${ID}" }
function do-loan { if [[ $# -lt 2 ]] ; then echo "library loan id name" exit 1 fi ID=$1 shift NAME=$1 shift recset -e "ID = ${ID}" \ -f "LoanTo" -S "${NAME}" \ ${LIBRARYFILE} recset -e "ID = ${ID}" \ -f "LoanOn" -S "`date +"%a, %d %b %Y %H:%M:%S %z"`" \ ${LIBRARYFILE} do-git add `basename ${LIBRARYFILE}` do-git commit -m "Loaned Book ${ID} to ${NAME}" } function do-return { if [[ $# -lt 1 ]] ; then echo "library return-book id" exit 1 fi ID=$1 shift recset -e "ID = ${ID}" \ -f "LoanTo" -d \ ${LIBRARYFILE} recset -e "ID = ${ID}" \ -f "LoanOn" -d \ ${LIBRARYFILE} do-git add `basename ${LIBRARYFILE}` do-git commit -m "Returned Book ${ID}" }
if [[ $# -eq 0 ]] ; then echo "library [ help | query | add | emacs | git | bulk-add | report | edit | loan | do-return | init ]" exit fi function display-help { cat <<EOF library [ help | query | add | emacs | git | bulk-add | report | edit | loan | do-return | init ] help: Display this help message. query: Query Library Database. add: Add a singular book record. emacs: View records in emacs. git: Run a git command. bulk-add: Add a specified number of records. report: Run a report. edit: Edit the value of a specified field in a specified record. loan: Loan a book out. do-return: Process a book return init: Initialize the database. 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 ;; loan) do-loan $@ exit ;; return-book) do-return $@ 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>> <<loan>> <<process-commands>>