delish 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,92 @@
1
+ = Quick Start
2
+
3
+ The first thing you need to do is to import your del.icio.us bookmarks. Start by setting environment variables for your del.icio.us username and password:
4
+
5
+ <tt>export delish_user=<em>user</em></tt>
6
+
7
+ <tt>export delish_pass=<em>pass</em></tt>
8
+
9
+ And then import your bookmarks.
10
+
11
+ <tt>import_delicious</tt>
12
+
13
+ You can access these bookmarks with the <tt>delish</tt> command. See more below.
14
+
15
+ = delish
16
+
17
+ Conveniently access del.icio.us bookmarks from a local database via commandline
18
+
19
+ = Usage
20
+
21
+ <tt>delish <em>basic_search</em>(<em>advanced_search</em>) <em>display_only</em></tt>
22
+
23
+ == <tt><em>basic_search</em></tt>
24
+
25
+ A string that should be in the url, the description, or the extended description. It is case insensitive and unanchored.
26
+
27
+ == <tt><em>advanced_search</em></tt>
28
+
29
+ Advanced search allows searching by tags and/or creation date or access date. Advanced searches must be separated by colons.
30
+
31
+ === Tags
32
+
33
+ Tag searches start with a character and follow with any number of tags separated with commas.
34
+
35
+ exact tag::
36
+ <tt>t</tt><em>tags</em>
37
+
38
+ start of tag::
39
+ <tt>s</tt><em>tags</em>
40
+
41
+ inside of tag::
42
+ <tt>i</tt><em>tags</em>
43
+
44
+ === Dates
45
+
46
+ Dates begin with a character specifying which date to check against. The character can be either <tt>a</tt> for accessed or <tt>c</tt> for creation date. Following this is an optional date specification type. If the type is left off it will be of day type. The date specification types include <tt>h</tt> for hour and <tt>w</tt> for week. Support for <tt>M</tt> (month), <tt>m</tt> (minute) and <tt>s</tt> (second) is planned for another release. After the date specification is the optional date search specification, followed by the amount of time to search. The date search specification can be a <tt>+</tt>, which means more than so much time, or a <tt>-</tt>, which means less than so much time.
47
+
48
+
49
+ <tt>display_only</tt>::
50
+ This can be any string at all. It will basically cause the program to output the links that it would have opened if you had not provided the string. The idea is that you can run delish with some random character at the end, make sure the link is right, and then press up and backspace to get it to open the link(s).
51
+
52
+ = Examples
53
+
54
+ +HaRoLd+:: bookmarks containing the word <em>harold</em> in the url, description, or extended description.
55
+
56
+ <tt>(truby,rails)</tt>::
57
+ bookmarks that contain both <em>ruby</em> and <em>rails</em> tags.
58
+
59
+ <tt>(sru,ra)</tt>::
60
+ bookmarks that contain tags that start with <em>ru</em> and <em>ra</em>.
61
+
62
+ <tt>(iun,si)</tt>::
63
+ bookmarks that contain tags that contain <em>un</em> and <em>si</em>.
64
+
65
+ <tt>(aw-6)</tt>::
66
+ bookmarks that have been accessed within the past 6 weeks.
67
+
68
+ <tt>(ch+2)</tt>::
69
+ bookmarks that were created more than two hours ago.
70
+
71
+ <tt>(cd2)</tt>::
72
+ bookmarks that were created the day before yesterday.
73
+
74
+ <tt>ruby(truby,rails:sz:iyes)</tt>::
75
+ This will return any bookmarks with a description, url, or extended description containing <em>ruby</em>, a <em>ruby</em> tag, a <em>rails</em> tag, a tag starting with <em>z</em> and a tag containing <em>yes</em>.
76
+
77
+ == TODO
78
+
79
+ * "search" tags
80
+ * zsh configuration section
81
+ * Month, minute, and second support
82
+ * Tags table in underlying database to improve searching speed
83
+ * Profile the code to increase speed
84
+ * Date Unit Tests
85
+ * "Build Script"
86
+ * Incremental updates instead of always importing all bookmarks
87
+
88
+ == Links
89
+
90
+ Project Page:: [http://rubyforge.org/projects/delish/]
91
+ Download:: [http://rubyforge.org/frs/?group_id=3935]
92
+
data/bin/delish ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'rubygems'
4
+ require 'sqlite3'
5
+
6
+ db = SQLite3::Database.new( "#{ENV['HOME']}/.delish" )
7
+ string = ARGV[0]
8
+
9
+ #{{{ Default command is firefox
10
+ unless ENV['delish_command'].to_s == ""
11
+ @@DELISH_COMMAND = ENV['delish_command']
12
+ else
13
+ @@DELISH_COMMAND = 'firefox'
14
+ end
15
+ #}}}
16
+
17
+ #{{{ Output only and debug
18
+ unless ARGV[1].nil?
19
+ @@OUTPUT_ONLY = true
20
+ unless ARGV[2].nil?
21
+ @@DEBUG = true
22
+ else
23
+ @@DEBUG = false
24
+ end
25
+ else
26
+ @@OUTPUT_ONLY = false
27
+ end
28
+ #}}}
29
+
30
+ require 'delish_bookmark_search.rb'
31
+
32
+ delish_bookmark_search = Delish_Bookmark_Search.new(string, db)
33
+
34
+ if @@OUTPUT_ONLY
35
+ delish_bookmark_search.each { |bookmark| puts "#{bookmark[1]}: [#{bookmark[0]}]" }
36
+ else
37
+ delish_bookmark_search.each { |bookmark| `#{@@DELISH_COMMAND} #{bookmark[0]}` }
38
+ end
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'rubygems'
4
+ require 'rexml/document'
5
+ require 'net/https'
6
+ require 'sqlite3'
7
+
8
+
9
+
10
+ user = ENV['delish_user']
11
+ pass = ENV['delish_pass']
12
+
13
+ db = SQLite3::Database.new( "#{ENV['HOME']}/.delish" )
14
+
15
+ # Create the table!!!
16
+ db.execute <<SQL
17
+ CREATE TABLE posts (
18
+ url VARCHAR(16384),
19
+ description VARCHAR(255),
20
+ extended VARCHAR(255),
21
+ created DATETIME,
22
+ hash CHAR(32) PRIMARY KEY,
23
+ accessed DATETIME
24
+ );
25
+ SQL
26
+
27
+ db.execute <<SQL
28
+ CREATE TABLE post_tags (
29
+ hash CHAR(32),
30
+ tag VARCHAR(255)
31
+ );
32
+ SQL
33
+
34
+ # User-Agent: required for del.icio.us api
35
+ agent = 'delish commandline client 0.4'
36
+
37
+ http = Net::HTTP.new('api.del.icio.us', 443)
38
+ http.use_ssl = true
39
+ http.start do |http|
40
+ request = Net::HTTP::Get.new('/v1/posts/all', {'User-Agent' => agent})
41
+ request.basic_auth user, pass
42
+ response = http.request(request)
43
+
44
+ data = REXML::Document.new(response.body)
45
+ data.elements.each("/posts/post") do |post|
46
+ db.execute(
47
+ "INSERT INTO posts
48
+ (url, description, extended, created, accessed, hash) VALUES
49
+ (?, ?, ?, JULIANDAY(?), JULIANDAY(?), ?);",
50
+ post.attributes['href'],
51
+ post.attributes['description'],
52
+ post.attributes['extended'],
53
+ post.attributes['time'].chop,
54
+ post.attributes['time'].chop,
55
+ post.attributes['hash']
56
+ )
57
+
58
+ post.attributes['tag'].split(' ').each do |tag|
59
+ db.execute(
60
+ "INSERT INTO post_tags
61
+ (hash, tag) VALUES
62
+ (?, ?);",
63
+ post.attributes['hash'],
64
+ tag
65
+ )
66
+ end
67
+ end
68
+ end
69
+
@@ -0,0 +1,286 @@
1
+
2
+
3
+ class SuperSQL
4
+ def initialize(order)
5
+ @statement = ""
6
+ @variables = []
7
+ if order.is_a? Integer
8
+ @order = order
9
+ else
10
+ throw "Not a good order."
11
+ end
12
+ end
13
+ attr_reader :statement
14
+ attr_reader :variables
15
+ attr_reader :order
16
+ attr_writer :statement
17
+ attr_writer :variables
18
+ end
19
+
20
+ class Delish_Bookmark_Search
21
+ #---
22
+ #{{{ initialize(search, database)
23
+ #+++
24
+ def initialize(search, database)
25
+ @db = database
26
+
27
+ @TAG_SPLIT = ','
28
+ @SPLIT = ':'
29
+
30
+ @search_string = search
31
+ @sss = []
32
+
33
+ @date_greater_or_equal = SuperSQL.new(0)
34
+ @date_greater_or_equal.statement = 'TYPE >= JULIANDAY(CURRENT_THING) - ?'
35
+
36
+ @date_less_than = SuperSQL.new(0)
37
+ @date_less_than.statement = 'TYPE < JULIANDAY(CURRENT_THING) - ?'
38
+
39
+ @use_tags = false
40
+ @tag_count = 0
41
+
42
+ self.search
43
+
44
+ end
45
+ #---
46
+ #}}}
47
+ #+++
48
+
49
+ #---
50
+ #{{{ search
51
+ #+++
52
+ def search
53
+ if @search_string =~ /^(.*?)(\((.*)\))?$/
54
+ self.main_search($1)
55
+ unless $2.nil?
56
+ @submatches = $3.split(@SPLIT)
57
+ self.sub_search_parse
58
+ end
59
+ self.execute(false)
60
+ end
61
+ end
62
+ #---
63
+ #}}}
64
+ #+++
65
+
66
+ #---
67
+ #{{{ main_search
68
+ #+++
69
+ def main_search(main_search_string)
70
+ @main_search = main_search_string
71
+
72
+ ssql = SuperSQL.new(40)
73
+ ssql.statement = "(url LIKE ? OR description LIKE ? OR extended LIKE ?)"
74
+ 3.times { ssql.variables << "%#{main_search_string}%" }
75
+
76
+ @sss << ssql
77
+ end
78
+ #---
79
+ #}}}
80
+ #+++
81
+
82
+ #---
83
+ #{{{ sub_search_parse
84
+ #+++
85
+ def sub_search_parse
86
+ @submatches.each do |i|
87
+ if i =~ /^t(.*)$/
88
+ self.tag_equality_search($1)
89
+ elsif i =~ /^s(.*)$/
90
+ self.tag_begin_search($1)
91
+ elsif i =~ /^i(.*)$/
92
+ self.tag_inside_search($1)
93
+ elsif i =~ /^([ca])([Mwhms]|)([+-]|)(\d+)$/
94
+ self.date_search($1, $2, $3, $4)
95
+ end
96
+ end
97
+ end
98
+ #---
99
+ #}}}
100
+ #+++
101
+
102
+ #---
103
+ #{{{ tag_equality_search(tag_string
104
+ #+++
105
+ def tag_equality_search(tag_string)
106
+ @use_tags = true
107
+
108
+ ssql = SuperSQL.new(5)
109
+ ssql.variables = tag_string.split(@TAG_SPLIT)
110
+ ssql.statement = ssql.variables.map { @tag_count += 1; 'PLACEHOLDER = ?' }.join(' AND ')
111
+ @sss << ssql
112
+ end
113
+ #---
114
+ #}}}
115
+ #+++
116
+
117
+ #---
118
+ #{{{ tag_begin_search(i)
119
+ #+++
120
+ def tag_begin_search(tag_string)
121
+ @use_tags = true
122
+
123
+ ssql = SuperSQL.new(10)
124
+ ssql.variables = tag_string.split(@TAG_SPLIT).map { |i| "#{i}%" }
125
+ ssql.statement = ssql.variables.map { @tag_count += 1; 'PLACEHOLDER LIKE ?' }.join(' AND ')
126
+ @sss << ssql
127
+ end
128
+ #---
129
+ #}}}
130
+ #+++
131
+
132
+ #---
133
+ #{{{ tag_inside_search(i)
134
+ #+++
135
+ def tag_inside_search(tag_string)
136
+ @use_tags = true
137
+
138
+ ssql = SuperSQL.new(20)
139
+ ssql.variables = tag_string.split(@TAG_SPLIT).map { |i| "%#{i}%" }
140
+ ssql.statement = ssql.variables.map { @tag_count += 1; 'PLACEHOLDER LIKE ?' }.join(' AND ')
141
+ @sss << ssql
142
+ end
143
+ #---
144
+ #}}}
145
+ #+++
146
+
147
+ # This needs cleanup
148
+ #---
149
+ #{{{ date_search
150
+ #+++
151
+ def date_search(search_type, date_type, search_inner_type, amount)
152
+
153
+ type = ""
154
+ how_much = ""
155
+ date_or_time = ""
156
+ if search_type == 'c'
157
+ type = 'created'
158
+ else
159
+ type = 'accessed'
160
+ end
161
+ if date_type == 'h'
162
+ how_much = Date.time_to_day_fraction(amount.to_i, 0, 0)
163
+ elsif date_type == ''
164
+ how_much = amount
165
+ elsif date_type == 'w'
166
+ how_much = amount * 7
167
+ end
168
+ if search_inner_type == '-'
169
+ date_or_time = "CURRENT_TIMESTAMP"
170
+ @date_greater_or_equal.variables << how_much
171
+ elsif search_inner_type == '+'
172
+ date_or_time = "CURRENT_TIMESTAMP"
173
+ @date_less_than.variables << how_much
174
+ else
175
+ date_or_time = "CURRENT_DATE"
176
+ @date_greater_or_equal.variables << how_much
177
+ @date_less_than.variables << (how_much.to_i + 1).to_s
178
+ end
179
+
180
+ @date_greater_or_equal.statement["TYPE"] = type
181
+ @date_less_than.statement["TYPE"] = type
182
+ @date_greater_or_equal.statement["CURRENT_THING"] = date_or_time
183
+ @date_less_than.statement["CURRENT_THING"] = date_or_time
184
+ end
185
+ #---
186
+ #}}}
187
+ #+++
188
+
189
+ #---
190
+ #{{{ execute
191
+ #+++
192
+ def execute(access)
193
+ @sss << @date_greater_or_equal unless @date_greater_or_equal.variables.empty?
194
+ @sss << @date_less_than unless @date_less_than.variables.empty?
195
+
196
+ @sss.sort! { |a, b| a.order <=> b.order }
197
+
198
+ statement, variables = self.build_statement(access)
199
+
200
+ @results = @db.execute(statement, variables)
201
+
202
+ if access
203
+ hashes = []
204
+ @results.each { |i| hashes << i[1] }
205
+ self.mark_accessed(hashes)
206
+ end
207
+
208
+ end
209
+ #---
210
+ #}}}
211
+ #+++
212
+
213
+ def [](index)
214
+ @results[index]
215
+ end
216
+
217
+ #---
218
+ #{{{ each_with_index
219
+ #+++
220
+ def each_with_index
221
+ @results.each_with_index { |result| yield(result) }
222
+ end
223
+ #---
224
+ #}}}
225
+ #+++
226
+
227
+ #---
228
+ #{{{ each
229
+ #+++
230
+ def each
231
+ @results.each { |result| yield(result) }
232
+ end
233
+ #---
234
+ #}}}
235
+ #+++
236
+
237
+ #---
238
+ #{{{ build_statement(access)
239
+ #+++
240
+ def build_statement(access)
241
+ variables = []
242
+
243
+ statement = "SELECT url"
244
+
245
+ unless access
246
+ statement += ", description"
247
+ else
248
+ statement += ", posts.hash"
249
+ end
250
+
251
+ statement += " FROM posts"
252
+ @tag_count.times { |i| statement += ", post_tags AS pt#{i}" }
253
+ statement += " WHERE "
254
+ @tag_count.times { |i| statement += "pt#{i}.hash == posts.hash AND " }
255
+
256
+ tag_count = 0
257
+ @sss.each_with_index do |ssql,i|
258
+ while ssql.statement.include? "PLACEHOLDER"
259
+ ssql.statement.sub!("PLACEHOLDER", "pt#{tag_count}.tag")
260
+ tag_count += 1
261
+ end
262
+ statement += ssql.statement
263
+ statement += ' AND ' unless i + 1 == @sss.length
264
+
265
+ variables += ssql.variables
266
+ end
267
+
268
+ statement += ' ORDER BY accessed'
269
+ return statement, variables
270
+ end
271
+ #---
272
+ #}}}
273
+ #+++
274
+
275
+ #---
276
+ #{{{ mark_accessed(hashes)
277
+ #+++
278
+ def mark_accessed(hashes)
279
+ statement = "UPDATE posts SET accessed = JULIANDAY(CURRENT_TIMESTAMP) WHERE hash IN (" + hashes.map {"?"}.join(",") + ")"
280
+ @db.execute(statement, hashes)
281
+ end
282
+ #---
283
+ #}}}
284
+ #+++
285
+
286
+ end
data/test/test_db ADDED
Binary file
data/test/tests ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/ruby -w
2
+ require '../delish_bookmark_search.rb'
3
+ require 'test/unit'
4
+ require 'sqlite3'
5
+
6
+ @@db = SQLite3::Database.new( "test_db" )
7
+
8
+ class Delish < Test::Unit::TestCase
9
+
10
+ def test_basic_search
11
+ search = Delish_Bookmark_Search.new('readable2', @@db )
12
+ search.each {|i| assert_equal('http://frew.livejournal.com/friends/readable2', i[0]); assert_equal('readable2', i[1]) }
13
+ assert_equal('http://frew.livejournal.com/friends/readable2', search[0][0])
14
+ assert_equal('readable2', search[0][1])
15
+ end
16
+
17
+ def test_tag_equality_search
18
+ search = Delish_Bookmark_Search.new('(tzsh,wiki)', @@db )
19
+ assert_equal('http://zshwiki.org/home/', search[0][0])
20
+ assert_equal('ZshWiki', search[0][1])
21
+ end
22
+
23
+ def test_tag_starts_with_search
24
+ search = Delish_Bookmark_Search.new('(svis,fun)', @@db )
25
+ assert_equal('http://www.drurywriting.com/david/05-SnowmanArt.htm', search[0][0])
26
+ assert_equal('calvin & hobbes snowman art', search[0][1])
27
+ end
28
+
29
+ def test_tag_inside_search
30
+ search = Delish_Bookmark_Search.new('(ivi,unn)', @@db )
31
+ assert_equal('http://www.bemroses.net/images/curves.jpg', search[2][0])
32
+ assert_equal('Editor Learning Curves', search[2][1])
33
+ end
34
+
35
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
4
+ name: delish
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.5.2
7
+ date: 2007-07-03 00:00:00 -06:00
8
+ summary: Allows use of del.icio.us bookmarksfrom the commandline with minimum keystrokes
9
+ require_paths:
10
+ - lib
11
+ email: frioux@gmail.com
12
+ homepage: http://afoolishmanifesto.com
13
+ rubyforge_project:
14
+ description:
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Arthur Axel "fREW" Schmidt
31
+ files:
32
+ - bin/delish
33
+ - bin/import_delicious
34
+ - lib/delish_bookmark_search.rb
35
+ - test/tests
36
+ - test/test_db
37
+ - README
38
+ test_files:
39
+ - test/tests
40
+ rdoc_options:
41
+ - --title
42
+ - Delish -- Ice cream on the console
43
+ - --main
44
+ - README
45
+ - --line-numbers
46
+ extra_rdoc_files:
47
+ - README
48
+ executables:
49
+ - delish
50
+ - import_delicious
51
+ extensions: []
52
+
53
+ requirements: []
54
+
55
+ dependencies:
56
+ - !ruby/object:Gem::Dependency
57
+ name: sqlite3-ruby
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Version::Requirement
60
+ requirements:
61
+ - - ">"
62
+ - !ruby/object:Gem::Version
63
+ version: 0.0.0
64
+ version: