all tasks
This commit is contained in:
parent
b83f433714
commit
68f8f3e56b
14735 changed files with 178959 additions and 0 deletions
23
Task/Simple-database/0DESCRIPTION
Normal file
23
Task/Simple-database/0DESCRIPTION
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
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. (e.g., title, name)
|
||||
* A category or tag (genre, topic, relationship such as “friend” or “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 (by making all entries in that category subitems)
|
||||
|
||||
The file format 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 [[wp:Comparison_of_data_serialization_formats|others]].
|
||||
|
||||
See also [[Take notes on the command line]] for a related task.
|
||||
2
Task/Simple-database/1META.yaml
Normal file
2
Task/Simple-database/1META.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
note: Data Structures
|
||||
193
Task/Simple-database/C/simple-database.c
Normal file
193
Task/Simple-database/C/simple-database.c
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h> /* malloc */
|
||||
#include <string.h> /* strlen */
|
||||
#define _XOPEN_SOURCE /* requred for time functions */
|
||||
#define __USE_XOPEN
|
||||
#include <time.h>
|
||||
#define DB "database.csv" /* database name */
|
||||
#define TRY(a) if (!(a)) {perror(#a);exit(1);}
|
||||
#define TRY2(a) if((a)<0) {perror(#a);exit(1);}
|
||||
#define FREE(a) if(a) {free(a);a=NULL;}
|
||||
#define sort_by(foo) \
|
||||
static int by_##foo (pdb_t *p1, pdb_t *p2) { \
|
||||
return strcmp ((*p1)->foo, (*p2)->foo); }
|
||||
typedef struct db {
|
||||
char title[26];
|
||||
char first_name[26];
|
||||
char last_name[26];
|
||||
time_t date;
|
||||
char publ[100];
|
||||
struct db *next;
|
||||
}
|
||||
db_t,*pdb_t;
|
||||
typedef int (sort)(pdb_t*, pdb_t*);
|
||||
typedef int (*compfn)(const void*, const void*);
|
||||
enum {CREATE,PRINT,TITLE,DATE,AUTH,READLINE,READ,SORT,DESTROY};
|
||||
static pdb_t dao (int cmd, FILE *f, pdb_t db, sort sortby);
|
||||
static char *time2str (time_t *time);
|
||||
static time_t str2time (char *date);
|
||||
/* qsort callbacks */
|
||||
sort_by(last_name);
|
||||
sort_by(title);
|
||||
static sort by_date;
|
||||
/* main */
|
||||
int main (int argc, char **argv) {
|
||||
char buf[100], *commands[]={"-c", "-p", "-t", "-d", "-a", NULL};
|
||||
db_t db;
|
||||
db.next=NULL;
|
||||
pdb_t dblist;
|
||||
int i;
|
||||
FILE *f;
|
||||
TRY (f=fopen(DB,"a+"));
|
||||
if (argc<2) {
|
||||
usage: printf ("Usage: %s [commands]\n"
|
||||
"-c Create new entry.\n"
|
||||
"-p Print the latest entry.\n"
|
||||
"-t Print all entries sorted by title.\n"
|
||||
"-d Print all entries sorted by date.\n"
|
||||
"-a Print all entries sorted by author.\n",argv[0]);
|
||||
fclose (f);
|
||||
return 0;
|
||||
}
|
||||
for (i=0;commands[i]&&strcmp(argv[1],commands[i]);i++);
|
||||
switch (i) {
|
||||
case CREATE:
|
||||
printf("-c Create a new entry.\n");
|
||||
printf("Title :");if((scanf(" %25[^\n]",db.title ))<0)break;
|
||||
printf("Author Firstname:");if((scanf(" %25[^\n]",db.first_name))<0)break;
|
||||
printf("Author Lastname :");if((scanf(" %25[^\n]",db.last_name ))<0)break;
|
||||
printf("Date 10-12-2000 :");if((scanf(" %10[^\n]",buf ))<0)break;
|
||||
printf("Publication :");if((scanf(" %99[^\n]",db.publ ))<0)break;
|
||||
db.date=str2time (buf);
|
||||
dao (CREATE,f,&db,NULL);
|
||||
break;
|
||||
case PRINT:
|
||||
printf ("-p Print the latest entry.\n");
|
||||
while (!feof(f)) dao (READLINE,f,&db,NULL);
|
||||
dao (PRINT,f,&db,NULL);
|
||||
break;
|
||||
case TITLE:
|
||||
printf ("-t Print all entries sorted by title.\n");
|
||||
dblist = dao (READ,f,&db,NULL);
|
||||
dblist = dao (SORT,f,dblist,by_title);
|
||||
dao (PRINT,f,dblist,NULL);
|
||||
dao (DESTROY,f,dblist,NULL);
|
||||
break;
|
||||
case DATE:
|
||||
printf ("-d Print all entries sorted by date.\n");
|
||||
dblist = dao (READ,f,&db,NULL);
|
||||
dblist = dao (SORT,f,dblist,by_date);
|
||||
dao (PRINT,f,dblist,NULL);
|
||||
dao (DESTROY,f,dblist,NULL);
|
||||
break;
|
||||
case AUTH:
|
||||
printf ("-a Print all entries sorted by author.\n");
|
||||
dblist = dao (READ,f,&db,NULL);
|
||||
dblist = dao (SORT,f,dblist,by_last_name);
|
||||
dao (PRINT,f,dblist,NULL);
|
||||
dao (DESTROY,f,dblist,NULL);
|
||||
break;
|
||||
default: {
|
||||
printf ("Unknown command: %s.\n",strlen(argv[1])<10?argv[1]:"");
|
||||
goto usage;
|
||||
} }
|
||||
fclose (f);
|
||||
return 0;
|
||||
}
|
||||
/* Data Access Object (DAO) */
|
||||
static pdb_t dao (int cmd, FILE *f, pdb_t in_db, sort sortby) {
|
||||
pdb_t *pdb=NULL,rec=NULL,hd=NULL;
|
||||
int i=0,ret;
|
||||
char buf[100];
|
||||
switch (cmd) {
|
||||
case CREATE:
|
||||
fprintf (f,"\"%s\",",in_db->title);
|
||||
fprintf (f,"\"%s\",",in_db->first_name);
|
||||
fprintf (f,"\"%s\",",in_db->last_name);
|
||||
fprintf (f,"\"%s\",",time2str(&in_db->date));
|
||||
fprintf (f,"\"%s\" \n",in_db->publ);
|
||||
break;
|
||||
case PRINT:
|
||||
for (;in_db;i++) {
|
||||
printf ("Title : %s\n", in_db->title);
|
||||
printf ("Author : %s %s\n", in_db->first_name, in_db->last_name);
|
||||
printf ("Date : %s\n", time2str(&in_db->date));
|
||||
printf ("Publication : %s\n\n", in_db->publ);
|
||||
if (!((i+1)%3)) {
|
||||
printf ("Press Enter to continue.\n");
|
||||
ret = scanf ("%*[^\n]");
|
||||
if (ret<0) return rec; /* handle EOF */
|
||||
else getchar();
|
||||
}
|
||||
in_db=in_db->next;
|
||||
}
|
||||
break;
|
||||
case READLINE:
|
||||
if((fscanf(f," \"%[^\"]\",",in_db->title ))<0)break;
|
||||
if((fscanf(f," \"%[^\"]\",",in_db->first_name))<0)break;
|
||||
if((fscanf(f," \"%[^\"]\",",in_db->last_name ))<0)break;
|
||||
if((fscanf(f," \"%[^\"]\",",buf ))<0)break;
|
||||
if((fscanf(f," \"%[^\"]\" ",in_db->publ ))<0)break;
|
||||
in_db->date=str2time (buf);
|
||||
break;
|
||||
case READ:
|
||||
while (!feof(f)) {
|
||||
dao (READLINE,f,in_db,NULL);
|
||||
TRY (rec=malloc(sizeof(db_t)));
|
||||
*rec=*in_db; /* copy contents */
|
||||
rec->next=hd;/* to linked list */
|
||||
hd=rec;i++;
|
||||
}
|
||||
if (i<2) {
|
||||
puts ("Empty database. Please create some entries.");
|
||||
fclose (f);
|
||||
exit (0);
|
||||
}
|
||||
break;
|
||||
case SORT:
|
||||
rec=in_db;
|
||||
for (;in_db;i++) in_db=in_db->next;
|
||||
TRY (pdb=malloc(i*sizeof(pdb_t)));
|
||||
in_db=rec;
|
||||
for (i=0;in_db;i++) {
|
||||
pdb[i]=in_db;
|
||||
in_db=in_db->next;
|
||||
}
|
||||
qsort (pdb,i,sizeof in_db,(compfn)sortby);
|
||||
pdb[i-1]->next=NULL;
|
||||
for (;i;i--) {
|
||||
pdb[i-1]->next=pdb[i];
|
||||
}
|
||||
rec=pdb[0];
|
||||
FREE (pdb);
|
||||
pdb=NULL;
|
||||
break;
|
||||
case DESTROY: {
|
||||
while ((rec=in_db)) {
|
||||
in_db=in_db->next;
|
||||
FREE (rec);
|
||||
} } }
|
||||
return rec;
|
||||
}
|
||||
/* convert numeric time to date string */
|
||||
static char *time2str (time_t *time) {
|
||||
static char buf[255];
|
||||
struct tm *ptm;
|
||||
ptm=localtime (time);
|
||||
strftime(buf, 255, "%m-%d-%Y", ptm);
|
||||
return buf;
|
||||
}
|
||||
/* convert date string to numeric time */
|
||||
static time_t str2time (char *date) {
|
||||
struct tm tm;
|
||||
memset (&tm, 0, sizeof(struct tm));
|
||||
strptime(date, "%m-%d-%Y", &tm);
|
||||
return mktime(&tm);
|
||||
}
|
||||
/* sort by date callback for qsort */
|
||||
static int by_date (pdb_t *p1, pdb_t *p2) {
|
||||
if ((*p1)->date < (*p2)->date) {
|
||||
return -1;
|
||||
}
|
||||
else return ((*p1)->date > (*p2)->date);
|
||||
}
|
||||
396
Task/Simple-database/Common-Lisp/simple-database.lisp
Normal file
396
Task/Simple-database/Common-Lisp/simple-database.lisp
Normal file
|
|
@ -0,0 +1,396 @@
|
|||
(defvar *db* nil)
|
||||
|
||||
(defstruct series description tags episodes)
|
||||
|
||||
(defstruct (episode (:print-function print-episode-struct))
|
||||
series title season episode part date tags)
|
||||
|
||||
(defun format-ymd (date)
|
||||
(format nil "~{~a.~a.~a~}" date))
|
||||
|
||||
(defun print-episode-struct (ep stream level)
|
||||
(let ((*print-pretty* nil))
|
||||
(format stream (if *print-escape*
|
||||
"#s(episode~@{~*~@[ :~1:*~a ~s~]~})"
|
||||
"~32<~*~a~; ~*~@[~d-~]~*~d~> ~45<~*~@[~a ~]~*~@[(~a) ~]~;~*~@[(~a)~]~>~*~@[ (~{~a~^ ~})~]")
|
||||
:series (episode-series ep)
|
||||
:season (episode-season ep)
|
||||
:episode (episode-episode ep)
|
||||
:title (episode-title ep)
|
||||
:part (episode-part ep)
|
||||
:date (if *print-escape*
|
||||
(episode-date ep)
|
||||
(when (episode-date ep)
|
||||
(format-ymd (episode-date ep))))
|
||||
:tags (episode-tags ep))))
|
||||
|
||||
(defun get-value (key alist)
|
||||
(cdr (assoc key alist)))
|
||||
|
||||
|
||||
(defun get-latest (database)
|
||||
(when database
|
||||
(cons (car (series-episodes (cdar database))) (get-latest (cdr database)))))
|
||||
|
||||
(defun get-all (database)
|
||||
(when database
|
||||
(append (series-episodes (cdar database)) (get-all (cdr database)))))
|
||||
|
||||
(defun compare-date (a b)
|
||||
(cond ((not a) t)
|
||||
((not 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 (episode-date a) (episode-date b)))
|
||||
|
||||
(defun prompt-read (prompt &optional default)
|
||||
(format *query-io* "~a~@[ (~a)~]: " prompt default)
|
||||
(force-output *query-io*)
|
||||
(let ((answer (read-line *query-io*)))
|
||||
(if (string= answer "")
|
||||
default
|
||||
answer)))
|
||||
|
||||
(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 get-current-date ()
|
||||
(multiple-value-bind
|
||||
(second minute hour date month year day-of-week dst-p tz)
|
||||
(get-decoded-time)
|
||||
(declare (ignore second minute hour day-of-week dst-p tz))
|
||||
(list date month year)))
|
||||
|
||||
|
||||
(defun parse-date (date)
|
||||
(reverse (mapcar #'parse-integer (split "." date))))
|
||||
|
||||
(defun parse-tags (tags)
|
||||
(when (and tags (string-not-equal "" tags))
|
||||
(mapcar #'intern (split " " (string-upcase tags)))))
|
||||
|
||||
(defun parse-number (number)
|
||||
(if (stringp number)
|
||||
(parse-integer number :junk-allowed t)
|
||||
number))
|
||||
|
||||
(defun prompt-for-episode (&optional last)
|
||||
(when (not last)
|
||||
(setf last (make-episode)))
|
||||
(let* ((series (prompt-read "Series Title" (episode-series last)))
|
||||
(title (prompt-read "Title"))
|
||||
(season (parse-number (prompt-read "Season" (episode-season last))))
|
||||
(episode (parse-number (prompt-read "Episode"
|
||||
(if (eq (episode-season last) season)
|
||||
(1+ (episode-episode last))
|
||||
1))))
|
||||
(part (parse-number (prompt-read "Part"
|
||||
(when (and (episode-part last)
|
||||
(or (eq (episode-season last) season)
|
||||
(eq (episode-part last) 1)))
|
||||
(1+ (episode-part last))))))
|
||||
(date (parse-date (prompt-read "Date watched" (format-ymd (get-current-date)))))
|
||||
(tags (parse-tags (prompt-read "Tags"))))
|
||||
(make-episode
|
||||
:series series
|
||||
:title title
|
||||
:season season
|
||||
:episode episode
|
||||
:part part
|
||||
:date date
|
||||
:tags tags)))
|
||||
|
||||
(defun parse-integer-quietly (&rest args)
|
||||
(ignore-errors (apply #'parse-integer args)))
|
||||
|
||||
(defun get-next-version (basename)
|
||||
(flet ((parse-version (pathname)
|
||||
(or (parse-integer-quietly
|
||||
(string-left-trim (file-namestring basename)
|
||||
(file-namestring pathname))
|
||||
:start 1) 0)))
|
||||
(let* ((files (directory (format nil "~A,*" (namestring basename))))
|
||||
(max (if files
|
||||
(reduce #'max files :key #'parse-version)
|
||||
0)))
|
||||
(merge-pathnames (format nil "~a,~d" (file-namestring basename) (1+ max))
|
||||
basename))))
|
||||
|
||||
(defun save-db (dbfile database)
|
||||
(let ((file (probe-file dbfile)))
|
||||
(rename-file file (get-next-version file))
|
||||
(with-open-file (out file :direction :output)
|
||||
(with-standard-io-syntax
|
||||
(let ((*print-case* :downcase))
|
||||
(pprint database out))))))
|
||||
|
||||
(defun watch-save (dbfile)
|
||||
(save-db dbfile *db*))
|
||||
|
||||
(defun load-db (dbfile)
|
||||
(with-open-file (in dbfile)
|
||||
(with-standard-io-syntax
|
||||
(read in))))
|
||||
|
||||
(defun get-series (name database)
|
||||
(cdr (assoc name database :test #'string-equal)))
|
||||
|
||||
(defun get-episode-list (series database)
|
||||
(series-episodes (get-series series database)))
|
||||
|
||||
(defun print-series (title series)
|
||||
(format t "~&~30a ~@[ (~{~a~^ ~})~]~%~@[ ~a~%~]" title (series-tags series)
|
||||
(series-description series))
|
||||
(format t "~{~& ~a~%~}" (reverse (series-episodes series))))
|
||||
|
||||
(defun watch-series (title)
|
||||
(let ((series (get-series title *db*)))
|
||||
(when series
|
||||
(print-series title series))))
|
||||
|
||||
(defun print-all-series (database)
|
||||
(loop for (title . series)
|
||||
in (sort database #'(lambda (a b)(compare-by-date (car (series-episodes (cdr a)))
|
||||
(car (series-episodes (cdr b))))))
|
||||
do (terpri) (print-series title series)))
|
||||
|
||||
(defun watch-all-series ()
|
||||
(print-all-series *db*))
|
||||
|
||||
(defun watch-latest ()
|
||||
(format t "~{~& ~a~%~}" (sort (get-latest *db*) #'compare-by-date)))
|
||||
|
||||
(defun timeline-all (database)
|
||||
(let* ((all (get-all database))
|
||||
(max (length all))
|
||||
(count max)
|
||||
(all-series-names nil)
|
||||
(all-series (make-hash-table :test 'equal)))
|
||||
(loop for episode in (reverse (sort all #'compare-by-date))
|
||||
do (unless (gethash (episode-series episode) all-series)
|
||||
(setf (gethash (episode-series episode) all-series)
|
||||
(make-array max :initial-element nil))
|
||||
(setf all-series-names
|
||||
(cons (episode-series episode) all-series-names)))
|
||||
(setf (elt (gethash (episode-series episode) all-series) (decf count))
|
||||
episode))
|
||||
(values all-series all-series-names max)))
|
||||
|
||||
(defun watch-timeline ()
|
||||
(multiple-value-bind (all-series all-series-names max) (timeline-all *db*)
|
||||
(loop for series in all-series-names
|
||||
do (format t "~30a (~{~:[ ~;•~]~})~%" series
|
||||
(coerce (subseq (gethash series all-series) (- max 60)) 'list)))))
|
||||
|
||||
(defun watch-timelinec ()
|
||||
(multiple-value-bind (all-series all-series-names max) (timeline-all *db*)
|
||||
(let ((chart (make-array (list (length all-series-names) max) :initial-element nil))
|
||||
(newcol 0)
|
||||
(oldrow -1))
|
||||
(loop for oldcol upto (1- max)
|
||||
do (loop for series in all-series-names
|
||||
for row from 0 upto (length all-series-names)
|
||||
do (when (elt (gethash series all-series) oldcol)
|
||||
(when (<= row oldrow)
|
||||
(incf newcol))
|
||||
(setf (aref chart row newcol)
|
||||
(elt (gethash series all-series) oldcol))
|
||||
(setf oldrow row))))
|
||||
(loop for series in all-series-names
|
||||
for i from 0 upto (length all-series-names)
|
||||
do (format t "~30a (" series)
|
||||
(loop for j from (- newcol 60) upto newcol
|
||||
do (format t "~:[ ~;•~]" (aref chart i j))
|
||||
(if (= j newcol)
|
||||
(format t ")~%")))))))
|
||||
|
||||
(defun watch-timelinev ()
|
||||
(multiple-value-bind (all-series all-series-names max) (timeline-all *db*)
|
||||
(loop for series in all-series-names
|
||||
counting series into count
|
||||
do (format t "~va ~30a~%" count " " series ))
|
||||
(loop for i from 0 upto (1- max)
|
||||
do (let ((episode nil))
|
||||
(loop for series in all-series-names
|
||||
do (format t "~:[ ~;~:*~02a~]"
|
||||
(when (elt (gethash series all-series) i)
|
||||
(setf episode (elt (gethash series all-series) i))
|
||||
(episode-episode episode))))
|
||||
(format t " (~a)~%" (episode-series episode))))))
|
||||
|
||||
(defun watch-all ()
|
||||
(format t "~{~& ~a~%~}" (sort (get-all *db*) #'compare-by-date)))
|
||||
|
||||
(defun watch-new-series (&key name description tags)
|
||||
(cdar (push (cons name (make-series :description description :tags tags)) *db*)))
|
||||
|
||||
(defun get-or-add-series (name database)
|
||||
(or (get-series name database)
|
||||
(if (y-or-n-p "Add new series? [y/n]: ")
|
||||
(watch-new-series
|
||||
:name name
|
||||
:description (prompt-read "Description" name)
|
||||
:tags (parse-tags (prompt-read "Tags" "active")))
|
||||
nil)))
|
||||
|
||||
(defun watch-add ()
|
||||
(let* ((series (loop thereis (get-or-add-series (prompt-read "Series") *db*)))
|
||||
(episode (prompt-for-episode (car (series-episodes series)))))
|
||||
(push episode (series-episodes series))))
|
||||
|
||||
(defun watch-series-names ()
|
||||
(format T "~{~& ~a~%~}"
|
||||
(sort (mapcar #'car *db*)
|
||||
(lambda (series1 series2)
|
||||
(compare-by-date (car (series-episodes (get-value series1 *db*)))
|
||||
(car (series-episodes (get-value series2 *db*))))))))
|
||||
|
||||
(defun exact-match (term text)
|
||||
(string-equal (format nil "~{~a~^ ~}" term) text))
|
||||
|
||||
(defun fuzzy-match (term text)
|
||||
(loop for word in term
|
||||
when (search word text :test 'string-equal)
|
||||
collect it))
|
||||
|
||||
(defun match-tags (term tags)
|
||||
(intersection (mapcar #'intern term) tags))
|
||||
|
||||
(defun search-title (term database)
|
||||
(loop for episode in (get-all database)
|
||||
when (exact-match term (episode-title episode))
|
||||
collect episode))
|
||||
|
||||
(defun search-tags (term database)
|
||||
(sort (loop for episode in (get-all database)
|
||||
for matches = (match-tags term (episode-tags episode))
|
||||
when matches collect (list (length matches) episode))
|
||||
#'> :key #'car))
|
||||
|
||||
(defun search-title-fuzzy (term database)
|
||||
(sort (loop for episode in (get-all database)
|
||||
for matches = (fuzzy-match term (episode-title episode))
|
||||
when matches collect (list (length matches) episode))
|
||||
#'> :key #'car))
|
||||
|
||||
(defun search-all (term database)
|
||||
(let ((exact-results '())
|
||||
(fuzzy-results '())
|
||||
(tag-results '()))
|
||||
(dolist (episode (get-all database))
|
||||
(cond ((exact-match term (episode-title episode))
|
||||
(push episode exact-results))
|
||||
((fuzzy-match term (episode-title episode))
|
||||
(push episode fuzzy-results))
|
||||
((match-tags term (episode-tags episode))
|
||||
(push episode tag-results))))
|
||||
(append (sort exact-results #'compare-by-date)
|
||||
(sort tag-results #'compare-by-date)
|
||||
(sort fuzzy-results #'compare-by-date))))
|
||||
|
||||
(defun watch-search (term)
|
||||
(format t "~{~& ~a~%~}" (search-all term *db*)))
|
||||
|
||||
(defun list-all-tags (database)
|
||||
(let ((tags (make-hash-table :test 'equal)))
|
||||
(dolist (tag (apply #'append (mapcar #'episode-tags (get-all database))))
|
||||
(setf (gethash tag tags) (1+ (or (gethash tag tags) 0))))
|
||||
tags))
|
||||
|
||||
(defun watch-tags ()
|
||||
(maphash #'(lambda (tag count) (format t "~a (~d) " tag count))
|
||||
(list-all-tags *db*))
|
||||
(terpri))
|
||||
|
||||
(defun find-series-episode (term database)
|
||||
(let ((series (get-series (format nil "~{~a~^ ~}" (butlast term)) database)))
|
||||
(if series
|
||||
(let* ((season-episode (car (last term)))
|
||||
(pos (position #\- season-episode))
|
||||
(season-str (when pos (subseq season-episode 0 pos)))
|
||||
(season (or (parse-integer-quietly season-str) season-str))
|
||||
(episode-str (if pos
|
||||
(subseq season-episode (1+ pos))
|
||||
season-episode))
|
||||
(episode-nr (or (parse-integer-quietly episode-str) episode-str)))
|
||||
(loop for episode in (series-episodes series)
|
||||
when (and (equal (episode-episode episode) episode-nr)
|
||||
(equal (episode-season episode) season))
|
||||
collect episode))
|
||||
(let ((series (get-series (format nil "~{~a~^ ~}" term) database)))
|
||||
(if series
|
||||
(list (car (series-episodes series))))))))
|
||||
|
||||
|
||||
(defun find-episode (term database)
|
||||
(or (find-series-episode term database)
|
||||
(search-title term database)
|
||||
(let* ((res (or (search-tags term database)
|
||||
(search-title-fuzzy term database)))
|
||||
(max (caar res)))
|
||||
(loop for (matches episode) in res when (equal matches max) collect episode))))
|
||||
|
||||
(defun edit-episode (episode database)
|
||||
(format t "editing:~%~a~%" episode)
|
||||
(setf (episode-series episode)
|
||||
(prompt-read "Series Title" (episode-series episode)))
|
||||
(setf (episode-title episode)
|
||||
(prompt-read "Title" (episode-title episode)))
|
||||
(setf (episode-season episode)
|
||||
(parse-number (prompt-read "Season" (episode-season episode))))
|
||||
(setf (episode-episode episode)
|
||||
(parse-number (prompt-read "Episode" (episode-episode episode))))
|
||||
(setf (episode-part episode)
|
||||
(parse-number (prompt-read "Part" (episode-part episode))))
|
||||
(setf (episode-date episode)
|
||||
(parse-date (prompt-read "Date watched" (format-ymd (episode-date episode)))))
|
||||
(setf (episode-tags episode)
|
||||
(parse-tags (prompt-read "Tags" (format nil "~{~a~^ ~}" (episode-tags episode))))))
|
||||
|
||||
(defun watch-edit (term)
|
||||
(let ((episodes (find-episode term *db*)))
|
||||
(if (> (length episodes) 1)
|
||||
(format t "found more than one episode, please be more specific:~%~{~& ~a~%~}" episodes)
|
||||
(edit-episode (car episodes) *db*))))
|
||||
|
||||
(defun watch-load (dbfile)
|
||||
(setf *db* (load-db dbfile)))
|
||||
|
||||
|
||||
(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)
|
||||
(let ((dbfile (make-pathname :name "lwatch" :type nil :defaults *load-pathname*)))
|
||||
(watch-load dbfile)
|
||||
(format t "loaded db~%")
|
||||
(cond ((equal (cadr argv) "add") (watch-add) (watch-save dbfile))
|
||||
((equal (cadr argv) "latest") (watch-latest))
|
||||
((null (cadr argv)) (watch-latest))
|
||||
((equal (cadr argv) "series") (watch-series-names))
|
||||
((and (equal (cadr argv) "all") (equal (caddr argv) "series")) (watch-all-series))
|
||||
((equal (cadr argv) "all") (watch-all))
|
||||
((equal (cadr argv) "tags") (watch-tags))
|
||||
((equal (cadr argv) "search") (watch-search (cddr argv)))
|
||||
((equal (cadr argv) "edit") (watch-edit (cddr argv)) (watch-save dbfile))
|
||||
((equal (cadr argv) "timeline") (watch-timeline))
|
||||
((equal (cadr argv) "timelinev") (watch-timelinev))
|
||||
((equal (cadr argv) "timelinec") (watch-timelinec))
|
||||
(T (watch-series (format nil "~{~a~^ ~}" (cdr argv)))))))
|
||||
|
||||
(main (argv))
|
||||
87
Task/Simple-database/D/simple-database.d
Normal file
87
Task/Simple-database/D/simple-database.d
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import std.stdio, std.algorithm, std.string, std.conv, std.array,
|
||||
std.file, std.csv, std.datetime;
|
||||
|
||||
private {
|
||||
immutable filename = "simdb.csv";
|
||||
|
||||
struct Item {
|
||||
string name, date, category;
|
||||
}
|
||||
|
||||
void addItem(in string[] item) {
|
||||
if (item.length < 3)
|
||||
return printUsage();
|
||||
auto db = load();
|
||||
const date = text(cast(DateTime)Clock.currTime);
|
||||
const cat = (item.length == 4) ? item[3] : "none";
|
||||
db ~= Item(item[2], date, cat);
|
||||
store(db);
|
||||
}
|
||||
|
||||
void printLatest(in string[] a) {
|
||||
auto db = load();
|
||||
if (db.empty)
|
||||
return writeln("No entries in database.");
|
||||
db.sort!q{ a.date > b.date };
|
||||
if (a.length == 3) {
|
||||
foreach (item; db)
|
||||
if (item.category == a[2])
|
||||
writefln("%s, %s, %s", item.tupleof);
|
||||
} else {
|
||||
writefln("%s, %s, %s", db[0].tupleof);
|
||||
}
|
||||
}
|
||||
|
||||
void printAll() {
|
||||
auto db = load();
|
||||
if (db.empty)
|
||||
return writeln("No entries in database.");
|
||||
db.sort!q{ a.date < b.date };
|
||||
foreach (item; db)
|
||||
writefln("%s, %s, %s", item.tupleof);
|
||||
}
|
||||
|
||||
Item[] load() {
|
||||
Item[] db;
|
||||
if (filename.exists && filename.isFile) {
|
||||
try {
|
||||
const text = filename.readText;
|
||||
if (!text.empty)
|
||||
db = csvReader!Item(text).array;
|
||||
} catch (CSVException e) {
|
||||
writeln(e.msg);
|
||||
}
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
void store(in Item[] db) {
|
||||
auto f = File(filename, "w+");
|
||||
foreach (item; db)
|
||||
f.writefln("%s,%s,%s", item.tupleof);
|
||||
}
|
||||
|
||||
void printUsage() {
|
||||
writeln(
|
||||
`Usage:
|
||||
simdb cmd [categoryName]
|
||||
|
||||
add add item, followed by optional category
|
||||
latest print last added item(s), followed by optional category
|
||||
all print all
|
||||
|
||||
For instance: add "some item name" "some category name"`);
|
||||
}
|
||||
}
|
||||
|
||||
void main(in string[] args) {
|
||||
if (args.length < 2 || args.length > 4)
|
||||
return printUsage();
|
||||
|
||||
switch (args[1].toLower) {
|
||||
case "add": addItem(args); break;
|
||||
case "latest": printLatest(args); break;
|
||||
case "all": printAll(); break;
|
||||
default: printUsage(); break;
|
||||
}
|
||||
}
|
||||
129
Task/Simple-database/Forth/simple-database.fth
Normal file
129
Task/Simple-database/Forth/simple-database.fth
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
\ sdb.fs Simple database. Gforth 0.7.0 specific
|
||||
' noop is bootmessage
|
||||
|
||||
wordlist constant USER: \ USER functions
|
||||
wordlist constant SDB \ DB keys
|
||||
wordlist constant FIELDS \ DB fields
|
||||
|
||||
: -SDB?EXIT ( -- ; Continue if `SDB' non-empty )
|
||||
SDB cell+ @ 0= IF rdrop exit THEN ;
|
||||
|
||||
\ MACROS
|
||||
: |comp| ( MACRO: | code to be compiled | )
|
||||
char parse evaluate ; immediate
|
||||
: LIST ( node -- ) ]] BEGIN @ dup WHILE >R [[ ; immediate
|
||||
: LOOP-LIST ( -- ) ]] R> REPEAT drop [[ ; immediate
|
||||
: UNLIST ( -- ) POSTPONE rdrop ; immediate
|
||||
|
||||
\ Helper words
|
||||
: .; [char] ; emit ;
|
||||
: !+ dup cell+ -rot ! ;
|
||||
: ($+) ( cas us cad ud -- cad us+ud ; append source string to destination )
|
||||
swap >R 2dup + R> swap >R tuck + swap >R swap move R> R> ;
|
||||
|
||||
\ --- Working Record Fields
|
||||
current @ FIELDS current !
|
||||
2variable person
|
||||
2variable birthday
|
||||
2variable relationship
|
||||
2variable hobby
|
||||
current ! FIELDS >order
|
||||
|
||||
\ --- Ring list
|
||||
: new-node ( -- node ; root of circular list )
|
||||
here dup , ;
|
||||
: do-link ( node new-node -- ; do link after current node )
|
||||
over @ over ! swap ! ;
|
||||
: empty-ring? ( node -- f ) dup @ = ;
|
||||
|
||||
: RING ( node -- ) ]] dup BEGIN @ 2dup <> WHILE 2>R [[ ; immediate
|
||||
: LOOP-RING ( -- ) ]] 2R> REPEAT 2drop [[ ; immediate
|
||||
: UNRING ( -- ) postpone 2RDROP ; immediate
|
||||
|
||||
:noname
|
||||
s" " person 2! s" 9999.01.01" birthday 2! s" " relationship 2! s" " hobby 2! ;
|
||||
new-node swap , CONSTANT OLD
|
||||
|
||||
\ --- Insertion into ring
|
||||
: node>xt ( node -- xt ) cell+ @ ;
|
||||
: bday@ ( node -- ca u ) node>xt execute birthday 2@ ;
|
||||
: datecmp ( node node -- -1|0|1 ) bday@ rot bday@ compare ;
|
||||
|
||||
: search-by-date ( new-node node -- node' ; Linear search by birth date )
|
||||
dup
|
||||
RING ( new-node node )
|
||||
over I datecmp 0<
|
||||
IF nip UNRING exit
|
||||
ELSE drop I THEN
|
||||
LOOP-RING nip ;
|
||||
|
||||
: insert-ordered ( new-node -- )
|
||||
OLD dup empty-ring? 0=
|
||||
IF over swap search-by-date THEN swap do-link ;
|
||||
|
||||
\ --- Field compiling
|
||||
: fld: [char] ; parse postpone sliteral ; immediate
|
||||
|
||||
: _sdb-walk ( MACRO: | xt[--] -- | )
|
||||
]] -SDB?EXIT here dup SDB cell+
|
||||
LIST I name>int swap !+ LOOP-LIST [[ 1 cells ]] literal -
|
||||
DO I @ |comp| [[ -1 cells ]] literal +LOOP [[ ; immediate
|
||||
|
||||
2variable category
|
||||
: msg_nocategory ( -- )
|
||||
." No such category!" cr ;
|
||||
|
||||
\ --- USER definitions
|
||||
user: >order current @ USER: current !
|
||||
: bye bye ;
|
||||
: .s .s ;
|
||||
: newline cr ;
|
||||
: godb SDB >order ;
|
||||
: .keys SDB wordlist-words ;
|
||||
: .person person 2@ type ;
|
||||
: .birthday birthday 2@ type ;
|
||||
: .relationship relationship 2@ type ;
|
||||
: .hobby hobby 2@ type ;
|
||||
: .record .person 1 spaces .birthday .; .relationship .; .hobby .; ;
|
||||
|
||||
: .last SDB cell+ @ dup
|
||||
IF name>int execute .record ELSE drop THEN ;
|
||||
|
||||
: .lastbycategory ( "field-name" -- )
|
||||
here 0 category 2! parse-name FIELDS search-wordlist
|
||||
IF sdb cell+
|
||||
LIST ( xt )
|
||||
I name>int execute dup execute 2@ category 2@ 2swap search nip nip 0=
|
||||
IF .record cr dup execute 2@ category 2@ ($+) category 2! THEN
|
||||
LOOP-LIST drop
|
||||
ELSE ['] msg_nocategory stderr OUTFILE-EXECUTE THEN ;
|
||||
|
||||
: .bydate ( -- )
|
||||
OLD RING I node>xt execute .record cr LOOP-RING ;
|
||||
|
||||
: .sdb _sdb-walk | execute .record cr | ;
|
||||
: sdb-dump _sdb-walk | execute ." +: " .record cr | ;
|
||||
|
||||
: dump ( "db-file" -- )
|
||||
parse-name w/o
|
||||
CREATE-FILE throw dup
|
||||
['] sdb-dump swap OUTFILE-EXECUTE
|
||||
CLOSE-FILE throw ;
|
||||
|
||||
: load ( "db-file" -- ) include ;
|
||||
|
||||
: +: ( "person birth-day;relationship;hobby; -- )
|
||||
new-node dup cell+ >R 0 , \ post `xt'
|
||||
current @ SDB current ! \ compile into `sdb' wordlist
|
||||
: latestxt dup R> ! \ fix-up `xt'
|
||||
]] literal >name name>string person 2! fld: birthday 2! fld: relationship 2! fld: hobby 2! ; [[
|
||||
current !
|
||||
insert-ordered ;
|
||||
|
||||
: HELP cr
|
||||
cr ." KEYWORDS: " USER: wordlist-words cr ." ---"
|
||||
cr ." Prefix `.' means print"
|
||||
cr ." To add a record, type: +: name yyyy.mm.dd;relationship;hobby;"
|
||||
cr cr ;
|
||||
current !
|
||||
SEAL godb
|
||||
340
Task/Simple-database/Go/simple-database.go
Normal file
340
Task/Simple-database/Go/simple-database.go
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Database record format. Time stamp and name are required.
|
||||
// Tags and notes are optional.
|
||||
type Item struct {
|
||||
Stamp time.Time
|
||||
Name string
|
||||
Tags []string `json:",omitempty"`
|
||||
Notes string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Item implements stringer interface
|
||||
func (i *Item) String() string {
|
||||
s := i.Stamp.Format(time.ANSIC) + "\n Name: " + i.Name
|
||||
if len(i.Tags) > 0 {
|
||||
s = fmt.Sprintf("%s\n Tags: %v", s, i.Tags)
|
||||
}
|
||||
if i.Notes > "" {
|
||||
s += "\n Notes: " + i.Notes
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// collection of Items
|
||||
type db []*Item
|
||||
|
||||
// db implements sort.Interface
|
||||
func (d db) Len() int { return len(d) }
|
||||
func (d db) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
|
||||
func (d db) Less(i, j int) bool { return d[i].Stamp.Before(d[j].Stamp) }
|
||||
|
||||
// hard coded database file name
|
||||
const fn = "sdb.json"
|
||||
|
||||
func main() {
|
||||
if len(os.Args) == 1 {
|
||||
latest()
|
||||
return
|
||||
}
|
||||
switch os.Args[1] {
|
||||
case "add":
|
||||
add()
|
||||
case "latest":
|
||||
latest()
|
||||
case "tags":
|
||||
tags()
|
||||
case "all":
|
||||
all()
|
||||
case "help":
|
||||
help()
|
||||
default:
|
||||
usage("unrecognized command")
|
||||
}
|
||||
}
|
||||
|
||||
func usage(err string) {
|
||||
if err > "" {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println(`usage: sdb [command] [data]
|
||||
where command is one of add, latest, tags, all, or help.`)
|
||||
}
|
||||
|
||||
func help() {
|
||||
usage("")
|
||||
fmt.Println(`
|
||||
Commands must be in lower case.
|
||||
If no command is specified, the default command is latest.
|
||||
|
||||
Latest prints the latest item.
|
||||
All prints all items in chronological order.
|
||||
Tags prints the lastest item for each tag.
|
||||
Help prints this message.
|
||||
|
||||
Add adds data as a new record. The format is,
|
||||
|
||||
name [tags] [notes]
|
||||
|
||||
Name is the name of the item and is required for the add command.
|
||||
|
||||
Tags are optional. A tag is a single word.
|
||||
A single tag can be specified without enclosing brackets.
|
||||
Multiple tags can be specified by enclosing them in square brackets.
|
||||
|
||||
Text remaining after tags is taken as notes. Notes do not have to be
|
||||
enclosed in quotes or brackets. The brackets above are only showing
|
||||
that notes are optional.
|
||||
|
||||
Quotes may be useful however--as recognized by your operating system shell
|
||||
or command line--to allow entry of arbitrary text. In particular, quotes
|
||||
or escape characters may be needed to prevent the shell from trying to
|
||||
interpret brackets or other special characters.
|
||||
|
||||
Examples:
|
||||
sdb add Bookends // no tags, no notes
|
||||
sdb add Bookends rock my favorite // tag: rock, notes: my favorite
|
||||
sdb add Bookends [rock folk] // two tags
|
||||
sdb add Bookends [] "Simon & Garfunkel" // notes, no tags
|
||||
sdb add "Simon&Garfunkel [artist]" // name: Simon&Garfunkel, tag: artist
|
||||
|
||||
As shown in the last example, if you use features of your shell to pass
|
||||
all data as a single string, the item name and tags will still be identified
|
||||
by separating whitespace.
|
||||
|
||||
The database is stored in JSON format in the file "sdb.json"
|
||||
`)
|
||||
}
|
||||
|
||||
// load data for read only purposes.
|
||||
func load() (db, bool) {
|
||||
d, f, ok := open()
|
||||
if ok {
|
||||
f.Close()
|
||||
if len(d) == 0 {
|
||||
fmt.Println("no items")
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
return d, ok
|
||||
}
|
||||
|
||||
// open database, leave open
|
||||
func open() (d db, f *os.File, ok bool) {
|
||||
var err error
|
||||
f, err = os.OpenFile(fn, os.O_RDWR|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
fmt.Println("cant open??")
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
jd := json.NewDecoder(f)
|
||||
err = jd.Decode(&d)
|
||||
// EOF just means file was empty. That's okay with us.
|
||||
if err != nil && err != io.EOF {
|
||||
fmt.Println(err)
|
||||
f.Close()
|
||||
return
|
||||
}
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// handle latest command
|
||||
func latest() {
|
||||
d, ok := load()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
sort.Sort(d)
|
||||
fmt.Println(d[len(d)-1])
|
||||
}
|
||||
|
||||
// handle all command
|
||||
func all() {
|
||||
d, ok := load()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
sort.Sort(d)
|
||||
for _, i := range d {
|
||||
fmt.Println("-----------------------------------")
|
||||
fmt.Println(i)
|
||||
}
|
||||
fmt.Println("-----------------------------------")
|
||||
}
|
||||
|
||||
// handle tags command
|
||||
func tags() {
|
||||
d, ok := load()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// we have to traverse the entire list to collect tags so there
|
||||
// is no point in sorting at this point.
|
||||
// collect set of unique tags associated with latest item for each
|
||||
latest := make(map[string]*Item)
|
||||
for _, item := range d {
|
||||
for _, tag := range item.Tags {
|
||||
li, ok := latest[tag]
|
||||
if !ok || item.Stamp.After(li.Stamp) {
|
||||
latest[tag] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
// invert to set of unique items, associated with subset of tags
|
||||
// for which the item is the latest.
|
||||
type itemTags struct {
|
||||
item *Item
|
||||
tags []string
|
||||
}
|
||||
inv := make(map[*Item][]string)
|
||||
for tag, item := range latest {
|
||||
inv[item] = append(inv[item], tag)
|
||||
}
|
||||
// now we sort just the items we will output
|
||||
li := make(db, len(inv))
|
||||
i := 0
|
||||
for item := range inv {
|
||||
li[i] = item
|
||||
i++
|
||||
}
|
||||
sort.Sort(li)
|
||||
// finally ready to print
|
||||
for _, item := range li {
|
||||
tags := inv[item]
|
||||
fmt.Println("-----------------------------------")
|
||||
fmt.Println("Latest item with tags", tags)
|
||||
fmt.Println(item)
|
||||
}
|
||||
fmt.Println("-----------------------------------")
|
||||
}
|
||||
|
||||
// handle add command
|
||||
func add() {
|
||||
if len(os.Args) < 3 {
|
||||
usage("add command requires data")
|
||||
return
|
||||
} else if len(os.Args) == 3 {
|
||||
add1()
|
||||
} else {
|
||||
add4()
|
||||
}
|
||||
}
|
||||
|
||||
// add command with one data string. look for ws as separators.
|
||||
func add1() {
|
||||
data := strings.TrimLeftFunc(os.Args[2], unicode.IsSpace)
|
||||
if data == "" {
|
||||
// data must have at least some non-whitespace
|
||||
usage("invalid name")
|
||||
return
|
||||
}
|
||||
sep := strings.IndexFunc(data, unicode.IsSpace)
|
||||
if sep < 0 {
|
||||
// data consists only of a name
|
||||
addItem(data, nil, "")
|
||||
return
|
||||
}
|
||||
name := data[:sep]
|
||||
data = strings.TrimLeftFunc(data[sep:], unicode.IsSpace)
|
||||
if data == "" {
|
||||
// nevermind trailing ws, it's still only a name
|
||||
addItem(name, nil, "")
|
||||
return
|
||||
}
|
||||
if data[0] == '[' {
|
||||
sep = strings.Index(data, "]")
|
||||
if sep < 0 {
|
||||
// close bracketed list for the user. no notes.
|
||||
addItem(name, strings.Fields(data[1:]), "")
|
||||
} else {
|
||||
// brackets make things easy
|
||||
addItem(name, strings.Fields(data[1:sep]),
|
||||
strings.TrimLeftFunc(data[sep+1:], unicode.IsSpace))
|
||||
}
|
||||
return
|
||||
}
|
||||
sep = strings.IndexFunc(data, unicode.IsSpace)
|
||||
if sep < 0 {
|
||||
// remaining word is a tag
|
||||
addItem(name, []string{data}, "")
|
||||
} else {
|
||||
// there's a tag and some data
|
||||
addItem(name, []string{data[:sep]},
|
||||
strings.TrimLeftFunc(data[sep+1:], unicode.IsSpace))
|
||||
}
|
||||
}
|
||||
|
||||
// add command with multiple strings remaining on command line
|
||||
func add4() {
|
||||
name := os.Args[2]
|
||||
tag1 := os.Args[3]
|
||||
if tag1[0] != '[' {
|
||||
// no brackets makes things easy
|
||||
addItem(name, []string{tag1}, strings.Join(os.Args[4:], " "))
|
||||
return
|
||||
}
|
||||
if tag1[len(tag1)-1] == ']' {
|
||||
// tags all in one os.Arg is easy too
|
||||
addItem(name, strings.Fields(tag1[1:len(tag1)-1]),
|
||||
strings.Join(os.Args[4:], " "))
|
||||
return
|
||||
}
|
||||
// start a list for tags
|
||||
var tags []string
|
||||
if tag1 > "[" {
|
||||
tags = []string{tag1[1:]}
|
||||
}
|
||||
for x, tag := range os.Args[4:] {
|
||||
if tag[len(tag)-1] != ']' {
|
||||
tags = append(tags, tag)
|
||||
} else {
|
||||
// found end of tag list
|
||||
if tag > "]" {
|
||||
tags = append(tags, tag[:len(tag)-1])
|
||||
}
|
||||
addItem(name, tags, strings.Join(os.Args[5+x:], " "))
|
||||
return
|
||||
}
|
||||
}
|
||||
// close bracketed list for the user. no notes.
|
||||
addItem(name, tags, "")
|
||||
}
|
||||
|
||||
// complete the add command
|
||||
func addItem(name string, tags []string, notes string) {
|
||||
db, f, ok := open()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
// add the item and format JSON
|
||||
db = append(db, &Item{time.Now(), name, tags, notes})
|
||||
sort.Sort(db)
|
||||
js, err := json.MarshalIndent(db, "", " ")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
// time to overwrite the file
|
||||
if _, err = f.Seek(0, 0); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
f.Truncate(0)
|
||||
if _, err = f.Write(js); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
128
Task/Simple-database/Haskell/simple-database.hs
Normal file
128
Task/Simple-database/Haskell/simple-database.hs
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import Control.Monad.State
|
||||
import Data.List (sortBy, nub)
|
||||
import System.Environment (getArgs, getProgName)
|
||||
import System.Directory (doesFileExist)
|
||||
import System.IO (openFile, hGetContents, hClose, IOMode(..),
|
||||
Handle, hPutStrLn)
|
||||
|
||||
-- for storing dates
|
||||
data Date = Date Integer Int Int deriving (Show, Read, Eq, Ord)
|
||||
|
||||
-- for storing database items
|
||||
data Item = Item {description :: String
|
||||
,category :: [String]
|
||||
,date :: Date
|
||||
,optional :: [String]}
|
||||
deriving (Show, Read)
|
||||
|
||||
-- a state monad transformer which wraps IO actions.
|
||||
-- the database (state) is passed implicitly between functions.
|
||||
type ItemList a = StateT [Item] IO a
|
||||
|
||||
-- add an item to the database
|
||||
addItem :: Item -> ItemList ()
|
||||
addItem i = modify (++ [i])
|
||||
|
||||
-- get the newest of a list of items
|
||||
latest :: [Item] -> [Item]
|
||||
latest [] = []
|
||||
latest [x]= [x]
|
||||
latest xs = take 1 $ sortBy newer xs
|
||||
|
||||
-- compare two items to see which one is newer
|
||||
newer :: Item -> Item -> Ordering
|
||||
newer a b = compare (date b) (date a)
|
||||
|
||||
-- list all different categories (no duplicates)
|
||||
categories :: ItemList [String]
|
||||
categories = liftM (nub . concatMap category) get
|
||||
|
||||
-- list only the items with the given category tag
|
||||
filterByCategory :: String -> ItemList [Item]
|
||||
filterByCategory c = liftM (filter (\i -> c `elem` category i)) get
|
||||
|
||||
-- get the newest of all items
|
||||
lastOfAll :: ItemList [Item]
|
||||
lastOfAll = liftM latest get
|
||||
|
||||
-- get the newest item in each category
|
||||
latestByCategory :: ItemList [Item]
|
||||
latestByCategory = do
|
||||
cats <- categories
|
||||
filt <- mapM filterByCategory cats
|
||||
return $ concatMap latest filt
|
||||
|
||||
-- sort all items chronologically, newest first
|
||||
sortByDate :: ItemList [Item]
|
||||
sortByDate = liftM (sortBy newer) get
|
||||
|
||||
toScreen :: Item -> IO ()
|
||||
toScreen (Item desc cats (Date y m d) opt) = putStrLn $
|
||||
"Description:\t" ++ desc ++ "\nCategories:\t" ++ show cats ++
|
||||
"\nDate:\t\t" ++ show y ++ "-" ++ show m ++ "-" ++ show d ++
|
||||
"\nOther info:\t" ++ show opt
|
||||
|
||||
-- command line argument handling
|
||||
-- if the user called the program with the option "add", the
|
||||
-- new item is returned to main so that it can be saved to disk.
|
||||
-- the argument "opt" is a list.
|
||||
arguments :: ItemList [Item]
|
||||
arguments = do
|
||||
args <- liftIO getArgs
|
||||
case args of
|
||||
("add":desc:cat:year:month:day:opt) -> do
|
||||
let newItem = parseItem args
|
||||
addItem newItem
|
||||
return [newItem]
|
||||
("latest":[]) -> do
|
||||
item <- lastOfAll
|
||||
lift $ mapM_ toScreen item
|
||||
return []
|
||||
("category":[]) -> do
|
||||
items <- latestByCategory
|
||||
lift $ mapM_ toScreen items
|
||||
return []
|
||||
("all":[]) -> do
|
||||
sorted <- sortByDate
|
||||
lift $ mapM_ toScreen sorted
|
||||
return []
|
||||
_ -> do
|
||||
lift usage
|
||||
return []
|
||||
|
||||
parseItem :: [String] -> Item
|
||||
parseItem (_:desc:cat:year:month:day:opt) =
|
||||
Item {description = desc, category = words cat,
|
||||
date = Date (read year) (read month) (read day),
|
||||
optional = opt}
|
||||
|
||||
usage :: IO ()
|
||||
usage = do
|
||||
progName <- getProgName
|
||||
putStrLn $ "Usage: " ++ progName ++ " add|all|category|latest \
|
||||
\OPTIONS\n\nadd \"description\" \"category1 category2\"... \
|
||||
\year month day [\"note1\" \"note2\"...]\n\tAdds a new record \
|
||||
\to the database.\n\nall\n\tPrints all items in chronological \
|
||||
\order.\n\ncategory\n\tPrints the latest item for each category.\
|
||||
\\n\nlatest\n\tPrints the latest item."
|
||||
|
||||
-- the program creates, reads and writes to a file in the current directory
|
||||
main :: IO ()
|
||||
main = do
|
||||
progName <- getProgName
|
||||
let fileName = progName ++ ".db"
|
||||
e <- doesFileExist fileName
|
||||
if e
|
||||
then do
|
||||
hr <- openFile fileName ReadMode
|
||||
f <- hGetContents hr
|
||||
v <- evalStateT arguments (map read $ lines f)
|
||||
hClose hr -- must be called after working with contents!
|
||||
hw <- openFile fileName AppendMode
|
||||
mapM_ (hPutStrLn hw . show) v
|
||||
hClose hw
|
||||
else do
|
||||
v <- evalStateT arguments []
|
||||
hw <- openFile fileName WriteMode
|
||||
mapM_ (hPutStrLn hw . show) v
|
||||
hClose hw
|
||||
108
Task/Simple-database/J/simple-database-1.j
Normal file
108
Task/Simple-database/J/simple-database-1.j
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
HELP=: 0 :0
|
||||
Commands:
|
||||
|
||||
DBNAME add DATA
|
||||
DBNAME display the latest entry
|
||||
DBNAME display the latest entry where CATEGORY contains WORD
|
||||
DBNAME display all entries
|
||||
DBNAME display all entries order by CATEGORY
|
||||
|
||||
1) The first add with new DBNAME assign category names.
|
||||
2) lower case arguments verbatim.
|
||||
3) UPPER CASE: substitute your values.
|
||||
|
||||
Examples, having saved this program as a file named s :
|
||||
$ jconsole s simple.db display all entries
|
||||
$ jconsole s simple.db add "first field" "2nd field"
|
||||
)
|
||||
|
||||
Q=: '''' NB. quote character
|
||||
time=: 6!:0@:('YYYY-MM-DD:hh:mm:ss.sss'"_)
|
||||
|
||||
embed=: >@:{.@:[ , ] , >@:{:@:[
|
||||
assert '(x+y)' -: '()' embed 'x+y'
|
||||
|
||||
Duplicate=: 1 :'#~ m&= + 1 #~ #'
|
||||
assert 0 1 2 3 3 4 -: 3 Duplicate i.5
|
||||
|
||||
prepare=: LF ,~ [: }:@:; (Q;Q,';')&embed@:(Q Duplicate)&.>@:(;~ time)
|
||||
assert (-: }.@:".@:}:@:prepare) 'boxed';'';'li''st';'of';'''strings'''
|
||||
|
||||
categorize=: dyad define
|
||||
i=. x i. y
|
||||
if. (1 (~: #) i) +. i (= #) x do.
|
||||
smoutput 'choose 1 of'
|
||||
smoutput x
|
||||
exit 1
|
||||
end.
|
||||
{. i NB. "index of" frame has rank 1.
|
||||
)
|
||||
assert 0 -: 'abc' categorize 'a'
|
||||
|
||||
loadsdb=: (<'time') (<0 0)} ".;._2@:(1!:1)
|
||||
|
||||
Dispatch=: conjunction define
|
||||
help
|
||||
:
|
||||
commands=. y
|
||||
command=. {. commands
|
||||
x (u`help@.(command(i.~ ;:)n)) }.commands
|
||||
)
|
||||
|
||||
NB. the long fork in show groups (": #~ (1 1 embed (1j1 }:@:# (1 #~ #))))
|
||||
show=: smoutput@:(": #~ 1 1 embed 1j1 }:@:# 1 #~ #)
|
||||
|
||||
in=: +./@:E.
|
||||
assert 'the' in'quick brown fox jumps over the lazy dog'
|
||||
assert 'the'-.@:in'QUICK BROWN FOX JUMPS OVER THE LAZY DOG'
|
||||
|
||||
where=: dyad define
|
||||
'category contains word'=. 3 {. y
|
||||
if. 'contains' -.@:-: contains do.
|
||||
help''
|
||||
exit 1
|
||||
end.
|
||||
i=. x ({.@:[ categorize <@:]) category
|
||||
j=. {: I. ; word&in&.> i {"1 x
|
||||
if. 0 (= #) j do.
|
||||
smoutput 'no matches'
|
||||
else.
|
||||
x (show@:{~ 0&,) j
|
||||
end.
|
||||
)
|
||||
|
||||
entry=: 4 : 0
|
||||
if. a: = y do.
|
||||
show@:({. ,: {:) x
|
||||
else.
|
||||
x ''`where Dispatch'where' y
|
||||
end.
|
||||
)
|
||||
|
||||
latest=: ''`entry Dispatch'entry'
|
||||
the=: ''`latest Dispatch'latest'
|
||||
|
||||
by=: 4 : 0
|
||||
i=. x (categorize~ {.)~ y
|
||||
show ({. , (/: i&{"1)@:}.) x
|
||||
)
|
||||
|
||||
order=: ''`by Dispatch'by'
|
||||
|
||||
entries=: 4 : 0
|
||||
if. a: = y do.
|
||||
show x
|
||||
else.
|
||||
x ''`order Dispatch'order' y
|
||||
end.
|
||||
)
|
||||
|
||||
all=: ''`entries Dispatch'entries'
|
||||
|
||||
help=: smoutput@:(HELP"_)
|
||||
add=: 1!:3~ prepare NB. minimal---no error tests
|
||||
display=: (the`all Dispatch'the all'~ loadsdb)~ NB. load the simple db for some sort of display
|
||||
|
||||
({. add`display Dispatch'add display' }.)@:(2&}.)ARGV
|
||||
|
||||
exit 0
|
||||
13
Task/Simple-database/J/simple-database-2.j
Normal file
13
Task/Simple-database/J/simple-database-2.j
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
D='jconsole s dataflow'
|
||||
|
||||
$D add name expression algebraic rank valence example explanation
|
||||
$D add insert 'f/ y' 'insert f within y' infinite dyad 'sum=: +/' 'continued_fraction=:+`%/'
|
||||
$D add fork '(f g h)y' 'g(f(y),h(y))' infinite monad 'average=: +/ % #' 'sum divided by tally'
|
||||
$D add hook '(f g)y' 'f(y,g(y))' infinite monad '(/: 2&{"1)table' 'sort by third column'
|
||||
$D add hook 'x(f g)y' 'f(x,g(y))' infinite dyad 'display verb in s' 'a reflexive dyadic hook'
|
||||
$D add fork 'x(f g h)y' 'g(f(x,y),h(x,y))' infinite dyad '2j1(+ * -)9 12' 'product of sum and difference'
|
||||
$D add reflexive 'f~ y' 'f(y,y)' infinite monad '^~y' 'y raised to the power of y'
|
||||
$D add passive 'x f~ y' 'f(y,x)' 'ranks of f' dyad '(%~ i.@:>:) 8x' '8 intervals from 0 to 1'
|
||||
$D add atop 'f@g y' 'f(g(y))' 'rank of g' monad '*:@(+/)' 'square the sum'
|
||||
$D add atop 'x f@g y' 'f(g(x,y))' 'rank of g' dyad '>@{.' '(lisp) open the car'
|
||||
$D add 'many more!'
|
||||
76
Task/Simple-database/J/simple-database-3.j
Normal file
76
Task/Simple-database/J/simple-database-3.j
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
$ . input # source the input
|
||||
$ echo $D
|
||||
jconsole s dataflow
|
||||
$ $D display the latest entry
|
||||
┌───────────────────────┬──────────┬──────────┬─────────┬────┬───────┬───────┬───────────┐
|
||||
│time │name │expression│algebraic│rank│valence│example│explanation│
|
||||
│2012-02-07:20:36:54.749│many more!│ │ │ │ │ │ │
|
||||
└───────────────────────┴──────────┴──────────┴─────────┴────┴───────┴───────┴───────────┘
|
||||
$ $D display the latest entry where 'part of speech' contains verb
|
||||
choose 1 of
|
||||
┌────┬────┬──────────┬─────────┬────┬───────┬───────┬───────────┐
|
||||
│time│name│expression│algebraic│rank│valence│example│explanation│
|
||||
└────┴────┴──────────┴─────────┴────┴───────┴───────┴───────────┘
|
||||
$ $D display the latest entry where example contains average
|
||||
┌───────────────────────┬────┬──────────┬────────────┬────────┬───────┬────────────────┬────────────────────┐
|
||||
│time │name│expression│algebraic │rank │valence│example │explanation │
|
||||
│2012-02-07:20:36:54.564│fork│(f g h)y │g(f(y),h(y))│infinite│monad │average=: +/ % #│sum divided by tally│
|
||||
└───────────────────────┴────┴──────────┴────────────┴────────┴───────┴────────────────┴────────────────────┘
|
||||
$ $D display all entries ordre by valence # oops! transposition typo.
|
||||
Commands:
|
||||
|
||||
DBNAME add DATA
|
||||
DBNAME display the latest entry
|
||||
DBNAME display the latest entry where CATEGORY contains WORD
|
||||
DBNAME display all entries
|
||||
DBNAME display all entries order by CATEGORY
|
||||
|
||||
1) The first add with new DBNAME assign category names.
|
||||
2) lower case arguments verbatim.
|
||||
3) UPPER CASE: substitute your values.
|
||||
|
||||
Examples, having saved this program as a file named s :
|
||||
$ jconsole s simple.db display all entries
|
||||
$ jconsole s simple.db add "first field" "2nd field"
|
||||
|
||||
$ $D display all entries order by valence
|
||||
┌───────────────────────┬──────────┬──────────┬─────────────────┬──────────┬───────┬─────────────────┬─────────────────────────────┐
|
||||
│time │name │expression│algebraic │rank │valence│example │explanation │
|
||||
│2012-02-08:23:45:06.539│many more!│ │ │ │ │ │ │
|
||||
│2012-02-08:23:45:06.329│insert │f/ y │insert f within y│infinite │dyad │sum=: +/ │continued_fraction=:+`%/ │
|
||||
│2012-02-08:23:45:06.400│hook │x(f g)y │f(x,g(y)) │infinite │dyad │display verb in s│a reflexive dyadic hook │
|
||||
│2012-02-08:23:45:06.426│fork │x(f g h)y │g(f(x,y),h(x,y)) │infinite │dyad │2j1(+ * -)9 12 │product of sum and difference│
|
||||
│2012-02-08:23:45:06.471│passive │x f~ y │f(y,x) │ranks of f│dyad │(%~ i.@:>:) 8x │8 intervals from 0 to 1 │
|
||||
│2012-02-08:23:45:06.515│atop │x f@g y │f(g(x,y)) │rank of g │dyad │>@{. │(lisp) open the car │
|
||||
│2012-02-08:23:45:06.353│fork │(f g h)y │g(f(y),h(y)) │infinite │monad │average=: +/ % # │sum divided by tally │
|
||||
│2012-02-08:23:45:06.376│hook │(f g)y │f(y,g(y)) │infinite │monad │(/: 2&{"1)table │sort by third column │
|
||||
│2012-02-08:23:45:06.448│reflexive │f~ y │f(y,y) │infinite │monad │^~y │y raised to the power of y │
|
||||
│2012-02-08:23:45:06.493│atop │f@g y │f(g(y)) │rank of g │monad │*:@(+/) │square the sum │
|
||||
└───────────────────────┴──────────┴──────────┴─────────────────┴──────────┴───────┴─────────────────┴─────────────────────────────┘
|
||||
$ $D display all entries
|
||||
┌───────────────────────┬──────────┬──────────┬─────────────────┬──────────┬───────┬─────────────────┬─────────────────────────────┐
|
||||
│time │name │expression│algebraic │rank │valence│example │explanation │
|
||||
│2012-02-08:23:45:06.329│insert │f/ y │insert f within y│infinite │dyad │sum=: +/ │continued_fraction=:+`%/ │
|
||||
│2012-02-08:23:45:06.353│fork │(f g h)y │g(f(y),h(y)) │infinite │monad │average=: +/ % # │sum divided by tally │
|
||||
│2012-02-08:23:45:06.376│hook │(f g)y │f(y,g(y)) │infinite │monad │(/: 2&{"1)table │sort by third column │
|
||||
│2012-02-08:23:45:06.400│hook │x(f g)y │f(x,g(y)) │infinite │dyad │display verb in s│a reflexive dyadic hook │
|
||||
│2012-02-08:23:45:06.426│fork │x(f g h)y │g(f(x,y),h(x,y)) │infinite │dyad │2j1(+ * -)9 12 │product of sum and difference│
|
||||
│2012-02-08:23:45:06.448│reflexive │f~ y │f(y,y) │infinite │monad │^~y │y raised to the power of y │
|
||||
│2012-02-08:23:45:06.471│passive │x f~ y │f(y,x) │ranks of f│dyad │(%~ i.@:>:) 8x │8 intervals from 0 to 1 │
|
||||
│2012-02-08:23:45:06.493│atop │f@g y │f(g(y)) │rank of g │monad │*:@(+/) │square the sum │
|
||||
│2012-02-08:23:45:06.515│atop │x f@g y │f(g(x,y)) │rank of g │dyad │>@{. │(lisp) open the car │
|
||||
│2012-02-08:23:45:06.539│many more!│ │ │ │ │ │ │
|
||||
└───────────────────────┴──────────┴──────────┴─────────────────┴──────────┴───────┴─────────────────┴─────────────────────────────┘
|
||||
$ cat dataflow
|
||||
'2012-02-08:23:45:06.304';'name';'expression';'algebraic';'rank';'valence';'example';'explanation'
|
||||
'2012-02-08:23:45:06.329';'insert';'f/ y';'insert f within y';'infinite';'dyad';'sum=: +/';'continued_fraction=:+`%/'
|
||||
'2012-02-08:23:45:06.353';'fork';'(f g h)y';'g(f(y),h(y))';'infinite';'monad';'average=: +/ % #';'sum divided by tally'
|
||||
'2012-02-08:23:45:06.376';'hook';'(f g)y';'f(y,g(y))';'infinite';'monad';'(/: 2&{"1)table';'sort by third column'
|
||||
'2012-02-08:23:45:06.400';'hook';'x(f g)y';'f(x,g(y))';'infinite';'dyad';'display verb in s';'a reflexive dyadic hook'
|
||||
'2012-02-08:23:45:06.426';'fork';'x(f g h)y';'g(f(x,y),h(x,y))';'infinite';'dyad';'2j1(+ * -)9 12';'product of sum and difference'
|
||||
'2012-02-08:23:45:06.448';'reflexive';'f~ y';'f(y,y)';'infinite';'monad';'^~y';'y raised to the power of y'
|
||||
'2012-02-08:23:45:06.471';'passive';'x f~ y';'f(y,x)';'ranks of f';'dyad';'(%~ i.@:>:) 8x';'8 intervals from 0 to 1'
|
||||
'2012-02-08:23:45:06.493';'atop';'f@g y';'f(g(y))';'rank of g';'monad';'*:@(+/)';'square the sum'
|
||||
'2012-02-08:23:45:06.515';'atop';'x f@g y';'f(g(x,y))';'rank of g';'dyad';'>@{.';'(lisp) open the car'
|
||||
'2012-02-08:23:45:06.539';'many more!'
|
||||
$
|
||||
36
Task/Simple-database/PicoLisp/simple-database.l
Normal file
36
Task/Simple-database/PicoLisp/simple-database.l
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/pil
|
||||
|
||||
(de usage ()
|
||||
(prinl
|
||||
"Usage:^J\
|
||||
sdb <file> add <title> <cat> <date> ... Add a new entry^J\
|
||||
sdb <file> get <title> Retrieve an entry^J\
|
||||
sdb <file> latest Print the latest entry^J\
|
||||
sdb <file> categories Print the latest for each cat^J\
|
||||
sdb <file> Print all, sorted by date" ) )
|
||||
|
||||
(de printEntry (E)
|
||||
(apply println (cdddr E) (car E) (cadr E) (datStr (caddr E))) )
|
||||
|
||||
(ifn (setq *File (opt))
|
||||
(usage)
|
||||
(case (opt)
|
||||
(add
|
||||
(let (Ttl (opt) Cat (opt))
|
||||
(if (strDat (opt))
|
||||
(rc *File Ttl (cons Cat @ (argv)))
|
||||
(prinl "Bad date") ) ) )
|
||||
(get
|
||||
(let Ttl (opt)
|
||||
(when (rc *File Ttl)
|
||||
(printEntry (cons Ttl @)) ) ) )
|
||||
(latest
|
||||
(printEntry (maxi caddr (in *File (read)))) )
|
||||
(categories
|
||||
(for Cat (by cadr group (in *File (read)))
|
||||
(printEntry (maxi caddr Cat)) ) )
|
||||
(NIL
|
||||
(mapc printEntry (by caddr sort (in *File (read)))) )
|
||||
(T (usage)) ) )
|
||||
|
||||
(bye)
|
||||
154
Task/Simple-database/Pike/simple-database.pike
Normal file
154
Task/Simple-database/Pike/simple-database.pike
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
mapping db = ([]);
|
||||
|
||||
mapping make_episode(string series, string title, string episode, array date)
|
||||
{
|
||||
return ([ "series":series, "episode":episode, "title":title, "date":date ]);
|
||||
}
|
||||
|
||||
void print_episode(mapping episode)
|
||||
{
|
||||
write(" %-30s %10s %-30s (%{%d.%})\n",
|
||||
episode->series, episode->episode, episode->title, episode->date);
|
||||
}
|
||||
|
||||
void print_series(mapping series)
|
||||
{
|
||||
write("%-30s %-10s\n", series->series, series->status);
|
||||
map(series->episodes, print_episode);
|
||||
}
|
||||
|
||||
void dump_db(mapping database)
|
||||
{
|
||||
foreach(database; string name; mapping series)
|
||||
{
|
||||
print_series(series);
|
||||
}
|
||||
}
|
||||
|
||||
array get_latest(mapping database)
|
||||
{
|
||||
array latest = ({});
|
||||
foreach(database; string name; mapping series)
|
||||
{
|
||||
latest += ({ series->episodes[0] });
|
||||
}
|
||||
return latest;
|
||||
}
|
||||
|
||||
int(0..1) compare_date(array a, array b)
|
||||
{
|
||||
if (!arrayp(a) && !arrayp(b)) return false;
|
||||
if (!arrayp(a) || !sizeof(a)) return arrayp(b) && sizeof(b);
|
||||
if (!arrayp(b) || !sizeof(b)) return arrayp(a) && sizeof(a);
|
||||
if (a[0] == b[0]) return compare_date(a[1..], b[1..]);
|
||||
return a[0] < b[0];
|
||||
}
|
||||
|
||||
int(0..1) compare_by_date(mapping a, mapping b)
|
||||
{
|
||||
return compare_date(reverse(a->date), reverse(b->date));
|
||||
}
|
||||
|
||||
void watch_list(mapping database)
|
||||
{
|
||||
map(Array.sort_array(get_latest(database), compare_by_date),
|
||||
print_episode);
|
||||
}
|
||||
|
||||
string prompt_read(string prompt)
|
||||
{
|
||||
write("%s: ", prompt);
|
||||
return Stdio.stdin.gets();
|
||||
}
|
||||
|
||||
array parse_date(string date)
|
||||
{
|
||||
return (array(int))(date/".");
|
||||
}
|
||||
|
||||
mapping prompt_for_episode()
|
||||
{
|
||||
return make_episode(prompt_read("Series"),
|
||||
prompt_read("Title"),
|
||||
prompt_read("Episode"),
|
||||
parse_date(prompt_read("Date watched")));
|
||||
}
|
||||
|
||||
// pike offers encode_value() and decode_value() as standard ways
|
||||
// to save and read data, but that is not a human readable format.
|
||||
// therefore we are instead printing the structure as debug-output
|
||||
// which is a readable form as long as it only contains integers,
|
||||
// strings, mappings, arrays and multisets this format can be read by pike.
|
||||
// to read it we are creating a class that contains the data as a value,
|
||||
// which is then compiled and instantiated to allow us to pull the data out.
|
||||
void save_db(string filename, mapping database)
|
||||
{
|
||||
Stdio.write_file(filename, sprintf("%O", database));
|
||||
}
|
||||
|
||||
void watch_save()
|
||||
{
|
||||
save_db("pwatch", db);
|
||||
}
|
||||
|
||||
mapping load_db(string filename)
|
||||
{
|
||||
if (file_stat(filename))
|
||||
return compile_string("mixed data = " +
|
||||
Stdio.read_file(filename) + ";")()->data;
|
||||
else return ([]);
|
||||
}
|
||||
|
||||
mapping get_series(string name, mapping database)
|
||||
{
|
||||
return database[name];
|
||||
}
|
||||
|
||||
array get_episode_list(string series, mapping database)
|
||||
{
|
||||
return database[series]->episodes;
|
||||
}
|
||||
|
||||
void watch_new_series(string name, string status, mapping database)
|
||||
{
|
||||
database[name] = (["series":name, "status":status, "episodes":({}) ]);
|
||||
}
|
||||
|
||||
mapping get_or_add_series(string name, mapping database)
|
||||
{
|
||||
if (!database[name])
|
||||
{
|
||||
string answer = prompt_read("Add new series? [y/n]: ");
|
||||
if (answer == "y")
|
||||
watch_new_series(name, "active", database);
|
||||
}
|
||||
return database[name];
|
||||
}
|
||||
|
||||
void watch_add(mapping database)
|
||||
{
|
||||
mapping episode = prompt_for_episode();
|
||||
string series_name = episode->series;
|
||||
mapping series = get_or_add_series(series_name, database);
|
||||
if (!series)
|
||||
watch_add(database);
|
||||
else
|
||||
series->episodes = Array.unshift(series->episodes, episode);
|
||||
}
|
||||
|
||||
void watch_load()
|
||||
{
|
||||
db = load_db("pwatch");
|
||||
}
|
||||
|
||||
int main(int argc, array argv)
|
||||
{
|
||||
watch_load();
|
||||
if (argc>1 && argv[1] == "add")
|
||||
{
|
||||
watch_add(db);
|
||||
watch_save();
|
||||
}
|
||||
else
|
||||
watch_list(db);
|
||||
}
|
||||
108
Task/Simple-database/Python/simple-database.py
Normal file
108
Task/Simple-database/Python/simple-database.py
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
'''\
|
||||
Simple database for: http://rosettacode.org/wiki/Simple_database
|
||||
|
||||
'''
|
||||
|
||||
import argparse
|
||||
from argparse import Namespace
|
||||
import datetime
|
||||
import shlex
|
||||
|
||||
|
||||
def parse_args():
|
||||
'Set up, parse, and return arguments'
|
||||
|
||||
parser = argparse.ArgumentParser(epilog=globals()['__doc__'])
|
||||
|
||||
parser.add_argument('command', choices='add pl plc pa'.split(),
|
||||
help='''\
|
||||
add: Add a new entry
|
||||
pl: Print the latest entry
|
||||
plc: Print the latest entry for each category/tag
|
||||
pa: Print all entries sorted by a date''')
|
||||
parser.add_argument('-d', '--description',
|
||||
help='A description of the item. (e.g., title, name)')
|
||||
parser.add_argument('-t', '--tag',
|
||||
help=('''A category or tag (genre, topic, relationship '''
|
||||
'''such as “friend” or “family”)'''))
|
||||
parser.add_argument('-f', '--field', nargs=2, action='append',
|
||||
help='Other optional fields with value (can be repeated)')
|
||||
|
||||
return parser
|
||||
|
||||
def do_add(args, dbname):
|
||||
'Add a new entry'
|
||||
if args.description is None:
|
||||
args.description = ''
|
||||
if args.tag is None:
|
||||
args.tag = ''
|
||||
del args.command
|
||||
print('Writing record to %s' % dbname)
|
||||
with open(dbname, 'a') as db:
|
||||
db.write('%r\n' % args)
|
||||
|
||||
def do_pl(args, dbname):
|
||||
'Print the latest entry'
|
||||
print('Getting last record from %s' % dbname)
|
||||
with open(dbname, 'r') as db:
|
||||
for line in db: pass
|
||||
record = eval(line)
|
||||
del record._date
|
||||
print(str(record))
|
||||
|
||||
def do_plc(args, dbname):
|
||||
'Print the latest entry for each category/tag'
|
||||
print('Getting latest record for each tag from %s' % dbname)
|
||||
with open(dbname, 'r') as db:
|
||||
records = [eval(line) for line in db]
|
||||
tags = set(record.tag for record in records)
|
||||
records.reverse()
|
||||
for record in records:
|
||||
if record.tag in tags:
|
||||
del record._date
|
||||
print(str(record))
|
||||
tags.discard(record.tag)
|
||||
if not tags: break
|
||||
|
||||
def do_pa(args, dbname):
|
||||
'Print all entries sorted by a date'
|
||||
print('Getting all records by date from %s' % dbname)
|
||||
with open(dbname, 'r') as db:
|
||||
records = [eval(line) for line in db]
|
||||
for record in records:
|
||||
del record._date
|
||||
print(str(record))
|
||||
|
||||
def test():
|
||||
import time
|
||||
parser = parse_args()
|
||||
for cmdline in [
|
||||
"""-d Book -f title 'Windy places' -f type hardback --tag DISCOUNT add""",
|
||||
"""-d Book -f title 'RC spammers' -f type paperback -t DISCOUNT add""",
|
||||
"""-d Book -f title 'Splat it' -f type hardback -f special 'first edition' -t PREMIUM add""",
|
||||
"""pl""",
|
||||
"""plc""",
|
||||
]:
|
||||
args = parser.parse_args(shlex.split(cmdline))
|
||||
now = datetime.datetime.utcnow()
|
||||
args._date = now.isoformat()
|
||||
do_command[args.command](args, dbname)
|
||||
time.sleep(0.5)
|
||||
|
||||
|
||||
|
||||
do_command = dict(add=do_add, pl=do_pl, plc=do_plc, pa=do_pa)
|
||||
dbname = '_simple_db_db.py'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if 0:
|
||||
test()
|
||||
else:
|
||||
parser = parse_args()
|
||||
args = parser.parse_args()
|
||||
now = datetime.datetime.utcnow()
|
||||
args._date = now.isoformat()
|
||||
do_command[args.command](args, dbname)
|
||||
187
Task/Simple-database/Ruby/simple-database.rb
Normal file
187
Task/Simple-database/Ruby/simple-database.rb
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
require 'date'
|
||||
require 'json'
|
||||
require 'securerandom'
|
||||
|
||||
class SimpleDatabase
|
||||
def initialize(dbname, *fields)
|
||||
@dbname = dbname
|
||||
@filename = @dbname + ".dat"
|
||||
@fields = fields
|
||||
@maxl = @fields.collect {|f| f.length}.max
|
||||
@data = {
|
||||
'fields' => fields,
|
||||
'items' => {},
|
||||
'history' => [],
|
||||
'tags' => {},
|
||||
}
|
||||
end
|
||||
attr_reader :dbname, :fields
|
||||
|
||||
def self.open(dbname)
|
||||
db = new(dbname)
|
||||
db.read
|
||||
db
|
||||
end
|
||||
|
||||
def read()
|
||||
if not File.exists?(@filename)
|
||||
raise ArgumentError, "Database #@dbname has not been created"
|
||||
end
|
||||
@data = JSON.parse(File.read(@filename))
|
||||
@fields = @data['fields']
|
||||
@maxl = @fields.collect {|f| f.length}.max
|
||||
end
|
||||
|
||||
def write()
|
||||
File.open(@filename, 'w') {|f| f.write(JSON.generate(@data))}
|
||||
end
|
||||
|
||||
def add(*values)
|
||||
id = SecureRandom.uuid
|
||||
@data['items'][id] = Hash[ @fields.zip(values) ]
|
||||
@data['history'] << [Time.now.to_f, id]
|
||||
id
|
||||
end
|
||||
|
||||
def tag(id, *tags)
|
||||
tags.each do |tag|
|
||||
if @data['tags'][tag].nil?
|
||||
@data['tags'][tag] = [id]
|
||||
else
|
||||
@data['tags'][tag] << id
|
||||
end
|
||||
end
|
||||
id
|
||||
end
|
||||
|
||||
def latest
|
||||
@data['history'].sort_by {|val| val[0]}.last.last
|
||||
end
|
||||
|
||||
def get_item(id)
|
||||
@data['items'][id]
|
||||
end
|
||||
|
||||
def tags()
|
||||
@data['tags'].keys.sort
|
||||
end
|
||||
|
||||
def ids_for_tag(tag)
|
||||
@data['tags'][tag]
|
||||
end
|
||||
|
||||
def tags_for_id(id)
|
||||
@data['tags'].keys.inject([]) do |tags, tag|
|
||||
tags << tag if @data['tags'][tag].include?(id)
|
||||
tags
|
||||
end
|
||||
end
|
||||
|
||||
def display(id)
|
||||
item = get_item(id)
|
||||
fmt = "%#{@maxl}s - %s\n"
|
||||
puts fmt % ['id', id]
|
||||
@fields.each {|f| print fmt % [f, item[f]]}
|
||||
puts fmt % ['tags', tags_for_id(id).join(',')]
|
||||
added = @data['history'].find {|x| x[1] == id}.first
|
||||
puts fmt % ['date added', Time.at(added).ctime]
|
||||
puts ""
|
||||
end
|
||||
|
||||
def each()
|
||||
@data['history'].each {|time, id| yield id}
|
||||
end
|
||||
|
||||
def each_item_with_tag(tag)
|
||||
@data['tags'][tag].each {|id| yield id}
|
||||
end
|
||||
end
|
||||
def usage()
|
||||
puts <<END
|
||||
usage: #{$0} command args ...
|
||||
|
||||
commands:
|
||||
help
|
||||
create dbname field ...
|
||||
fields dbname
|
||||
add dbname value ...
|
||||
tag dbname id tag ...
|
||||
tags dbname
|
||||
list dbname [tag ...]
|
||||
latest dbname
|
||||
latest_by_tag dbname
|
||||
END
|
||||
end
|
||||
|
||||
def open_database(args)
|
||||
dbname = args.shift
|
||||
begin
|
||||
SimpleDatabase.open(dbname)
|
||||
rescue ArgumentError => e
|
||||
STDERR.puts e.message
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
def process_command_line(command, *args)
|
||||
case command
|
||||
when 'help'
|
||||
usage
|
||||
|
||||
when 'create'
|
||||
db = SimpleDatabase.new(*args)
|
||||
db.write
|
||||
puts "Database #{args[0]} created"
|
||||
|
||||
when 'fields'
|
||||
db = open_database(args)
|
||||
puts "Database #{db.dbname} fields:"
|
||||
puts db.fields.join(',')
|
||||
|
||||
when 'add'
|
||||
db = open_database(args)
|
||||
id = db.add(*args)
|
||||
db.write
|
||||
puts "Database #{db.dbname} added id #{id}"
|
||||
|
||||
when 'tag'
|
||||
db = open_database(args)
|
||||
id = args.shift
|
||||
db.tag(id, *args)
|
||||
db.write
|
||||
db.display(id)
|
||||
|
||||
when 'tags'
|
||||
db = open_database(args)
|
||||
puts "Database #{db.dbname} tags:"
|
||||
puts db.tags.join(',')
|
||||
|
||||
when 'list'
|
||||
db = open_database(args)
|
||||
if args.empty?
|
||||
db.each {|id| db.display(id)}
|
||||
else
|
||||
args.each do |tag|
|
||||
puts "Items tagged #{tag}"
|
||||
db.each_item_with_tag(tag) {|id| db.display(id)}
|
||||
end
|
||||
end
|
||||
|
||||
when 'latest'
|
||||
db = open_database(args)
|
||||
db.display(db.latest)
|
||||
|
||||
when 'latest_by_tag'
|
||||
db = open_database(args)
|
||||
db.tags.each do |tag|
|
||||
puts tag
|
||||
db.display(db.ids_for_tag(tag).last)
|
||||
end
|
||||
|
||||
else
|
||||
puts "Error: unknown command '#{command}'"
|
||||
usage
|
||||
end
|
||||
end
|
||||
|
||||
process_command_line *ARGV
|
||||
100
Task/Simple-database/Run-BASIC/simple-database.run
Normal file
100
Task/Simple-database/Run-BASIC/simple-database.run
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
sqliteconnect #sql, "f:\client.db" ' Connect to the DB
|
||||
|
||||
' -------------------------------
|
||||
' show user options
|
||||
' -------------------------------
|
||||
[sho]
|
||||
cls ' clear screen
|
||||
button #acd, "Add a new entry", [add]
|
||||
button #acd, "Print the latest entry", [last]
|
||||
button #acd, "Print the latest entry for each category", [lastCat]
|
||||
button #acd, "Print all entries sorted by a date", [date]
|
||||
button #ex, "Exit", [exit]
|
||||
wait
|
||||
|
||||
' ------------------------------------
|
||||
' add a new entry (user input screen)
|
||||
' ------------------------------------
|
||||
[add]
|
||||
cls ' clear the screen
|
||||
html "<TABLE BORDER=1 CELLPADDING=0 CELLSPACING=0 bgcolor=wheat>"
|
||||
html "<TR align=center BGCOLOR=tan><TD colspan=2>Client Maintenance</TD></TR><TR>"
|
||||
html "<TD bgcolor=tan align=right>Client Num</TD><TD>"
|
||||
textbox #clientNum,clientNum$,5
|
||||
|
||||
html "</TD></TR><TR><TD bgcolor=tan align=right>Name</TD><TD>"
|
||||
textbox #name,name$,30
|
||||
|
||||
html "</TD></TR><TR><TD bgcolor=tan align=right>Client Date</TD><TD>"
|
||||
textbox #clientDate,clientDate$,19
|
||||
|
||||
html "</TD></TR><TR><TD bgcolor=tan align=right>Category</TD><TD>"
|
||||
textbox #category,category$,10
|
||||
|
||||
html "</TD></TR><TR><TR bgcolor=tan><TD colspan=2 ALIGN=CENTER>"
|
||||
button #acd, "Add", [addIt]
|
||||
button #ex, "Exit", [sho]
|
||||
html "</TD></TR></TABLE>"
|
||||
wait
|
||||
|
||||
' ---------------------------------------------
|
||||
' Get data from the screen
|
||||
' ---------------------------------------------
|
||||
[addIt]
|
||||
clientNum = #clientNum contents$()
|
||||
name$ = trim$(#name contents$())
|
||||
clientDate$ = trim$(#clientDate contents$())
|
||||
category$ = trim$(#category contents$())
|
||||
dbVals$ = clientNum;",'";name$;"','";clientDate$;"','";category$;"'"
|
||||
sql$ = "INSERT into client VALUES ("; dbVals$ ; ")"
|
||||
#sql execute(sql$)
|
||||
goto [sho]
|
||||
|
||||
' ------------------------------------
|
||||
' Select last entry
|
||||
' ------------------------------------
|
||||
[last]
|
||||
sql$ = "SELECT *,client.rowid as rowid FROM client ORDER BY rowid desc LIMIT 1"
|
||||
what$ = "---- Last Entry ----"
|
||||
goto [shoQuery]
|
||||
|
||||
' ------------------------------------
|
||||
' Select by category (Last date only)
|
||||
' ------------------------------------
|
||||
[lastCat]
|
||||
sql$ = "SELECT * FROM client
|
||||
WHERE client.clientDate = (SELECT max(c.clientDate)
|
||||
FROM client as c WHERE c.category = client.category)
|
||||
ORDER BY category"
|
||||
what$ = "---- Last Category Sequence ----"
|
||||
goto [shoQuery]
|
||||
|
||||
' ------------------------------------
|
||||
' Select by date
|
||||
' ------------------------------------
|
||||
[date]
|
||||
sql$ = "SELECT * FROM client ORDER BY clientDate"
|
||||
what$ = "---- By Date ----"
|
||||
|
||||
[shoQuery]
|
||||
cls
|
||||
print what$
|
||||
html "<TABLE BORDER=1 CELLPADDING=0 CELLSPACING=0>"
|
||||
html "<TR align=center bgcolor=wheat><TD>Client<br>Num</TD><TD>Name</TD><TD>Client<br>Date</TD><TD>Category</TD></TR>" ' heading
|
||||
#sql execute(sql$)
|
||||
WHILE #sql hasanswer()
|
||||
#row = #sql #nextrow()
|
||||
clientNum = #row clientNum()
|
||||
name$ = #row name$()
|
||||
clientDate$ = #row clientDate$()
|
||||
category$ = #row category$()
|
||||
|
||||
html "<TR><TD align=right>";clientNum;"</TD><TD>";name$;"</TD><TD>";clientDate$;"</TD><TD>";category$;"</TD></TR>"
|
||||
WEND
|
||||
html "</TABLE>"
|
||||
button #c, "Continue", [sho]
|
||||
wait
|
||||
|
||||
' ------ the end -------
|
||||
[exit]
|
||||
end
|
||||
101
Task/Simple-database/Tcl/simple-database-1.tcl
Normal file
101
Task/Simple-database/Tcl/simple-database-1.tcl
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#!/usr/bin/env tclsh8.6
|
||||
package require Tcl 8.6
|
||||
namespace eval udb {
|
||||
variable db {}
|
||||
|
||||
proc Load {filename} {
|
||||
variable db
|
||||
if {[catch {set f [open $filename]}]} {
|
||||
set db {}
|
||||
return
|
||||
}
|
||||
set db [read $f]
|
||||
close $f
|
||||
}
|
||||
proc Store {filename} {
|
||||
variable db
|
||||
if {[catch {set f [open $filename w]}]} return
|
||||
dict for {nm inf} $db {
|
||||
puts $f [list $nm $inf]
|
||||
}
|
||||
close $f
|
||||
}
|
||||
|
||||
proc add {title category {date "now"} args} {
|
||||
variable db
|
||||
if {$date eq "now"} {
|
||||
set date [clock seconds]
|
||||
} else {
|
||||
set date [clock scan $date]
|
||||
}
|
||||
dict set db $title [list $category $date $args]
|
||||
return
|
||||
}
|
||||
proc Rec {nm cat date xtra} {
|
||||
dict create description $nm category $cat date [clock format $date] \
|
||||
{*}$xtra _names [dict keys $xtra]
|
||||
}
|
||||
proc latest {{category ""}} {
|
||||
variable db
|
||||
if {$category eq ""} {
|
||||
set d [lsort -stride 2 -index {1 1} -integer -decreasing $db]
|
||||
dict for {nm inf} $d break
|
||||
return [list [Rec $nm {*}$inf]]
|
||||
}
|
||||
set latestbycat {}
|
||||
dict for {nm inf} [lsort -stride 2 -index {1 1} -integer $db] {
|
||||
dict set latestbycat [lindex $inf 0] [list $nm {*}$inf]
|
||||
}
|
||||
return [list [Rec {*}[dict get $latestbycat $category]]]
|
||||
}
|
||||
proc latestpercategory {} {
|
||||
variable db
|
||||
set latestbycat {}
|
||||
dict for {nm inf} [lsort -stride 2 -index {1 1} -integer $db] {
|
||||
dict set latestbycat [lindex $inf 0] [list $nm {*}$inf]
|
||||
}
|
||||
set result {}
|
||||
dict for {- inf} $latestbycat {
|
||||
lappend result [Rec {*}$inf]
|
||||
}
|
||||
return $result
|
||||
}
|
||||
proc bydate {} {
|
||||
variable db
|
||||
set result {}
|
||||
dict for {nm inf} [lsort -stride 2 -index {1 1} -integer $db] {
|
||||
lappend result [Rec $nm {*}$inf]
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
namespace export add latest latestpercategory bydate
|
||||
namespace ensemble create
|
||||
}
|
||||
|
||||
if {$argc < 2} {
|
||||
puts stderr "wrong # args: should be \"$argv0 dbfile subcommand ?args...?\""
|
||||
exit 1
|
||||
}
|
||||
udb::Load [lindex $argv 0]
|
||||
set separator ""
|
||||
if {[catch {udb {*}[lrange $argv 1 end]} msg]} {
|
||||
puts stderr [regsub "\"udb " $msg "\"$argv0 dbfile "]
|
||||
exit 1
|
||||
}
|
||||
foreach row $msg {
|
||||
puts -nonewline $separator
|
||||
apply {row {
|
||||
dict with row {
|
||||
puts "Title: $description"
|
||||
puts "Category: $category"
|
||||
puts "Date: $date"
|
||||
foreach v $_names {
|
||||
puts "${v}: [dict get $row $v]"
|
||||
}
|
||||
}
|
||||
}} $row
|
||||
set separator [string repeat - 70]\n
|
||||
}
|
||||
|
||||
udb::Store [lindex $argv 0]
|
||||
91
Task/Simple-database/Tcl/simple-database-2.tcl
Normal file
91
Task/Simple-database/Tcl/simple-database-2.tcl
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
bash$ udb.tcl db
|
||||
wrong # args: should be "udb.tcl dbfile subcommand ?args...?"
|
||||
bash$ udb.tcl db ?
|
||||
unknown or ambiguous subcommand "?": must be add, bydate, latest, or latestpercategory
|
||||
bash$ udb.tcl db add
|
||||
wrong # args: should be "udb.tcl dbfile add title category ?date? ?arg ...?"
|
||||
bash$ udb.tcl db add "Title 1" foo
|
||||
bash$ udb.tcl db add "Title 2" foo
|
||||
bash$ udb.tcl db add "Title 3" bar
|
||||
bash$ udb.tcl db bydate
|
||||
Title: Title 1
|
||||
Category: foo
|
||||
Date: Tue Nov 15 18:11:58 GMT 2011
|
||||
----------------------------------------------------------------------
|
||||
Title: Title 2
|
||||
Category: foo
|
||||
Date: Tue Nov 15 18:12:01 GMT 2011
|
||||
----------------------------------------------------------------------
|
||||
Title: Title 3
|
||||
Category: bar
|
||||
Date: Tue Nov 15 18:12:07 GMT 2011
|
||||
bash$ udb.tcl db latest
|
||||
Title: Title 3
|
||||
Category: bar
|
||||
Date: Tue Nov 15 18:12:07 GMT 2011
|
||||
bash$ udb.tcl db latest foo
|
||||
Title: Title 2
|
||||
Category: foo
|
||||
Date: Tue Nov 15 18:12:01 GMT 2011
|
||||
bash$ udb.tcl db latest bar
|
||||
Title: Title 3
|
||||
Category: bar
|
||||
Date: Tue Nov 15 18:12:07 GMT 2011
|
||||
bash$ udb.tcl db latestpercategory
|
||||
Title: Title 2
|
||||
Category: foo
|
||||
Date: Tue Nov 15 18:12:01 GMT 2011
|
||||
----------------------------------------------------------------------
|
||||
Title: Title 3
|
||||
Category: bar
|
||||
Date: Tue Nov 15 18:12:07 GMT 2011
|
||||
bash$ udb.tcl db add "Title 4" bar "12:00 Monday last week"
|
||||
bash$ udb.tcl db bydate
|
||||
Title: Title 4
|
||||
Category: bar
|
||||
Date: Mon Nov 14 12:00:00 GMT 2011
|
||||
----------------------------------------------------------------------
|
||||
Title: Title 1
|
||||
Category: foo
|
||||
Date: Tue Nov 15 18:11:58 GMT 2011
|
||||
----------------------------------------------------------------------
|
||||
Title: Title 2
|
||||
Category: foo
|
||||
Date: Tue Nov 15 18:12:01 GMT 2011
|
||||
----------------------------------------------------------------------
|
||||
Title: Title 3
|
||||
Category: bar
|
||||
Date: Tue Nov 15 18:12:07 GMT 2011
|
||||
bash$ cat db
|
||||
{Title 1} {foo 1321380718 {}}
|
||||
{Title 2} {foo 1321380721 {}}
|
||||
{Title 3} {bar 1321380727 {}}
|
||||
{Title 4} {bar 1321272000 {}}
|
||||
bash$ udb.tcl db add "Title 5" foo "12:00 Monday last week" Comment 'Wholly excellent!'
|
||||
bash$ cat db
|
||||
{Title 1} {foo 1321380718 {}}
|
||||
{Title 2} {foo 1321380721 {}}
|
||||
{Title 3} {bar 1321380727 {}}
|
||||
{Title 4} {bar 1321272000 {}}
|
||||
{Title 5} {foo 1321272000 {Comment {Wholly excellent!}}}
|
||||
bash$ udb.tcl db bydate
|
||||
Title: Title 4
|
||||
Category: bar
|
||||
Date: Mon Nov 14 12:00:00 GMT 2011
|
||||
----------------------------------------------------------------------
|
||||
Title: Title 5
|
||||
Category: foo
|
||||
Date: Mon Nov 14 12:00:00 GMT 2011
|
||||
Comment: Wholly excellent!
|
||||
----------------------------------------------------------------------
|
||||
Title: Title 1
|
||||
Category: foo
|
||||
Date: Tue Nov 15 18:11:58 GMT 2011
|
||||
----------------------------------------------------------------------
|
||||
Title: Title 2
|
||||
Category: foo
|
||||
Date: Tue Nov 15 18:12:01 GMT 2011
|
||||
----------------------------------------------------------------------
|
||||
Title: Title 3
|
||||
Category: bar
|
||||
Date: Tue Nov 15 18:12:07 GMT 2011
|
||||
96
Task/Simple-database/UNIX-Shell/simple-database-1.sh
Normal file
96
Task/Simple-database/UNIX-Shell/simple-database-1.sh
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#!/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
|
||||
37
Task/Simple-database/UNIX-Shell/simple-database-2.sh
Normal file
37
Task/Simple-database/UNIX-Shell/simple-database-2.sh
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
$ 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'
|
||||
$
|
||||
Loading…
Add table
Add a link
Reference in a new issue