epitron-delicious-cli 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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