epitron-delicious-cli 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Chris Gahan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,66 @@
1
+ = Delicious.com Command-line Interface
2
+
3
+ Locally mirror your delicious links, and search them. Usage:
4
+
5
+ dels <search query>
6
+
7
+ == Installation
8
+
9
+ gem sources --add http://gemcutter.org
10
+ gem install delicious-cli
11
+
12
+ == Example:
13
+
14
+ === Set your delicious login information:
15
+
16
+ (If you have never configured delicious-cli before, this will automatically
17
+ happen the first time you run it.)
18
+
19
+ epi@fizz$ dels -c
20
+
21
+ Delicious login info
22
+ ---------------------------
23
+
24
+ Username: mydeliciouslogin
25
+ Password: mypassword
26
+
27
+ User: "mydeliciouslogin" | Pass: "mypassword"
28
+ Is this correct? [y/N] y
29
+
30
+ * Checking that login/password works...
31
+ |_ Login successful! Saving config...
32
+
33
+
34
+ === Download all your delicious links:
35
+
36
+ epi@fizz$ dels -r
37
+
38
+ * Redownloading database...
39
+ * Retrieving all links... (3465 links found)
40
+ * Inserting links into database...done!
41
+
42
+ === Download new links (since last sync):
43
+
44
+ epi@fizz$ dels -s
45
+
46
+ * Synchronizing database...
47
+ * Retrieving new links... (5 links found)
48
+ * Inserting links into database...done!
49
+
50
+ == Commandline options:
51
+
52
+ Usage: dels [options] <search query>
53
+
54
+ Specific options:
55
+ -d, --debug Debug info
56
+ -s, --sync Synchronize links
57
+ -r, --redownload Erase database and redownload all links
58
+ -c, --config Configure app (set delicious username/password)
59
+ -t, --test-auth Test that authentication info works
60
+
61
+ Common options:
62
+ -h, --help Show this message
63
+
64
+ == Copyright
65
+
66
+ Copyright (c) 2009 Chris Gahan. See LICENSE for details.
data/bin/dels ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path(File.dirname(__FILE__))
4
+
5
+ require 'delicious-cli'
6
+ main
@@ -0,0 +1,36 @@
1
+ require 'httparty'
2
+ require 'delicious-cli/settings'
3
+
4
+ # Documentation:
5
+ # http://delicious.com/help/api
6
+
7
+ class Delicious
8
+ include HTTParty
9
+ base_uri 'api.del.icio.us:443/v1'
10
+ format :xml
11
+
12
+ def self.posts_update
13
+ result = get('/posts/update')
14
+ if result["update"]
15
+ result["update"]["time"]
16
+ else
17
+ nil
18
+ end
19
+ end
20
+
21
+ def self.posts_all(options={})
22
+ result = get('/posts/all', :query=>options)
23
+ [result["posts"]["post"]].flatten # ensure it's an array
24
+ end
25
+
26
+ def self.posts_since(time)
27
+ $log.debug "Retrieving links newer than #{time}"
28
+ results = posts_all(:fromdt=>time)
29
+ results.select { |r| r["time"] != time }
30
+ end
31
+
32
+ def self.valid_auth?
33
+ not posts_update.nil?
34
+ end
35
+
36
+ end
@@ -0,0 +1,117 @@
1
+ require 'sequel'
2
+ require 'sequel/extensions/blank'
3
+
4
+ require 'delicious-cli/settings'
5
+ require 'delicious-cli/api'
6
+
7
+ #################################################################
8
+
9
+ $log.debug "Loading database..."
10
+ DB = Sequel.sqlite(configfile('delicious.db'))
11
+ $log.debug "done."
12
+
13
+ #################################################################
14
+
15
+ =begin
16
+ Sample record:
17
+
18
+ {
19
+ "href"=>"http://guru.com/",
20
+ "time"=>"2009-04-24T06:06:37Z",
21
+ "hash"=>"80e9a5f0bccb7f9e9dfd2e309eabd7de",
22
+ "tag"=>"jobs business freelance employment outsourcing exchanges networking",
23
+ "meta"=>"485e955fe2937fbef85390623ad0984c",
24
+ "description"=>"Guru.com",
25
+ "extended"=>"Worldwide freelancer marketplace."
26
+ }
27
+ =end
28
+
29
+ #################################################################
30
+
31
+ class Database
32
+ @@posts = DB[:posts]
33
+
34
+ def self.init!
35
+ create_posts! unless DB.table_exists?(:posts)
36
+ end
37
+
38
+ def self.clear!
39
+ DB.drop_table(:posts)
40
+ create_posts!
41
+ end
42
+
43
+ def self.create_posts!
44
+ DB.create_table :posts do
45
+ primary_key :id
46
+
47
+ string :href
48
+ string :time
49
+ string :tag
50
+ string :description
51
+ text :extended
52
+ string :hash
53
+ string :meta
54
+
55
+ index :hash, {:unique=>true}
56
+ index [:description, :extended, :tag]
57
+ end
58
+ end
59
+
60
+ def self.sync(all=false)
61
+ all = true if @@posts.empty?
62
+
63
+ if all
64
+ print "* Retrieving all links..."
65
+ STDOUT.flush
66
+ posts = Delicious.posts_all
67
+ else
68
+ print "* Retrieving new links..."
69
+ STDOUT.flush
70
+ posts = Delicious.posts_since(most_recent_time)
71
+ end
72
+
73
+ $log.debug "sync: got #{posts.size} posts"
74
+
75
+ puts " (#{posts.size} links found)"
76
+
77
+ return if posts.empty?
78
+
79
+ print "* Inserting links into database..."
80
+
81
+ counter = 0
82
+ for post in posts
83
+ counter += 1
84
+ begin
85
+ add post
86
+ rescue Sequel::DatabaseError => e
87
+ $log.debug "error: #{e} adding #{post.inspect}"
88
+ end
89
+
90
+ if counter % 37 == 0
91
+ print "."
92
+ STDOUT.flush
93
+ end
94
+ end
95
+
96
+ puts "done!"
97
+ end
98
+
99
+ def self.most_recent_time
100
+ @@posts.order(:time.desc).limit(1).first[:time]
101
+ end
102
+
103
+ def self.add(params)
104
+ @@posts << params
105
+ end
106
+
107
+ def self.find(words)
108
+ sequel_query = @@posts
109
+ for word in words
110
+ pattern = "%#{word}%"
111
+ sequel_query = sequel_query.filter(:extended.like(pattern) | :description.like(pattern) | :tag.like(pattern))
112
+ end
113
+ sequel_query.order(:time)
114
+ end
115
+
116
+
117
+ end
@@ -0,0 +1,60 @@
1
+ require 'date'
2
+ require 'colorize'
3
+
4
+ #################################################################
5
+ ## Load the colorize gem, and define the "hilite" function
6
+ begin
7
+ require 'rubygems'
8
+ require 'colorize'
9
+ # Colourized hilite...
10
+ class String
11
+ def hilite(words, color=:white)
12
+ escaped_words = words.map { |word| Regexp.escape(word) }
13
+ matcher = /(#{escaped_words.join('|')})/io
14
+
15
+ chunks = self.to_s.split(matcher)
16
+ chunks.map do |chunk|
17
+ if chunk =~ matcher
18
+ chunk.black.on_yellow
19
+ else
20
+ chunk.send(color)
21
+ end
22
+ end.join('')
23
+ end
24
+ end
25
+ rescue LoadError
26
+ STDERR.puts "Note: You should install the 'colorize' gem for extra prettiness.\n"
27
+ # Monochrome hilite does nothing...
28
+ class String
29
+ def hilite(words); self; end
30
+ end
31
+ end
32
+
33
+ #################################################################
34
+
35
+ def formatdate(date, width=11)
36
+ dt = DateTime.parse(date)
37
+ time = "%l:%M%P"
38
+ date = "%d %b %g"
39
+ #dt.strftime("#{date} #{time}")
40
+ result = dt.strftime(date)
41
+
42
+ result.ljust(width).light_yellow
43
+ end
44
+
45
+
46
+ def display(post, query, indent_size=11)
47
+ indent = " " * indent_size
48
+
49
+ date = formatdate(post[:time], indent_size)
50
+ desc = post[:description].hilite(query, :light_white)
51
+ ext = post[:extended].hilite(query, :white)
52
+ url = post[:href].hilite(query, :light_blue)
53
+ tags = post[:tag].hilite(query, :light_cyan)
54
+
55
+ puts date + desc
56
+ puts indent + ext if post[:extended].any?
57
+ puts indent + url
58
+ puts indent + "(".cyan + tags + ")".cyan
59
+ puts
60
+ end
@@ -0,0 +1,7 @@
1
+ require 'logger'
2
+
3
+ $log = Logger.new(STDOUT)
4
+ $log.level = Logger::INFO
5
+ #$log.level = Logger::DEBUG
6
+
7
+ $log.datetime_format = "%d %b %g @ %l:%M:%S. %L %P "
@@ -0,0 +1,60 @@
1
+ require 'fileutils'
2
+ require 'yaml'
3
+
4
+ require 'delicious-cli/log'
5
+
6
+ #################################################################
7
+ HOMEDIR = File.expand_path("~")
8
+ CONFIGDIR = File.join(HOMEDIR, ".delicious")
9
+ #################################################################
10
+
11
+ #################################################################
12
+ class FileNotFound < IOError; end
13
+ #################################################################
14
+
15
+ #################################################################
16
+ def configfile(filename, check=false)
17
+ path = File.join(CONFIGDIR, filename)
18
+
19
+ if check and not File.exists? path
20
+ raise FileNotFound, path
21
+ end
22
+
23
+ path
24
+ end
25
+ #################################################################
26
+
27
+
28
+
29
+ #################################################################
30
+
31
+ unless File.exists? CONFIGDIR
32
+ $log.info "* Creating new config directory: #{CONFIGDIR}"
33
+ FileUtils.mkdir_p CONFIGDIR
34
+ end
35
+
36
+ class Settings
37
+
38
+ def self.settings
39
+ @settings ||= {}
40
+ end
41
+
42
+ def self.[](key)
43
+ settings[key]
44
+ end
45
+
46
+ def self.[]=(key, val)
47
+ settings[key] = val
48
+ end
49
+
50
+ def self.load!
51
+ @settings = YAML::load_file( configfile('settings.yml') )
52
+ end
53
+
54
+ def self.save!
55
+ open( configfile('settings.yml'), "w" ) do |f|
56
+ f.write YAML::dump(settings)
57
+ end
58
+ end
59
+
60
+ end
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #################################################################
4
+
5
+ require "delicious-cli/log"
6
+
7
+ libs = %w[
8
+ rubygems
9
+
10
+ optparse
11
+ optparse/time
12
+ ostruct
13
+
14
+ delicious-cli/db
15
+ delicious-cli/display
16
+ delicious-cli/api
17
+ delicious-cli/settings
18
+ ]
19
+
20
+ libs.each do |lib|
21
+ #$log.debug "loading #{lib}"
22
+ require lib
23
+ end
24
+
25
+ #################################################################
26
+
27
+ def search(words)
28
+ matches = Database.find(words)
29
+ matches.each { |match| display(match, words) }
30
+ end
31
+
32
+ def sync
33
+ puts "* Synchronizing database..."
34
+ Database.sync
35
+ end
36
+
37
+ def redownload
38
+ puts "* Redownloading database..."
39
+ Database.clear!
40
+ Database.sync
41
+ end
42
+
43
+ def config
44
+ # prompt user and stuff
45
+ puts "Delicious login info"
46
+ puts "---------------------------"
47
+ puts
48
+ print "Username: "
49
+ username = gets.strip
50
+ print "Password: "
51
+ password = gets.strip
52
+
53
+ puts
54
+ puts "User: #{username.inspect} | Pass: #{password.inspect}"
55
+ print "Is this correct? [y/N] "
56
+ answer = gets.strip
57
+ if answer =~ /^[Yy]$/
58
+ puts
59
+
60
+ puts "* Checking that login/password works..."
61
+ Delicious.basic_auth(username, password)
62
+ if Delicious.valid_auth?
63
+ puts " |_ Login successful! Saving config..."
64
+ Settings["username"] = username
65
+ Settings["password"] = password
66
+ Settings.save!
67
+ else
68
+ puts " |_ Login failed! (Please check your credentials or network.)"
69
+ end
70
+ puts
71
+ else
72
+ puts "Aborting..."
73
+ end
74
+ end
75
+
76
+
77
+ #################################################################
78
+
79
+ def main
80
+
81
+ # In it!
82
+
83
+ Database.init!
84
+ begin
85
+ Settings.load!
86
+ rescue Errno::ENOENT
87
+ config
88
+ retry
89
+ end
90
+
91
+ Delicious.basic_auth(Settings["username"], Settings["password"])
92
+
93
+ # Parse de opts
94
+
95
+ ARGV.push("--help") if ARGV.empty?
96
+
97
+ options = OpenStruct.new
98
+ OptionParser.new do |opts|
99
+ opts.banner = "Usage: dels [options] <search query>"
100
+ opts.separator ""
101
+ opts.separator "Specific options:"
102
+
103
+ opts.on("-d", "--debug", "Debug info") do |opt|
104
+ options.debug = true
105
+ end
106
+
107
+ opts.on("-s", "--sync", "Synchronize links") do |opt|
108
+ options.sync = true
109
+ end
110
+
111
+ opts.on("-r", "--redownload", "Erase database and redownload all links") do |opt|
112
+ options.redownload = true
113
+ end
114
+
115
+ opts.on("-c", "--config", "Configure app (set delicious username/password)") do |opt|
116
+ options.config = true
117
+ end
118
+
119
+ opts.on("-t", "--test-auth", "Test that authentication info works") do |opt|
120
+ options.test_auth = true
121
+ end
122
+
123
+
124
+ opts.separator ""
125
+ opts.separator "Common options:"
126
+
127
+ # No argument, shows at tail. This will print an options summary.
128
+ # Try it and see!
129
+ opts.on_tail("-h", "--help", "Show this message") do
130
+ puts opts
131
+ puts
132
+ exit
133
+ end
134
+
135
+ end.parse!
136
+
137
+
138
+ # Handle options
139
+
140
+ $log.level = Logger::DEBUG if options.debug
141
+
142
+ if options.test_auth
143
+ puts "Logging in as '#{Settings["username"]}': #{Delicious.valid_auth? ? "success!" : "fail!"}"
144
+ elsif options.config
145
+ config
146
+ elsif options.redownload
147
+ redownload
148
+ elsif options.sync
149
+ sync
150
+ else
151
+ exit 1 unless ARGV.size > 0
152
+ search(ARGV)
153
+ end
154
+
155
+ end
156
+
157
+
158
+ if $0 == __FILE__
159
+ main
160
+ end
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ class DeliciousCliTest < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'delicious-cli'
8
+
9
+ class Test::Unit::TestCase
10
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: epitron-delicious-cli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.4
5
+ platform: ruby
6
+ authors:
7
+ - epitron
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-29 00:00:00 -07:00
13
+ default_executable: dels
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: sequel
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: httparty
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: colorize
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: sqlite3-ruby
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ description: A commandline tool which lets you download all your delicious.com links into a local SQLite database and search them (with pretty color-coded results).
56
+ email: chris@ill-logic.com
57
+ executables:
58
+ - dels
59
+ extensions: []
60
+
61
+ extra_rdoc_files:
62
+ - LICENSE
63
+ - README.rdoc
64
+ files:
65
+ - lib/delicious-cli.rb
66
+ - lib/delicious-cli/api.rb
67
+ - lib/delicious-cli/db.rb
68
+ - lib/delicious-cli/display.rb
69
+ - lib/delicious-cli/log.rb
70
+ - lib/delicious-cli/settings.rb
71
+ - LICENSE
72
+ - README.rdoc
73
+ has_rdoc: false
74
+ homepage: http://github.com/epitron/delicious-cli
75
+ post_install_message:
76
+ rdoc_options:
77
+ - --charset=UTF-8
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: "0"
85
+ version:
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: "0"
91
+ version:
92
+ requirements: []
93
+
94
+ rubyforge_project:
95
+ rubygems_version: 1.2.0
96
+ signing_key:
97
+ specification_version: 3
98
+ summary: Delicious.com commandline interface
99
+ test_files:
100
+ - test/delicious-cli_test.rb
101
+ - test/test_helper.rb