#+Title: Library Management for Small Users #+Subtitle: Simple software for average users #+AUTHOR: Samuel W. Flint #+EMAIL: swflint@flintfam.org #+DATE: \today #+INFOJS_OPT: view:info toc:nil path:http://flintfam.org/org-info.js #+OPTIONS: toc:nil H:5 ':t *:t todo:nil stat:nil d:nil #+PROPERTY: header-args :noweb tangle :comments noweb #+LATEX_HEADER: \usepackage[margins=0.75in]{geometry} #+LATEX_HEADER: \parskip=5pt #+LATEX_HEADER: \parindent=0pt #+LATEX_HEADER: \lstset{texcl=true,breaklines=true,columns=fullflexible,basicstyle=\ttfamily,frame=lines,literate={<=}{$\leq$}1 {>=}{$\geq$}1} #+LATEX_CLASS_OPTIONS: [10pt,twoside] #+LATEX_HEADER: \pagestyle{headings} * Export :noexport: :PROPERTIES: :CREATED: <2016-10-06 Thu 14:21> :END: #+Caption: Export Document #+Name: export-document #+BEGIN_SRC emacs-lisp :exports none :results none (save-buffer) (let ((org-confirm-babel-evaluate (lambda (lang body) (declare (ignorable lang body)) nil))) (org-latex-export-to-pdf)) #+END_SRC * Tangle :noexport: :PROPERTIES: :CREATED: <2016-10-06 Thu 14:21> :END: #+Caption: Tangle Document #+Name: tangle-document #+BEGIN_SRC emacs-lisp :exports none :results none (save-buffer) (let ((python-indent-offset 4)) (org-babel-tangle)) #+END_SRC * DONE Introduction CLOSED: [2016-04-16 Sat 15:15] :PROPERTIES: :CREATED: <2016-04-13 Wed 20:15> :UNNUMBERED: t :END: 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. * TOC :ignore: :PROPERTIES: :CREATED: <2016-04-13 Wed 20:15> :END: #+TOC: headlines 3 #+TOC: listings * DONE File Format CLOSED: [2016-10-06 Thu 17:29] :PROPERTIES: :CREATED: <2016-04-13 Wed 20:17> :ID: 1745de61-6511-4257-bed8-112df2362fe7 :END: The following defines the format of book records, which are used to describe each individual book in the library. A record contains the following: - ID :: an integer, automatically generated by the system. - Title :: A string, at most, one line long. - Author :: A string, in the format of ~Last, First && Second Last, First~. - LCCN :: A string, being the LOC Classification of the given book. - ISBN :: A string, made up of the numbers 0-9, with an optional "X" at the end. - Publisher :: A string, generally unformatted. - Copyright :: An integer, the year the book was published, or the copyright, whichever is later. - Location :: A string denoting where the book is located. - Withdrawn :: An optional timestamp denoting when a book was withdrawn from the library. - Inserted :: A timestamp representing when the book was inserted into the library, this is automatically generated. #+Caption: File Format #+Name: file-format #+BEGIN_SRC text # -*- 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 #+END_SRC * DONE Initialize CLOSED: [2016-10-23 Sun 15:01] :PROPERTIES: :CREATED: <2016-04-13 Wed 22:13> :ID: 8308ab62-05a0-4240-a073-d2e4baab9ab2 :END: 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. #+Caption: Initialize a Database #+Name: initialize-database #+BEGIN_SRC sh function initialize { OLD=`pwd` mkdir -p ${LIBRARYDIRECTORY} cd ${LIBRARYDIRECTORY} [[ -ne `basename ${LIBRARYFILE}` ]] && \ cat < `basename ${LIBRARYFILE}` <> EOF } #+END_SRC * WORKING Handle Git :PROPERTIES: :CREATED: <2016-04-13 Wed 20:17> :ID: 68ee8e3d-3280-4280-8dd4-e824b5c81929 :END: #+Caption: Handle Git #+Name: handle-git #+BEGIN_SRC sh 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 } #+END_SRC * WORKING Run Queries :PROPERTIES: :CREATED: <2016-04-13 Wed 20:17> :ID: 9cc04c91-08ea-49db-a0ac-f3789e22ff33 :END: #+Caption: Handle Query #+Name: handle-query #+BEGIN_SRC sh function run-query { recsel -t Book $@ ${LIBRARYFILE} } #+END_SRC * WORKING Add Book :PROPERTIES: :CREATED: <2016-04-13 Wed 20:17> :ID: 7b842d87-7f13-483e-96ee-5a318877eeb7 :END: #+Caption: Add Book #+Name: add-book #+BEGIN_SRC sh 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}\"" } #+END_SRC * WORKING Add Books in Bulk :PROPERTIES: :CREATED: <2016-04-13 Wed 20:17> :ID: 2316a78c-88d7-4629-b310-b6b0c8b848b6 :END: #+Caption: Add Books in Bulk #+Name: add-in-bulk #+BEGIN_SRC sh 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" } #+END_SRC * WORKING View the File in Emacs with Rec Mode :PROPERTIES: :CREATED: <2016-04-13 Wed 20:17> :ID: adfb988b-508b-4b3e-91a7-fd549ef3779e :END: #+Caption: View in Emacs #+Name: view-in-emacs #+BEGIN_SRC sh function view-in-emacs { emacsclient --alternate-editor="" -n ${LIBRARYFILE} } #+END_SRC * WORKING Handle Reporting :PROPERTIES: :CREATED: <2016-04-13 Wed 20:18> :ID: 1e6d79cb-aa8d-4a44-9268-5c5d502c5c38 :END: #+Caption: Reporting #+Name: handle-reports #+BEGIN_SRC sh 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 } #+END_SRC * WORKING Edit Field :PROPERTIES: :CREATED: <2016-04-15 Fri 11:50> :ID: 56281ec3-8323-4f9c-892f-7edaff86393f :END: #+Caption: Edit Fields #+Name: edit-field #+BEGIN_SRC sh function do-edit { 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}" } #+END_SRC * WORKING Help Message :PROPERTIES: :CREATED: <2016-04-13 Wed 22:04> :ID: 3d90ee25-d8af-4590-b6bc-80e0430299f2 :END: #+Caption: Help Message #+Name: help-message #+BEGIN_SRC sh 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 < :ID: 9d10ce06-45f0-4957-85c3-b8fcfff42da8 :END: #+Caption: Process Commands #+Name: process-commands #+BEGIN_SRC sh 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 #+END_SRC * DONE Packaging CLOSED: [2016-04-16 Sat 15:20] :PROPERTIES: :CREATED: <2016-04-13 Wed 22:07> :ID: 57c4d5f0-9fc7-41bd-a2f6-1ab6b8d12ae6 :END: 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. #+Caption: Packaging #+Name: packaging #+BEGIN_SRC sh :tangle "~/bin/library" :shebang "#!/bin/zsh -f" LIBRARYFILE=~/.library/library.rec LIBRARYDIRECTORY=~/.library GIT=detect <> <> <> <> <> <> <> <> <> <> #+END_SRC