Simple database: Difference between revisions

From Rosetta Code
Content added Content deleted
(see also)
Line 24: Line 24:


See also [[Take notes on the command line]] for a related task.
See also [[Take notes on the command line]] for a related task.
=={{header|Common Lisp}}==

a tool to track the episodes you have watched in a series.
tested with [[SBCL]] but should work with other implementations.

run from the commandline as:
sbcl --script watch.lisp

without arguments the function <code>(watch-list)</code> is invoked to show the last episode of each series.
with the argument <code>add</code> the function <code>(watch-add)</code> will allow you to add a new episode with series name, episode title, episode number and date watched. if the series does not yet exist you will be asked if you want to create it.

this code is also available under the GNU GPLv3.

<lang lisp>(defun make-episode (series title episode date-watched)
`((series . ,series) (episode . ,episode) (title . ,title) (date . ,date-watched)))

(defvar db nil)


(defun dump-db (database)
(format t "~{~{~a:~10t~a~%~}~%~}" database))

(defun print-episode (episode)
(format t "~30a ~10a ~30a (~{~a~^.~})~%" (cdr (assoc 'series episode)) (cdr (assoc 'episode episode)) (cdr (assoc 'title episode)) (cdr (assoc 'date episode))))

(defun get-latest (database)
(cond ((endp database) nil)
(T (cons (cadr (assoc 'episodes (cdar database))) (get-latest (cdr database))))))

(defun compare-date (a b)
(cond ((not (and (listp a) (listp b))) nil)
((null a) (not (null b)))
((null b) nil)
((= (first a) (first b)) (compare-date (rest a) (rest b)))
(t (< (first a) (first b))) ))

(defun compare-by-date (a b)
(compare-date (reverse (cdr (assoc 'date a))) (reverse (cdr (assoc 'date b)))))

(defun watch-list ()
(mapcar #'print-episode (sort (get-latest db) #'compare-by-date)))

(defun prompt-read (prompt)
(format *query-io* "~a: " prompt)
(force-output *query-io*)
(read-line *query-io*))

(defun split (seperator string)
(loop for i = 0 then (1+ j)
as j = (search seperator string :start2 i)
collect (subseq string i j)
while j))

(defun parse-date (date)
(mapcar #'parse-integer (split "." date)))

(defun prompt-for-episode ()
(make-episode
(prompt-read "Series")
(prompt-read "Title")
(prompt-read "Episode")
(parse-date (prompt-read "Date watched"))))

(defun add-episodes ()
(loop (push (prompt-for-episode) db)
(if (not (y-or-n-p "Another? [y/n]: ")) (return))))

(defun save-db (filename database)
(with-open-file (out filename
:direction :output
:if-exists :supersede)
(with-standard-io-syntax
(pprint database out))))

(defun watch-save ()
(save-db "lwatch" db))

(defun load-db (filename database)
(with-open-file (in filename)
(with-standard-io-syntax
(setf database (read in)))))

(defun has-series (name list)
(assoc name list :test #'equal))

(defun get-series (name list)
(cdr (assoc name list :test #'equal)))

(defun get-episode-list (series list)
(cdr (assoc 'episodes (get-series series list))))

(defun watch-add-series (name status)
(cdar (push `(,name (series . ,name) (status . ,status) (episodes)) db)))

(defun get-or-add-series (name database)
(or (get-series name database)
(if (y-or-n-p "Add new series? [y/n]: ")
(watch-add-series name 'active) nil)))

(defun watch-add ()
(let* ((episode (prompt-for-episode))
(series-name (cdr (assoc 'series episode)))
(series (get-or-add-series series-name db)))
(if (endp series) (watch-add)
(rplacd (assoc 'episodes series)
(cons episode (get-episode-list series-name db))))))

(defun watch-load ()
(with-open-file (in "lwatch") (with-standard-io-syntax (setf db (read in)))))


(defun argv ()
(or
#+clisp (ext:argv)
#+sbcl sb-ext:*posix-argv*
#+clozure (ccl::command-line-arguments)
#+gcl si:*command-args*
#+ecl (loop for i from 0 below (si:argc) collect (si:argv i))
#+cmu extensions:*command-line-strings*
#+allegro (sys:command-line-arguments)
#+lispworks sys:*line-arguments-list*
nil))

(defun main (argv)
(watch-load)
(cond ((equal (cadr argv) "add") (watch-add) (watch-save))
(T (watch-list))))

(main (argv))</lang>

=={{header|UNIX Shell}}==
=={{header|UNIX Shell}}==
This format is guaranteed to be human readable: if you can type it, you can read it.
This format is guaranteed to be human readable: if you can type it, you can read it.

Revision as of 18:26, 2 November 2011

Simple database is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Write a simple tool to track a small set of data. The tool should have a commandline interface to enter at least two different values. The entered data should be stored in a structured format and saved to disk.

It does not matter what kind of data is being tracked. It could be your CD collection, your friends birthdays, or diary.

You should track the following details:

  • A description of the item. (eg title, name)
  • A category or tag (genre, topic, relationship (friend, family))
  • A date (either the date when the entry was made or some other date that is meaningful (like the birthday)) (the date may be generated or entered manually)
  • Other optional fields

The command should support the following Command-line arguments to run:

  • Add a new entry
  • Print the latest entry
  • Print the latest entry for each category
  • Print all entries sorted by a date

The category may be realized as a tag or as structure (making all entries in that category subitems)

The fileformat on disk should be human readable, but it need not be standardized. A natively available format that doesn't need an external library is preferred. Avoid developing your own format however if you can use an already existing one. If there is no existing format available pick one of: JSON, S-Expressions, YAML, or others.

See also Take notes on the command line for a related task.

Common Lisp

a tool to track the episodes you have watched in a series. tested with SBCL but should work with other implementations.

run from the commandline as:

sbcl --script watch.lisp

without arguments the function (watch-list) is invoked to show the last episode of each series. with the argument add the function (watch-add) will allow you to add a new episode with series name, episode title, episode number and date watched. if the series does not yet exist you will be asked if you want to create it.

this code is also available under the GNU GPLv3.

<lang lisp>(defun make-episode (series title episode date-watched)

  `((series . ,series) (episode . ,episode) (title . ,title) (date . ,date-watched)))

(defvar db nil)


(defun dump-db (database)

 (format t "~{~{~a:~10t~a~%~}~%~}" database))

(defun print-episode (episode)

 (format t "~30a ~10a ~30a (~{~a~^.~})~%" (cdr (assoc 'series episode)) (cdr (assoc 'episode episode)) (cdr (assoc 'title episode)) (cdr (assoc 'date episode))))

(defun get-latest (database)

 (cond ((endp database) nil)
       (T (cons (cadr (assoc 'episodes (cdar database))) (get-latest (cdr database))))))

(defun compare-date (a b)

 (cond ((not (and (listp a) (listp b))) nil)
       ((null a) (not (null b)))
       ((null b) nil)
       ((= (first a) (first b)) (compare-date (rest a) (rest b)))
       (t (< (first a) (first b))) ))

(defun compare-by-date (a b)

 (compare-date (reverse (cdr (assoc 'date a))) (reverse (cdr (assoc 'date b)))))

(defun watch-list ()

(mapcar #'print-episode (sort (get-latest db) #'compare-by-date)))

(defun prompt-read (prompt)

 (format *query-io* "~a: " prompt)
 (force-output *query-io*)
 (read-line *query-io*))

(defun split (seperator string)

   (loop for i = 0 then (1+ j)
         as j = (search seperator string :start2 i)
         collect (subseq string i j)
         while j))

(defun parse-date (date)

 (mapcar #'parse-integer (split "." date)))

(defun prompt-for-episode ()

 (make-episode
  (prompt-read "Series")
  (prompt-read "Title")
  (prompt-read "Episode")
  (parse-date (prompt-read "Date watched"))))

(defun add-episodes ()

 (loop (push (prompt-for-episode) db)
     (if (not (y-or-n-p "Another? [y/n]: ")) (return))))

(defun save-db (filename database)

 (with-open-file (out filename
                  :direction :output
                  :if-exists :supersede)
   (with-standard-io-syntax
     (pprint database out))))

(defun watch-save ()

(save-db "lwatch" db))

(defun load-db (filename database)

 (with-open-file (in filename)
   (with-standard-io-syntax
     (setf database (read in)))))

(defun has-series (name list)

 (assoc name list :test #'equal))

(defun get-series (name list)

 (cdr (assoc name list :test #'equal)))

(defun get-episode-list (series list)

 (cdr (assoc 'episodes (get-series series list))))

(defun watch-add-series (name status)

 (cdar (push `(,name (series . ,name) (status . ,status) (episodes)) db)))

(defun get-or-add-series (name database)

 (or (get-series name database)
     (if (y-or-n-p "Add new series? [y/n]: ") 
       (watch-add-series name 'active) nil)))

(defun watch-add ()

 (let* ((episode (prompt-for-episode))
        (series-name (cdr (assoc 'series episode)))
        (series (get-or-add-series series-name db)))
   (if (endp series) (watch-add)
     (rplacd (assoc 'episodes series) 
           (cons episode (get-episode-list series-name db))))))

(defun watch-load ()

 (with-open-file (in "lwatch") (with-standard-io-syntax (setf db (read in)))))


(defun argv ()

 (or
  #+clisp (ext:argv)
  #+sbcl sb-ext:*posix-argv*
  #+clozure (ccl::command-line-arguments)
  #+gcl si:*command-args*
  #+ecl (loop for i from 0 below (si:argc) collect (si:argv i))
  #+cmu extensions:*command-line-strings*
  #+allegro (sys:command-line-arguments)
  #+lispworks sys:*line-arguments-list*
  nil))

(defun main (argv)

 (watch-load)
 (cond ((equal (cadr argv) "add") (watch-add) (watch-save))
       (T (watch-list))))

(main (argv))</lang>

UNIX Shell

This format is guaranteed to be human readable: if you can type it, you can read it. <lang bash>#!/bin/sh

db_create() { mkdir ./"$1" && mkdir "./$1/.tag" && echo "Create DB \`$1'" }

db_delete() { rm -r ./"$1" && echo "Delete DB \`$1'" }

db_show() { if [ -z "$2" ]; then show_help; fi for x in "./$1/$2/"*; do echo "$x:" | sed "s/.*\///" cat "$x" | sed "s/^/ /" echo done

printf "Tags: " ls "./$1/$2/.tag" }

db_tag() { local db="$1" item="$2" shift shift for tag in $@; do mkdir "./$db/.tag/$tag" ln -s "$PWD/$db/$item" "./$db/.tag/$tag/" touch "./$db/$item/.tag/$tag" done }

show_help() { echo "Usage: $0 command [args]" echo "Commands:" cat $0 | grep ") ##" | grep -v grep | sed 's/) ## /:\t/' exit }

if [ -z "$1" ]; then show_help; fi

action=$1 it=database shift case $action in create) ## db -- create $it db_create "$@" ;;

drop) ## db -- delete $it db_delete "$@" ;;

add) ## db item -- add new item to $it mkdir -p "./$1/$2/.tag" && touch "./$1/$2/Description" ;;

rem) ## db item -- delete item from $it rm -r "./$1/$2" rm "./$1/.tag/"*"/$2" ;;

show) ## db item -- show item db_show "$@" ;;

newtag) ## db new-tag-name -- create new tag name mkdir "./$1/.tag/$2" ;;

prop) ## db item property-name property-content -- add property to item echo "$4" > "./$1/$2/$3" ;;

tag) ## db item tag [more-tags...] -- mark item with tags db_tag "$@" ;;

last) ## db -- show latest item ls "$1" --sort=time | tail -n 1 ;;

list) ## db -- list all items ls "$1" -1 --sort=time ;;

last-all) ## db -- list items in each category for x in "$1/.tag/"*; do echo "$x" | sed 's/.*\//Tag: /' printf " " ls "$x" --sort=time | tail -n 1 echo done ;;

help) ## this message show_help ;;

*) echo Bad DB command: $1 show_help ;; esac</lang> Sample usage (assuming script is named "sdb"):<lang>$ sdb create CDs Create DB `CDs' $ sdb add CDs Bookends $ sdb prop CDs Bookends artists "Simon & Garfunkel" $ sdb add CDs "Ode to joy" $ sdb prop CDs "Ode to joy" artist "Beethoven" $ sdb tag CDs Bookends rock folk # I'm not sure about this $ sdb tag CDs "Ode to joy" classical $ sdb show CDs Bookends Description:

artists:

   Simon & Garfunkel

Tags: folk rock $ sdb prop CDs "Ode to joy" Description "Sym. No. 9" $ sdb show CDs "Ode to joy" Description:

   Sym. No. 9

artist:

   Beethoven

Tags: classical $ sdb last-all CDs Tag: classical

   Ode to joy

Tag: folk

   Bookends

Tag: rock

   Bookends

$ sdb drop CDs Delete DB `CDs' $</lang>