snoopy 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/snoopy +97 -0
- data/lib/ftp.rb +22 -0
- data/lib/http.rb +94 -0
- data/lib/https.rb +113 -0
- data/lib/snoopy.rb +22 -0
- data/lib/source.rb +50 -0
- data/lib/torrent.rb +90 -0
- metadata +76 -0
data/bin/snoopy
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
#
|
3
|
+
# Created by me on 2007-12-24.
|
4
|
+
# Copyright (c) 2007. All pwnage reserved.
|
5
|
+
|
6
|
+
require "rubygems"
|
7
|
+
require "snoopy"
|
8
|
+
|
9
|
+
##################################################################################
|
10
|
+
|
11
|
+
opts = Trollop::options do
|
12
|
+
version "snoopy 0.0.4 (c) Ari Brown"
|
13
|
+
banner <<-EOS
|
14
|
+
snoopy is a multithreaded golden retriever for the internet
|
15
|
+
--> handles http, ftp (single threaded), and torrent
|
16
|
+
|
17
|
+
Usage:
|
18
|
+
snoopy <URI of file> [options]
|
19
|
+
Example:
|
20
|
+
snoopy http://www.lighttpd.net -t 5 option
|
21
|
+
where [options] are:
|
22
|
+
EOS
|
23
|
+
|
24
|
+
opt :verbose, "Be as loud as possible"
|
25
|
+
opt :clean, "Remove the out file after finishing (debugging)"
|
26
|
+
opt :out, "Where to save the file", :type => String
|
27
|
+
opt :threads, "Set number of threads to use", :default => 5
|
28
|
+
opt :dots, "Display a dot every X times a block comes in", :default => 10
|
29
|
+
end
|
30
|
+
Trollop::die "need a file to download" if ARGV.empty?
|
31
|
+
|
32
|
+
##################################################################################
|
33
|
+
|
34
|
+
opts[:in] = ARGV[0]
|
35
|
+
|
36
|
+
url = URI.parse(opts[:in])
|
37
|
+
|
38
|
+
ball = Source.new(url, opts)
|
39
|
+
|
40
|
+
def start_report(opts)
|
41
|
+
puts Time.now
|
42
|
+
puts "Downloading #{opts[:in]}"
|
43
|
+
puts "with #{opts[:threads]} thread" + (opts[:threads] > 1 ? "s" : '')
|
44
|
+
puts
|
45
|
+
end
|
46
|
+
|
47
|
+
def determine_size(value)
|
48
|
+
if value >= 1073741824
|
49
|
+
end_value = value / 1073741824
|
50
|
+
unit = 'GB'
|
51
|
+
elsif value >= 1048576 and value < 1073741824
|
52
|
+
end_value = value / 1048576
|
53
|
+
unit = 'MB'
|
54
|
+
elsif value >= 1024 and value < 1048576
|
55
|
+
end_value = value / 1024
|
56
|
+
unit = 'KB'
|
57
|
+
else
|
58
|
+
end_value = value
|
59
|
+
unit = 'B'
|
60
|
+
end
|
61
|
+
|
62
|
+
[end_value, unit, value]
|
63
|
+
end
|
64
|
+
|
65
|
+
def end_report(source, opts)
|
66
|
+
puts
|
67
|
+
puts "Download file written to " + opts[:out] + (opts[:clean] ? ", and then deleted" : '')
|
68
|
+
puts "Elapsed time: " + $elapsed_time.to_s + " seconds"
|
69
|
+
|
70
|
+
end_value, unit, actual = determine_size(source.length)
|
71
|
+
|
72
|
+
if end_value.zero?
|
73
|
+
end_value, unit, actual = determine_size(File.size(opts[:out]))
|
74
|
+
end
|
75
|
+
|
76
|
+
puts "Total downloaded: " + end_value.to_s + " " + unit
|
77
|
+
res = determine_size(actual/$elapsed_time)
|
78
|
+
puts "At %.2f%s/s" % [res[0], res[1]]
|
79
|
+
|
80
|
+
source.finish
|
81
|
+
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
start_report(opts)
|
88
|
+
##################################################################################
|
89
|
+
$start_time = Time.now
|
90
|
+
##################################################################################
|
91
|
+
|
92
|
+
ball.roll
|
93
|
+
|
94
|
+
##################################################################################
|
95
|
+
$elapsed_time = Time.now - $start_time
|
96
|
+
##################################################################################
|
97
|
+
end_report(ball, opts)
|
data/lib/ftp.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
#
|
3
|
+
# Created by me on 2007-12-28.
|
4
|
+
# Copyright (c) 2007. All pwnage reserved.
|
5
|
+
|
6
|
+
module FTP
|
7
|
+
|
8
|
+
def setup(opts, uri)
|
9
|
+
@opts = opts
|
10
|
+
end
|
11
|
+
|
12
|
+
def prep
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch
|
16
|
+
Net::FTP.open(@opts[:in]) {|ftp| ftp.getbinaryfile(@opts[:in], @opts[:out]) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def clean
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/lib/http.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
#
|
3
|
+
# Created by me on 2007-12-28.
|
4
|
+
# Copyright (c) 2007. All pwnage reserved.
|
5
|
+
|
6
|
+
module HTTP
|
7
|
+
class << self
|
8
|
+
def setup(opts, uri)
|
9
|
+
@opts = opts
|
10
|
+
@url = uri
|
11
|
+
end
|
12
|
+
|
13
|
+
def prep
|
14
|
+
req = Net::HTTP.new(@url.host, @url.port)
|
15
|
+
len = req.request_head(@url.page)['content-length']
|
16
|
+
@url.length = len.to_i
|
17
|
+
|
18
|
+
if len.nil? or @url.length == 0
|
19
|
+
@opts[:threads] = 1
|
20
|
+
puts "--> Uhoh! Server isn't cooperating..."
|
21
|
+
puts "--> Changing threadcount to 1"
|
22
|
+
alias_method :fetch_multi, :fetch
|
23
|
+
alias_method :fetch, :fetch_single
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def fetch_single
|
28
|
+
p single
|
29
|
+
open(@opts[:out], "w") do |f|
|
30
|
+
http = Net::HTTP.new(@url.host, @url.port)
|
31
|
+
http.get(@url.page) do |str|
|
32
|
+
f.write str
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def fetch
|
38
|
+
i = 0
|
39
|
+
array = []
|
40
|
+
start = 0
|
41
|
+
jump = @url.length / @opts[:threads]
|
42
|
+
finish = -1 # -1 so that start becomes 0
|
43
|
+
|
44
|
+
@opts[:threads].times do |t|
|
45
|
+
start = finish + 1
|
46
|
+
finish = (start + jump) - 1
|
47
|
+
array << [start, finish, t + 1]
|
48
|
+
end
|
49
|
+
|
50
|
+
array.each_simultaneously { |d, b, w|
|
51
|
+
puts "[LOG] Setting up a new thread..." if @opts[:verbose]
|
52
|
+
open(@opts[:out], "a+") do |f|
|
53
|
+
begin
|
54
|
+
f.seek(d)
|
55
|
+
Net::HTTP.new(@url.host, @url.port).start do |http|
|
56
|
+
http.get(@url.page, {'range' => "bytes=#{d}-#{b}"}) do |str|
|
57
|
+
i += 1
|
58
|
+
print '.' if i % @opts[:dots] == 0
|
59
|
+
STDOUT.flush
|
60
|
+
f.write str if str
|
61
|
+
end
|
62
|
+
end
|
63
|
+
rescue Timeout::Error => e
|
64
|
+
print "\n[Error] Error in server response in thread #{w}. Retrying!\n"
|
65
|
+
puts e
|
66
|
+
STDOUT.flush
|
67
|
+
sleep 5
|
68
|
+
retry
|
69
|
+
rescue => e
|
70
|
+
print "\n[Error] Error in server response in thread #{w}. Retrying!\n"
|
71
|
+
puts e
|
72
|
+
STDOUT.flush
|
73
|
+
sleep 5
|
74
|
+
retry
|
75
|
+
end
|
76
|
+
end
|
77
|
+
puts "[LOG] Thread #{w} finished!" if @opts[:verbose]
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
def clean
|
82
|
+
remove_extra_files
|
83
|
+
end
|
84
|
+
|
85
|
+
def remove_extra_files
|
86
|
+
puts "[LOG] Writing #{@opts[:out]}" if @opts[:verbose]
|
87
|
+
|
88
|
+
if @opts[:clean]
|
89
|
+
File.delete(@opts[:out])
|
90
|
+
puts "[LOG] Removing #{@opts[:out]}" if @opts[:verbose]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/https.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
#
|
3
|
+
# Created by me on 2007-12-28.
|
4
|
+
# Copyright (c) 2007. All pwnage reserved.
|
5
|
+
|
6
|
+
module HTTPS
|
7
|
+
class << self
|
8
|
+
def setup(opts, uri)
|
9
|
+
@opts = opts
|
10
|
+
@url = uri
|
11
|
+
end
|
12
|
+
|
13
|
+
def prep
|
14
|
+
req = Net::HTTP.new(@url.host, @url.port)
|
15
|
+
req.use_ssl = true
|
16
|
+
len = req.request_head(@url.page)['content-length']
|
17
|
+
@url.length = len.to_i
|
18
|
+
|
19
|
+
if len.nil?
|
20
|
+
@opts[:threads] = 1
|
21
|
+
puts "--> Uhoh! Server isn't cooperating..."
|
22
|
+
puts "--> Changing threadcount to 1"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def fetch
|
27
|
+
array = []
|
28
|
+
start = 0
|
29
|
+
jump = @url.length / @opts[:threads]
|
30
|
+
finish = -1 # -1 so that start becomes 0
|
31
|
+
|
32
|
+
@opts[:threads].times do |t|
|
33
|
+
start = finish + 1
|
34
|
+
finish = (start + jump) - 1
|
35
|
+
array << [start, finish, t + 1]
|
36
|
+
end
|
37
|
+
|
38
|
+
array.each_simultaneously { |d, b, w|
|
39
|
+
puts "[LOG] Setting up a new thread..." if @opts[:verbose]
|
40
|
+
p @opts[:out]
|
41
|
+
open(@opts[:out], "a+") do |f|
|
42
|
+
f.seek(d)
|
43
|
+
http = Net::HTTP.new(@url.host, @url.port)
|
44
|
+
http.use_ssl = true
|
45
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
46
|
+
c = http.start do |http|
|
47
|
+
a = Net::HTTP::Get.new(@url.page)
|
48
|
+
a.range = (d..b)
|
49
|
+
http.request(a)
|
50
|
+
end
|
51
|
+
f.write(c.body)
|
52
|
+
end
|
53
|
+
}
|
54
|
+
end
|
55
|
+
=begin
|
56
|
+
def fetch_fat
|
57
|
+
array = []
|
58
|
+
start = 0
|
59
|
+
jump = @url.length / @opts[:threads]
|
60
|
+
finish = -1 # -1 so that start becomes 0
|
61
|
+
|
62
|
+
@opts[:threads].times do |t|
|
63
|
+
start = finish + 1
|
64
|
+
finish = (start + jump) - 1
|
65
|
+
array << [start, finish, t + 1]
|
66
|
+
end
|
67
|
+
|
68
|
+
array.each_simultaneously { |d, b, w|
|
69
|
+
puts "[LOG] Setting up a new thread..." if @opts[:verbose]
|
70
|
+
open("/tmp/part#{w}", "w") do |f|
|
71
|
+
http = Net::HTTP.new(@url.host, @url.port)
|
72
|
+
c = http.start do |http|
|
73
|
+
a = Net::HTTP::Get.new(@url.page)
|
74
|
+
a.range = (d..b)
|
75
|
+
http.request(a)
|
76
|
+
end
|
77
|
+
f.write(c.body)
|
78
|
+
end
|
79
|
+
}
|
80
|
+
end
|
81
|
+
=end
|
82
|
+
def clean
|
83
|
+
mash_files
|
84
|
+
remove_extra_files
|
85
|
+
end
|
86
|
+
|
87
|
+
def remove_extra_files
|
88
|
+
puts "[LOG] Writing #{@opts[:out]}" if @opts[:verbose]
|
89
|
+
|
90
|
+
Dir["/tmp/part*"].each do |part|
|
91
|
+
File.delete(part)
|
92
|
+
puts "[LOG] Removing #{part}" if @opts[:verbose]
|
93
|
+
end
|
94
|
+
|
95
|
+
if @opts[:clean]
|
96
|
+
File.delete(@opts[:out])
|
97
|
+
puts "[LOG] Removing #{@opts[:out]}" if @opts[:verbose]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def mash_files
|
102
|
+
open(@opts[:out], "a") do |f|
|
103
|
+
files = Dir["/tmp/part*"].sort_by { |word|
|
104
|
+
word.scan(/\d+/)[0].to_i
|
105
|
+
}
|
106
|
+
files.each do |part|
|
107
|
+
puts "[LOG] Reading #{part}" if @opts[:verbose]
|
108
|
+
f.print open(part, "r").read
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/lib/snoopy.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "trollop"
|
3
|
+
require "net/http"
|
4
|
+
require "net/https"
|
5
|
+
require "net/ftp"
|
6
|
+
require "rubytorrent"
|
7
|
+
require "tempfile"
|
8
|
+
require "open-uri"
|
9
|
+
|
10
|
+
require File.join(File.dirname(__FILE__), "source")
|
11
|
+
require File.join(File.dirname(__FILE__), "http")
|
12
|
+
require File.join(File.dirname(__FILE__), "https")
|
13
|
+
require File.join(File.dirname(__FILE__), "ftp")
|
14
|
+
|
15
|
+
|
16
|
+
module Enumerable
|
17
|
+
def each_simultaneously
|
18
|
+
threads = []
|
19
|
+
each { |e, f, i| threads << Thread.new { yield e, f, i } }
|
20
|
+
return threads.each {|t| t.join }
|
21
|
+
end
|
22
|
+
end
|
data/lib/source.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
#
|
3
|
+
# Created by me on 2007-12-28.
|
4
|
+
# Copyright (c) 2007. All pwnage reserved.
|
5
|
+
|
6
|
+
class Source
|
7
|
+
|
8
|
+
attr_accessor :port, :scheme, :host, :path, :length, :page, :full
|
9
|
+
|
10
|
+
def initialize(uri_obj, opts)
|
11
|
+
@uri = uri_obj
|
12
|
+
@host = uri_obj.host
|
13
|
+
@port = uri_obj.port
|
14
|
+
@scheme = uri_obj.scheme
|
15
|
+
@page = (uri_obj.path =~ /^$/ ? "/" : uri_obj.path)
|
16
|
+
@full = @scheme + '://' + @host + @page
|
17
|
+
@in = opts[:in]
|
18
|
+
@out = opts[:out]
|
19
|
+
opts[:out] = opts[:out] || (File.basename(@page) =~ /^\/$/ ? 'default' : File.basename(@page))
|
20
|
+
opts[:out] = (File.directory?(opts[:out]) ? File.join(opts[:out], File.basename(@page)) : opts[:out])
|
21
|
+
|
22
|
+
case @scheme
|
23
|
+
when "http" then @prefix = HTTP
|
24
|
+
when "https" then @prefix = HTTPS
|
25
|
+
when "ftp" then @prefix = FTP
|
26
|
+
when "torrent" then @prefix = Torrent
|
27
|
+
else
|
28
|
+
abort "Unknown URL scheme!\nsnoopy --help for help and usage"
|
29
|
+
end
|
30
|
+
|
31
|
+
prep(opts)
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def start
|
36
|
+
@prefix.prep
|
37
|
+
@prefix.fetch
|
38
|
+
end
|
39
|
+
alias_method :roll, :start
|
40
|
+
|
41
|
+
def finish
|
42
|
+
@prefix.clean
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def prep(opts)
|
47
|
+
@prefix.setup(opts, self)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
data/lib/torrent.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
#
|
3
|
+
# Created by me on 2007-12-28.
|
4
|
+
# Copyright (c) 2007. All pwnage reserved.
|
5
|
+
|
6
|
+
module Torrent
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def setup(opts, uri)
|
10
|
+
@opts = opts
|
11
|
+
@url = uri
|
12
|
+
end
|
13
|
+
|
14
|
+
def prep
|
15
|
+
begin
|
16
|
+
mi = RubyTorrent::MetaInfo.from_location(File.join(@url.host, @url.path), ENV["http_proxy"])
|
17
|
+
rescue RubyTorrent::MetaInfoFormatError, RubyTorrent::BEncodingError => e
|
18
|
+
abort "[Error] Can't parse metainfo file '#{torrent}' -- maybe not a .torrent?"
|
19
|
+
rescue RubyTorrent::TypedStructError => e
|
20
|
+
raise e
|
21
|
+
rescue IOError, SystemCallError => e
|
22
|
+
abort "[Error] Can't read file '#{torrent}': #{e.message}"
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
if FileTest.directory?(@opts[:out]) && mi.info.single?
|
27
|
+
dest = File.join(@opts[:out], mi.info.name)
|
28
|
+
elsif FileTest.file?(@opts[:out]) && mi.info.multiple?
|
29
|
+
abort "[Error] .torrent contains multiple files, but '#{dest}' is a single file (must be a directory)"
|
30
|
+
end
|
31
|
+
dest ||= @opts[:out]
|
32
|
+
|
33
|
+
print "[LOG] Checking file status: " if @opts[:verbose]
|
34
|
+
package = RubyTorrent::Package.new(mi, dest) do |piece|
|
35
|
+
print(piece.complete? && piece.valid? ? "#" : ".") if @opts[:verbose]
|
36
|
+
end
|
37
|
+
puts " done" if @opts[:verbose]
|
38
|
+
end
|
39
|
+
|
40
|
+
def fetch
|
41
|
+
puts "[Log] Starting peer..." if @opts[:verbose]
|
42
|
+
bt = RubyTorrent::BitTorrent.new(mi, package, :http_proxy => proxy)
|
43
|
+
|
44
|
+
if @opts[:verbose]
|
45
|
+
bt.on_event(self, :added_peer) { |s, p| puts "[Log] Connected to peer #{p}" }
|
46
|
+
bt.on_event(self, :received_block) { |s, b, peer| puts "<-- got block #{b} from peer #{peer}, now #{package.pieces[b.pindex].percent_done.f}% done and #{package.pieces[b.pindex].percent_claimed.f}% claimed" }
|
47
|
+
bt.on_event(self, :sent_block) { |s, b, peer| puts "--> Sent block #{b} to peer #{peer}" }
|
48
|
+
bt.on_event(self, :discarded_piece) { |s, p| puts "[Log] Checksum error on piece #{p}, discarded" }
|
49
|
+
bt.on_event(self, :tracker_connected) { |s, url| puts "--> Connected to tracker #{url}" }
|
50
|
+
bt.on_event(self, :tracker_lost) { |s, url| puts "--> Couldn't connect to tracker #{url}" }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Always register this
|
54
|
+
bt.on_event(self, :complete) do
|
55
|
+
@url.length = bt.dlamt
|
56
|
+
puts "--> Download complete!"
|
57
|
+
end
|
58
|
+
|
59
|
+
puts "[Log] Listening on #{bt.ip}:#{bt.port}" if @opts[:verbose]
|
60
|
+
|
61
|
+
|
62
|
+
thread = Thread.new do
|
63
|
+
until bt.complete?
|
64
|
+
previous = ''
|
65
|
+
|
66
|
+
if bt.tracker
|
67
|
+
|
68
|
+
current = '%s: %dk of %dk (%.2f%% complete)' % [Time.now,
|
69
|
+
bt.bytes_completed / 1024,
|
70
|
+
bt.total_bytes / 1024,
|
71
|
+
bt.percent_completed]
|
72
|
+
print "\b" * previous.size
|
73
|
+
print curent
|
74
|
+
previous = ''
|
75
|
+
|
76
|
+
sleep(5)
|
77
|
+
else
|
78
|
+
sleep(5)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
thread.join
|
84
|
+
end
|
85
|
+
|
86
|
+
def clean
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: snoopy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ari Brown
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-01-14 00:00:00 -05:00
|
13
|
+
default_executable: snoopy
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rubytorrent
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0.3"
|
23
|
+
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: trollop
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: 0.0.1
|
32
|
+
version:
|
33
|
+
description: Segmented downloader for HTTP(S)/FTP/Bit Torrent.
|
34
|
+
email: ari@aribrown.com
|
35
|
+
executables:
|
36
|
+
- snoopy
|
37
|
+
extensions: []
|
38
|
+
|
39
|
+
extra_rdoc_files: []
|
40
|
+
|
41
|
+
files:
|
42
|
+
- lib/ftp.rb
|
43
|
+
- lib/http.rb
|
44
|
+
- lib/https.rb
|
45
|
+
- lib/snoopy.rb
|
46
|
+
- lib/source.rb
|
47
|
+
- lib/torrent.rb
|
48
|
+
- bin/snoopy
|
49
|
+
has_rdoc: false
|
50
|
+
homepage: http://snoopy.rubyforge.org/
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.0.0
|
72
|
+
signing_key:
|
73
|
+
specification_version: 2
|
74
|
+
summary: Snoopy is a golden retriever for the internet. It's a great big world and there... And you need a great big dog to do your bidding.
|
75
|
+
test_files: []
|
76
|
+
|