1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
;;; markam -- Store/retrieve/manage bookmarks
;; Copyright (C) 2012,2013 Tom Willemsen <tom at ryuslash dot org>
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; The main program. For documentation you should consult the
;; README.org and/or any Texinfo documentation provided.
;;; Code:
(declare (uses paths
common))
(require-extension sqlite3)
(require-library posix srfi-4)
(define version "0.1.0")
;; If STR contains a \0 byte at the end, remove it, otherwise return
;; STR unchanged.
(: string-no-null (string -> string))
(define (string-no-null str)
(if (and (> (string-length str) 0)
(char=? (string-ref str (- (string-length str) 1)) #\nul))
(substring str 0 (- (string-length str) 1))
str))
;; Print URL, SECONDS, NAME and DESCRIPTION to the standard output
;; stream.
(: print-row (string fixnum string string -> void))
(define (print-row url seconds name description)
(if script?
(format #t "~a~a~a"
(string-no-null name) (string-no-null description)
(string-no-null url))
(format #t "~a~% ~a~% ~a~% ~a~%~%" (string-no-null name)
(string-no-null description) (string-no-null url)
(seconds->string seconds))))
;; Add NAME to the `tag' table in DB.
(define (add-tag db name)
(execute db "INSERT INTO tag VALUES (?)" name)
(last-insert-rowid db))
;; Add a bookmark to the `bookmark' table in DB. URL is the url of the
;; bookmark, NAME the title, DESCRIPTION a (possibly longer)
;; description of the bookmark and TAGS is a list of tags to assign to
;; it. Each tag will be created if necessary.
(define (add-bookmark db url name description tags)
(execute db "INSERT INTO bookmark VALUES (?, STRFTIME('%s'), ?, ?)"
url name description)
(let ((bookmark-id (last-insert-rowid db)))
(for-each (lambda (tag)
(let ((tag-id '()))
(condition-case
(set! tag-id
(first-result
db "SELECT rowid FROM tag WHERE name = ?" tag))
(exn (exn sqlite3)
(if (eq? (get-condition-property
exn 'sqlite3 'status) 'done)
(set! tag-id (add-tag db tag))
(signal exn))))
(execute db "INSERT INTO bookmark_tag VALUES (?, ?)"
bookmark-id tag-id)))
tags)))
(define (search-bookmarks db str)
(for-each-row print-row db #<<END
SELECT *
FROM bookmark
WHERE name LIKE ?
OR ? IN (SELECT name
FROM tag
JOIN bookmark_tag ON (tag_id = tag.rowid)
WHERE bookmark_id = bookmark.rowid)
END
(string-append "%" str "%") str))
;; Is STR a URL? Very naïve, assumes all URLs begin with either
;; `http://' or `https://', should be improved.
(define (url-string? str)
(and (> (string-length str) 7)
(or (string= (substring str 0 7) "http://")
(string= (substring str 0 8) "https://"))))
;; Display markam's help message.
(define (display-help)
(format #t (string-append
"Usage: markam [options]...~%"
" markam <url> <name> <description> [<tag>...]~%"
"~%"
"Possible options:~%"
"~%"
"--help, -h Display this help and exit~%"
"--version, -v Output version information and exit~%")))
;; Parse "other" command-line arguments
(define (handle-regular-args args)
(do ((arg (car args) (and (not (null? args))
(car args))))
((or (null? arg) (not arg)))
(cond
((or (string= arg "-v") (string= arg "--version"))
(display-version)
(exit 0))
((or (string= arg "-h") (string= arg "--help"))
(display-help)
(exit 0))
(else
(format #t "Unrecognized option: ~a~%" (car args))))
(set! args (cdr args))))
(define (check-script args)
(if (member "--script" args)
(begin
(set! script? #t)
(delete "--script" args))
args))
(define script? #f)
;; Open a database connection, list bookmarks, create a bookmark or
;; pass arguments on to `handle-regular-args'.
(define (main args)
(set! args (check-script args))
(let ((db (open-database (data-file "markam.db"))))
(cond
((null? args)
(for-each-row print-row db "select * from bookmark"))
((url-string? (car args))
(with-transaction
db (lambda ()
(add-bookmark
db (car args) (cadr args) (caddr args) (cdddr args)) #t)))
((string= (car args) "search")
(search-bookmarks db (cadr args)))
(else
(handle-regular-args args)))
(finalize! db #t)))
(main (command-line-arguments))
|