downlow 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,23 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+
23
+ tmp
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Aaron Quint
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.
@@ -0,0 +1,58 @@
1
+ = downlow
2
+
3
+ Downloading files on the DL
4
+
5
+ == Installation
6
+
7
+ gem install downlow
8
+
9
+ Tested against Ruby 1.8.7 and 1.9.1
10
+
11
+ == Usage
12
+
13
+ Why is downloading and extracting files such a pain in Ruby?
14
+
15
+ Downlow to the rescue.
16
+
17
+ $ irb -rubygems -rdownlow
18
+ >> DL('http://gist.github.com/gists/290151/download', '~/Desktop/gist')
19
+ => #<Pathname:/Users/aaronquint/Desktop/gist>
20
+
21
+ $ ls -l ~/Desktop/gist/
22
+ total 8
23
+ drwxrwxr-x 3 aaronquint aaronquint 102 Jan 30 00:17 gist290151-4d195b0fc72a4b5e52f8e2b5d1670d078c03a018
24
+
25
+ >> DL('git://github.com/quirkey/resque-status.git', '~/Desktop')
26
+ Initialized empty Git repository in /Users/aaronquint/Sites/__active/downlow/tmp/resque-status/.git/
27
+ remote: Counting objects: 323, done.
28
+ remote: Compressing objects: 100% (186/186), done.
29
+ remote: Total 323 (delta 166), reused 169 (delta 83)
30
+ Receiving objects: 100% (323/323), 42.84 KiB, done.
31
+ Resolving deltas: 100% (166/166), done.
32
+ => #<Pathname:/Users/aaronquint/Desktop/resque-status>
33
+
34
+ Sweet.
35
+
36
+ BONUS BEATS FOR GITHUB
37
+ Downloads the tarball and extracts.
38
+
39
+ >> DL('gh://quirkey/sammy', ~/Desktop')
40
+
41
+ == Thanks
42
+
43
+ Thanks to @mxcl for the awesome pathname extensions in homebrew/
44
+ Thanks to @defunkt as I took some ideas from rip.
45
+
46
+ == Note on Patches/Pull Requests
47
+
48
+ * Fork the project.
49
+ * Make your feature addition or bug fix.
50
+ * Add tests for it. This is important so I don't break it in a
51
+ future version unintentionally.
52
+ * Commit, do not mess with rakefile, version, or history.
53
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
54
+ * Send me a pull request. Bonus points for topic branches.
55
+
56
+ == Copyright
57
+
58
+ Copyright (c) 2010 Aaron Quint. See LICENSE for details.
@@ -0,0 +1,63 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
5
+
6
+ require 'downlow'
7
+
8
+ begin
9
+ require 'jeweler'
10
+ Jeweler::Tasks.new do |gem|
11
+ gem.name = "downlow"
12
+ gem.version = Downlow::VERSION
13
+ gem.summary = %Q{easy downloading and extracting API}
14
+ gem.description = %Q{Downlow provides an easy way to fetch files or archives and extract them with minimal hassle.}
15
+ gem.email = "aaron@quirkey.com"
16
+ gem.homepage = "http://github.com/quirkey/downlow"
17
+ gem.authors = ["Aaron Quint"]
18
+
19
+ gem.add_dependency "rubyzip", ">=0.9.4"
20
+ gem.add_dependency "archive-tar-minitar", ">=0.5.2"
21
+
22
+ gem.add_development_dependency "shoulda", ">= 0"
23
+ gem.add_development_dependency "fakeweb", ">= 1.2"
24
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
25
+ end
26
+ Jeweler::GemcutterTasks.new
27
+ rescue LoadError
28
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
29
+ end
30
+
31
+ require 'rake/testtask'
32
+ Rake::TestTask.new(:test) do |test|
33
+ test.libs << 'lib' << 'test'
34
+ test.pattern = 'test/**/test_*.rb'
35
+ test.verbose = true
36
+ end
37
+
38
+ begin
39
+ require 'rcov/rcovtask'
40
+ Rcov::RcovTask.new do |test|
41
+ test.libs << 'test'
42
+ test.pattern = 'test/**/test_*.rb'
43
+ test.verbose = true
44
+ end
45
+ rescue LoadError
46
+ task :rcov do
47
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
48
+ end
49
+ end
50
+
51
+ task :test => :check_dependencies
52
+
53
+ task :default => :test
54
+
55
+ require 'rake/rdoctask'
56
+ Rake::RDocTask.new do |rdoc|
57
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
58
+
59
+ rdoc.rdoc_dir = 'rdoc'
60
+ rdoc.title = "downlow #{version}"
61
+ rdoc.rdoc_files.include('README*')
62
+ rdoc.rdoc_files.include('lib/**/*.rb')
63
+ end
@@ -0,0 +1,80 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{downlow}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Aaron Quint"]
12
+ s.date = %q{2010-01-31}
13
+ s.description = %q{Downlow provides an easy way to fetch files or archives and extract them with minimal hassle.}
14
+ s.email = %q{aaron@quirkey.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "downlow.gemspec",
26
+ "lib/downlow.rb",
27
+ "lib/downlow/ext/pathname.rb",
28
+ "lib/downlow/extractor.rb",
29
+ "lib/downlow/extractors/dir.rb",
30
+ "lib/downlow/extractors/tar_gz.rb",
31
+ "lib/downlow/extractors/zip.rb",
32
+ "lib/downlow/fetcher.rb",
33
+ "lib/downlow/fetchers/git.rb",
34
+ "lib/downlow/fetchers/github.rb",
35
+ "lib/downlow/fetchers/http.rb",
36
+ "lib/downlow/fetchers/local.rb",
37
+ "test/fixtures/gist_response",
38
+ "test/fixtures/location_response",
39
+ "test/fixtures/test.tar.gz",
40
+ "test/fixtures/test.zip",
41
+ "test/helper.rb",
42
+ "test/test_downlow.rb",
43
+ "test/test_downlow_extractor.rb",
44
+ "test/test_downlow_fetcher.rb"
45
+ ]
46
+ s.homepage = %q{http://github.com/quirkey/downlow}
47
+ s.rdoc_options = ["--charset=UTF-8"]
48
+ s.require_paths = ["lib"]
49
+ s.rubygems_version = %q{1.3.5}
50
+ s.summary = %q{easy downloading and extracting API}
51
+ s.test_files = [
52
+ "test/helper.rb",
53
+ "test/test_downlow.rb",
54
+ "test/test_downlow_extractor.rb",
55
+ "test/test_downlow_fetcher.rb"
56
+ ]
57
+
58
+ if s.respond_to? :specification_version then
59
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
60
+ s.specification_version = 3
61
+
62
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
63
+ s.add_runtime_dependency(%q<rubyzip>, [">= 0.9.4"])
64
+ s.add_runtime_dependency(%q<archive-tar-minitar>, [">= 0.5.2"])
65
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
66
+ s.add_development_dependency(%q<fakeweb>, [">= 1.2"])
67
+ else
68
+ s.add_dependency(%q<rubyzip>, [">= 0.9.4"])
69
+ s.add_dependency(%q<archive-tar-minitar>, [">= 0.5.2"])
70
+ s.add_dependency(%q<shoulda>, [">= 0"])
71
+ s.add_dependency(%q<fakeweb>, [">= 1.2"])
72
+ end
73
+ else
74
+ s.add_dependency(%q<rubyzip>, [">= 0.9.4"])
75
+ s.add_dependency(%q<archive-tar-minitar>, [">= 0.5.2"])
76
+ s.add_dependency(%q<shoulda>, [">= 0"])
77
+ s.add_dependency(%q<fakeweb>, [">= 1.2"])
78
+ end
79
+ end
80
+
@@ -0,0 +1,48 @@
1
+ require 'downlow/ext/pathname'
2
+
3
+ module Downlow
4
+ VERSION = '0.1.0'
5
+
6
+ def self.get(url, *args)
7
+ options = {}
8
+ first = args.shift
9
+ if first.is_a?(Hash)
10
+ # hash as argument means were setting the options
11
+ options = first
12
+ elsif first.to_s != ''
13
+ # string as argument means we're setting the destination
14
+ options[:destination] = first
15
+ end
16
+ # merge the rest as options
17
+ args.inject(options) {|o, arg| o = o.merge(arg) } if !args.empty?
18
+ # fetch to a temp dir
19
+ fetch_options = options.dup
20
+ fetch_options.delete(:destination)
21
+ path = fetch(url, fetch_options)
22
+ final_path = extract(path, options)
23
+ FileUtils.rm_r(path) # delete tmp path
24
+ final_path
25
+ end
26
+
27
+ def self.fetch(*args)
28
+ Downlow::Fetcher.fetch(*args)
29
+ end
30
+
31
+ def self.extract(*args)
32
+ Downlow::Extractor.extract(*args)
33
+ end
34
+
35
+ end
36
+
37
+ def DL(*args) Downlow.get(*args); end
38
+
39
+ require 'downlow/fetcher'
40
+ require 'downlow/fetchers/git'
41
+ require 'downlow/fetchers/http'
42
+ require 'downlow/fetchers/github'
43
+ require 'downlow/fetchers/local'
44
+
45
+ require 'downlow/extractor'
46
+ require 'downlow/extractors/tar_gz'
47
+ require 'downlow/extractors/zip'
48
+ require 'downlow/extractors/dir'
@@ -0,0 +1,201 @@
1
+ # Copyright 2009 Max Howell and other contributors.
2
+ #
3
+ # Redistribution and use in source and binary forms, with or without
4
+ # modification, are permitted provided that the following conditions
5
+ # are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright
8
+ # notice, this list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright
10
+ # notice, this list of conditions and the following disclaimer in the
11
+ # documentation and/or other materials provided with the distribution.
12
+ #
13
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14
+ # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15
+ # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16
+ # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17
+ # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18
+ # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22
+ # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+ #
24
+ require 'pathname'
25
+
26
+ # we enhance pathname to make our code more readable
27
+ class Pathname
28
+ def install src
29
+ if src.is_a? Array
30
+ src.collect {|src| install src }
31
+ else
32
+ raise "#{src} does not exist" unless File.exist? src
33
+ mkpath
34
+ if File.symlink? src
35
+ # we use the BSD mv command because FileUtils copies the target and
36
+ # not the link! I'm beginning to wish I'd used Python quite honestly!
37
+ raise unless Kernel.system 'mv', src, to_s and $? == 0
38
+ else
39
+ # we mv when possible as it is faster and you should only be using
40
+ # this function when installing from the temporary build directory
41
+ FileUtils.mv src, to_s
42
+ end
43
+ src=Pathname.new src
44
+ return self+src.basename
45
+ end
46
+ end
47
+
48
+ # we assume this pathname object is a file obviously
49
+ def write content
50
+ raise "Will not overwrite #{to_s}" if exist? and not ARGV.force?
51
+ dirname.mkpath
52
+ File.open(self, 'w') {|f| f.write content }
53
+ end
54
+
55
+ def cp dst
56
+ if file?
57
+ FileUtils.cp to_s, dst
58
+ else
59
+ FileUtils.cp_r to_s, dst
60
+ end
61
+ return dst
62
+ end
63
+
64
+ # extended to support the double extensions .tar.gz and .tar.bz2
65
+ def extname
66
+ /(\.tar\.(gz|bz2))$/.match to_s
67
+ return $1 if $1
68
+ return File.extname(to_s)
69
+ end
70
+
71
+ # for filetypes we support, basename without extension
72
+ def stem
73
+ return File.basename(to_s, extname)
74
+ end
75
+
76
+ # I don't trust the children.length == 0 check particularly, not to mention
77
+ # it is slow to enumerate the whole directory just to see if it is empty,
78
+ # instead rely on good ol' libc and the filesystem
79
+ def rmdir_if_possible
80
+ rmdir
81
+ true
82
+ rescue SystemCallError => e
83
+ raise unless e.errno == Errno::ENOTEMPTY::Errno or e.errno == Errno::EACCES::Errno
84
+ false
85
+ end
86
+
87
+ def chmod_R perms
88
+ require 'fileutils'
89
+ FileUtils.chmod_R perms, to_s
90
+ end
91
+
92
+ def abv
93
+ out=''
94
+ n=`find #{to_s} -type f | wc -l`.to_i
95
+ out<<"#{n} files, " if n > 1
96
+ out<<`/usr/bin/du -hd0 #{to_s} | cut -d"\t" -f1`.strip
97
+ end
98
+
99
+ # attempts to retrieve the version component of this path, so generally
100
+ # you'll call it on tarballs or extracted tarball directories, if you add
101
+ # to this please provide amend the unittest
102
+ def version
103
+ if directory?
104
+ # directories don't have extnames
105
+ stem=basename.to_s
106
+ else
107
+ stem=self.stem
108
+ end
109
+
110
+ # github tarballs are special
111
+ # we only support numbered tagged downloads
112
+ %r[github.com/.*/tarball/((\d\.)+\d)$].match to_s
113
+ return $1 if $1
114
+
115
+ # eg. boost_1_39_0
116
+ /((\d+_)+\d+)$/.match stem
117
+ return $1.gsub('_', '.') if $1
118
+
119
+ # eg. foobar-4.5.1-1
120
+ # eg. ruby-1.9.1-p243
121
+ /-((\d+\.)*\d\.\d+-(p|rc)?\d+)$/.match stem
122
+ return $1 if $1
123
+
124
+ # eg. lame-398-1
125
+ /-((\d)+-\d)/.match stem
126
+ return $1 if $1
127
+
128
+ # eg. foobar-4.5.1
129
+ /-((\d+\.)*\d+)$/.match stem
130
+ return $1 if $1
131
+
132
+ # eg. foobar-4.5.1b
133
+ /-((\d+\.)*\d+([abc]|rc\d))$/.match stem
134
+ return $1 if $1
135
+
136
+ # eg foobar-4.5.0-beta1
137
+ /-((\d+\.)*\d+-beta\d+)$/.match stem
138
+ return $1 if $1
139
+
140
+ # eg. foobar4.5.1
141
+ /((\d+\.)*\d+)$/.match stem
142
+ return $1 if $1
143
+
144
+ # eg foobar-4.5.0-bin
145
+ /-((\d+\.)+\d+[abc]?)[-.](bin|src|sources?)$/.match stem
146
+ return $1 if $1
147
+
148
+ # eg. otp_src_R13B (this is erlang's style)
149
+ # eg. astyle_1.23_macosx.tar.gz
150
+ stem.scan /_([^_]+)/ do |match|
151
+ return match.first if /\d/.match $1
152
+ end
153
+
154
+ nil
155
+ end
156
+
157
+ def md5
158
+ require 'digest'
159
+ Digest::MD5.hexdigest(File.read(self))
160
+ end
161
+
162
+ if '1.9' <= RUBY_VERSION
163
+ alias_method :to_str, :to_s
164
+ end
165
+ end
166
+
167
+ # sets $n and $d so you can observe creation of stuff
168
+ module ObserverPathnameExtension
169
+ def unlink
170
+ super
171
+ puts "rm #{to_s}" if ARGV.verbose?
172
+ $n+=1
173
+ end
174
+ def rmdir
175
+ super
176
+ puts "rmdir #{to_s}" if ARGV.verbose?
177
+ $d+=1
178
+ end
179
+ def resolved_path_exists?
180
+ (dirname+readlink).exist?
181
+ end
182
+ def mkpath
183
+ super
184
+ puts "mkpath #{to_s}" if ARGV.verbose?
185
+ $d+=1
186
+ end
187
+ def make_relative_symlink src
188
+ dirname.mkpath
189
+ Dir.chdir dirname do
190
+ # TODO use Ruby function so we get exceptions
191
+ # NOTE Ruby functions may work, but I had a lot of problems
192
+ rv=system 'ln', '-sf', src.relative_path_from(dirname)
193
+ raise "Could not create symlink #{to_s}" unless rv and $? == 0
194
+ puts "ln #{to_s}" if ARGV.verbose?
195
+ $n+=1
196
+ end
197
+ end
198
+ end
199
+
200
+ $n=0
201
+ $d=0
@@ -0,0 +1,42 @@
1
+ module Downlow
2
+ class Extractor
3
+
4
+ def self.handles(which)
5
+ @@handlers ||= []
6
+ @@handlers << [which, self]
7
+ end
8
+
9
+ def self.extractor_for(path)
10
+ @@handlers.each do |matcher, klass|
11
+ return klass if matcher.match path
12
+ end
13
+ end
14
+
15
+ def self.extract(url, options = {})
16
+ klass = extractor_for(url)
17
+ extractor = klass.new(url, options)
18
+ extractor.extract
19
+ extractor.final_path
20
+ end
21
+
22
+ attr_reader :path, :options, :final_path
23
+ attr_accessor :tmp_dir, :destination
24
+
25
+ def initialize(path, options = {})
26
+ @path = Pathname.new(path)
27
+ @options = options
28
+ @tmp_dir = Pathname.new(options[:tmp_dir] || 'tmp').expand_path
29
+ @tmp_dir.mkpath
30
+ @destination = Pathname.new(options[:destination] || tmp_dir + self.path.stem).expand_path
31
+ end
32
+
33
+ def extract
34
+ raise "Should be overridden by subclass"
35
+ end
36
+
37
+ def extracted?
38
+ !!@final_path
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,18 @@
1
+ module Downlow
2
+ class Dir < Extractor
3
+
4
+ handles(/.*$/)
5
+
6
+ def extract
7
+ if path.directory?
8
+ self.destination = destination + path.basename
9
+ destination.dirname.mkpath
10
+ else
11
+ destination.dirname.mkpath
12
+ end
13
+ path.cp destination
14
+ @final_path = destination
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ require 'zlib'
2
+ require 'archive/tar/minitar'
3
+
4
+ module Downlow
5
+ class TarGz < Extractor
6
+
7
+ handles(/\.tar\.gz$/)
8
+
9
+ def extract
10
+ destination.mkpath
11
+ tgz = ::Zlib::GzipReader.new(File.open(path, 'rb'))
12
+ ::Archive::Tar::Minitar.unpack(tgz, destination.to_s)
13
+ @final_path = destination
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ require 'zip/zip'
2
+
3
+ module Downlow
4
+ class Zip < Extractor
5
+
6
+ handles(/\.zip$/)
7
+
8
+ def extract
9
+ ::Zip::ZipFile.foreach(path) do |file|
10
+ path = destination + file.name
11
+ path.dirname.mkpath
12
+ file.extract(path)
13
+ end
14
+ @final_path = destination
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,43 @@
1
+ module Downlow
2
+ class Fetcher
3
+
4
+ def self.handles(which)
5
+ @@handlers ||= []
6
+ @@handlers << [which, self]
7
+ end
8
+
9
+ def self.fetcher_for(url)
10
+ @@handlers.each do |matcher, klass|
11
+ return klass if matcher.match url
12
+ end
13
+ end
14
+
15
+ def self.fetch(url, options = {})
16
+ klass = fetcher_for(url)
17
+ fetcher = klass.new(url, options)
18
+ fetcher.fetch
19
+ fetcher.local_path
20
+ end
21
+
22
+ attr_reader :url, :options, :local_path
23
+ attr_accessor :tmp_dir, :destination
24
+
25
+ def initialize(url, options = {})
26
+ @url = Pathname.new(url)
27
+ @options = options
28
+ @tmp_dir = Pathname.new(options[:tmp_dir] || 'tmp').expand_path
29
+ @tmp_dir.mkpath
30
+ @destination = Pathname.new(options[:destination] || tmp_dir + self.url.basename ).expand_path
31
+ @destination.dirname.mkpath
32
+ end
33
+
34
+ def fetch
35
+ raise "Should be overridden by subclass"
36
+ end
37
+
38
+ def fetched?
39
+ !!@local_path
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,22 @@
1
+ module Downlow
2
+ class Git < Fetcher
3
+
4
+ handles(/^git\:\/\//)
5
+
6
+ def fetch
7
+ self.destination = destination.dirname + destination.stem
8
+ git_clone
9
+ rm_dot_git unless options[:keep_git]
10
+ @local_path = destination
11
+ end
12
+
13
+ def git_clone
14
+ system "`which git` clone #{url} #{destination.expand_path}"
15
+ end
16
+
17
+ def rm_dot_git
18
+ FileUtils.rm_rf(destination + '.git')
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ module Downlow
2
+ class Github < Http
3
+
4
+ handles(/^gh\:\/\//)
5
+
6
+ def fetch
7
+ # change
8
+ # gh://quirkey/sammy
9
+ # to:
10
+ # http://github.com/quirkey/sammy/tarball/master
11
+ project = url.to_s.gsub(/^(gh\:\/\/)/, '')
12
+ @url = "http://github.com/#{project}/tarball/master"
13
+ super
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ require 'open-uri'
2
+
3
+ module Downlow
4
+ class Http < Fetcher
5
+
6
+ handles(/^http\:\/\//)
7
+
8
+ def fetch
9
+ data = ""
10
+ filename = destination.basename
11
+ open(url.to_s) do |u|
12
+ if disposition = u.meta['content-disposition'] and
13
+ disposition.match(/filename=\"([^\"]+)\"/)
14
+ filename = $1
15
+ else
16
+ filename = Pathname.new(u.base_uri.to_s).basename
17
+ end
18
+ data << u.read
19
+ end
20
+ self.destination = destination.dirname + filename
21
+ File.open(destination, 'w') {|f| f << data }
22
+ @local_path = destination
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ module Downlow
2
+ class Local < Fetcher
3
+
4
+ handles(/\w+/)
5
+
6
+ def fetch
7
+ url.cp destination
8
+ @local_path = destination
9
+ end
10
+
11
+ end
12
+ end
13
+
@@ -0,0 +1,13 @@
1
+ HTTP/1.1 302 Found
2
+ Server: nginx/0.7.61
3
+ Date: Sun, 31 Jan 2010 01:32:51 GMT
4
+ Content-Type: text/html; charset=utf-8
5
+ Connection: keep-alive
6
+ Status: 302 Found
7
+ Location: http://github.com/quirkey-lighthouse_stats-e9012c9.tar.gz
8
+ X-Runtime: 75ms
9
+ Content-Length: 133
10
+ Set-Cookie: _github_ses=BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA%3D%3D--884981fc5aa85daf318eeff084d98e2cff92578f; path=/; expires=Wed, 01 Jan 2020 08:00:00 GMT; HttpOnly
11
+ Cache-Control: no-cache
12
+
13
+ <html><body>You are being <a href="http://waitdownload.github.com/quirkey-lighthouse_stats-e9012c9.zip">redirected</a>.</body></html>
Binary file
Binary file
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'fakeweb'
5
+
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ require 'downlow'
9
+
10
+ class Test::Unit::TestCase
11
+
12
+ def tmp_dir
13
+ dir = File.join('/tmp', 'downlow')
14
+ FileUtils.mkdir_p(dir)
15
+ dir
16
+ end
17
+
18
+ def fixture_path(path)
19
+ full_path = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', path))
20
+ end
21
+
22
+ def fixture(path)
23
+ File.read(fixture_path(path))
24
+ end
25
+
26
+ end
@@ -0,0 +1,50 @@
1
+ require 'helper'
2
+
3
+ class TestDownlow < Test::Unit::TestCase
4
+
5
+ context "Downlow" do
6
+ setup do
7
+ FileUtils.rm_rf(tmp_dir) if File.readable?(tmp_dir)
8
+ end
9
+
10
+ context ".get" do
11
+ should "fetch a gzip then extract it" do
12
+ url = 'http://example.org/example.tar.gz'
13
+ FakeWeb.register_uri(:get, url, :body => fixture('test.tar.gz'))
14
+ path = Downlow.get(url, :destination => File.join(tmp_dir, 'final'))
15
+ assert path.is_a?(Pathname)
16
+ assert_match(/final/, path.to_s)
17
+ assert (path + 'test/test.jpg').readable?
18
+ end
19
+
20
+ should "assume string as second arg is destination" do
21
+ url = 'http://example.org/example.tar.gz'
22
+ FakeWeb.register_uri(:get, url, :body => fixture('test.tar.gz'))
23
+ path = Downlow.get(url, File.join(tmp_dir, 'final'))
24
+ assert path.is_a?(Pathname)
25
+ assert_match(/final/, path.to_s)
26
+ assert (path + 'test/test.jpg').readable?
27
+ end
28
+
29
+ should "fetch a single file to a destination" do
30
+ url = 'http://example.org/example.js'
31
+ FakeWeb.register_uri(:get, url, :body => fixture('sammy.git/lib/sammy.js'))
32
+ path = Downlow.get(url, File.join(tmp_dir, 'final'))
33
+ assert path.is_a?(Pathname)
34
+ assert_match(/final/, path.to_s)
35
+ assert path.file?
36
+ end
37
+
38
+ should "move a single file to a destination" do
39
+ path = Downlow.get(fixture_path('sammy.git/lib/sammy.js'), File.join(tmp_dir, 'final'))
40
+ assert path.is_a?(Pathname)
41
+ assert_match(/final/, path.to_s)
42
+ assert path.file?
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+
@@ -0,0 +1,131 @@
1
+ require 'helper'
2
+
3
+ class TestDownlowExtractor < Test::Unit::TestCase
4
+
5
+ context "Downlow::Extractor" do
6
+ setup do
7
+ FileUtils.rm_rf(tmp_dir) if File.readable?(tmp_dir)
8
+ end
9
+
10
+ context ".extract" do
11
+ setup do
12
+ @path = fixture_path('test.zip')
13
+ @final_path = Downlow::Extractor.extract(@path, {:destination => tmp_dir})
14
+ end
15
+
16
+ should "extract the files" do
17
+ assert @final_path.is_a?(Pathname)
18
+ assert @final_path.directory?
19
+ assert_match(/tmp/, @final_path.to_s)
20
+ assert File.readable?(@final_path + 'test/test.jpg')
21
+ end
22
+
23
+ end
24
+
25
+ context ".extractor_for" do
26
+
27
+ should 'determine the fetcher by path' do
28
+ assert_equal Downlow::TarGz, Downlow::Extractor.extractor_for(fixture_path('test.tar.gz'))
29
+ assert_equal Downlow::Zip, Downlow::Extractor.extractor_for(fixture_path('test.zip'))
30
+ end
31
+
32
+ end
33
+
34
+ context "initializing" do
35
+ setup do
36
+ @path = fixture_path('test.zip')
37
+ @extractor = Downlow::Zip.new(@path, {:tmp_dir => tmp_dir})
38
+ end
39
+
40
+ should "set the path" do
41
+ assert_equal @path, @extractor.path.to_s
42
+ end
43
+
44
+ should "set the options" do
45
+ assert_equal tmp_dir, @extractor.options[:tmp_dir]
46
+ end
47
+
48
+ end
49
+
50
+ context "extract" do
51
+
52
+ context "zip" do
53
+ setup do
54
+ @extractor = Downlow::Zip.new(fixture_path('test.zip'), :destination => tmp_dir)
55
+ @path = @extractor.extract
56
+ end
57
+
58
+ should 'extract to the destination' do
59
+ assert @path.is_a?(Pathname)
60
+ assert @path.directory?
61
+ assert_match(/tmp/, @path.to_s)
62
+ assert (@path + 'test/test.jpg').readable?
63
+ end
64
+
65
+ should "set the local path" do
66
+ assert_equal @path, @extractor.final_path
67
+ end
68
+
69
+ end
70
+
71
+
72
+ context "tar.gz" do
73
+ setup do
74
+ @extractor = Downlow::TarGz.new(fixture_path('test.tar.gz'), :destination => tmp_dir)
75
+ @path = @extractor.extract
76
+ end
77
+
78
+ should 'extract to the destination' do
79
+ assert @path.is_a?(Pathname)
80
+ assert @path.directory?
81
+ assert_match(/tmp/, @path.to_s)
82
+ assert (@path + 'test/test.jpg').readable?
83
+ end
84
+
85
+ should "set the final_path" do
86
+ assert_equal @path, @extractor.final_path
87
+ end
88
+ end
89
+
90
+ context "dir" do
91
+ setup do
92
+ @extractor = Downlow::Dir.new(fixture_path(''), :destination => tmp_dir)
93
+ @path = @extractor.extract
94
+ end
95
+
96
+ should 'extract to the destination' do
97
+ assert @path.is_a?(Pathname)
98
+ assert @path.directory?
99
+ assert_match(/tmp/, @path.to_s)
100
+ assert (@path + 'test.tar.gz').readable?
101
+ end
102
+
103
+ should "set the final_path" do
104
+ assert_equal @path, @extractor.final_path
105
+ end
106
+ end
107
+
108
+ context "single file" do
109
+ setup do
110
+ @destination = File.join(tmp_dir, "sammy.js")
111
+ @extractor = Downlow::Dir.new(fixture_path('sammy.git/lib/sammy.js'), :destination => @destination)
112
+ @path = @extractor.extract
113
+ end
114
+
115
+ should 'extract to the exact destination' do
116
+ assert @path.is_a?(Pathname)
117
+ assert @path.file?
118
+ assert_match(/tmp/, @path.to_s)
119
+ assert @path.readable?
120
+ assert_equal @destination, @path.to_s
121
+ end
122
+
123
+ should "set the final_path" do
124
+ assert_equal @path, @extractor.final_path
125
+ end
126
+ end
127
+ end
128
+
129
+ end
130
+
131
+ end
@@ -0,0 +1,161 @@
1
+ require 'helper'
2
+
3
+ class TestDownlowFetcher < Test::Unit::TestCase
4
+
5
+ context "Downlow::Fetcher" do
6
+ setup do
7
+ FileUtils.rm_rf(tmp_dir) if File.readable?(tmp_dir)
8
+ @url = 'http://github.com/quirkey/sammy/'
9
+ FakeWeb.register_uri(:get, @url, :body => 'quirkey.com Sammy')
10
+ end
11
+
12
+ context ".fetch" do
13
+ setup do
14
+ @path = Downlow::Fetcher.fetch(@url, {:tmp_dir => tmp_dir})
15
+ end
16
+
17
+ should "fetch the files" do
18
+ assert @path.is_a?(Pathname)
19
+ assert @path.file?
20
+ assert_match(/tmp/, @path.to_s)
21
+ assert_match(/quirkey/, @path.read)
22
+ end
23
+
24
+ end
25
+
26
+ context ".fetcher_for" do
27
+
28
+ should 'determine the fetcher by URL' do
29
+ assert_equal Downlow::Git, Downlow::Fetcher.fetcher_for('git://github.com/quirkey/sammy.git')
30
+ assert_equal Downlow::Http, Downlow::Fetcher.fetcher_for('http://github.com/quirkey/sammy/')
31
+ end
32
+
33
+ end
34
+
35
+ context "initializing" do
36
+ setup do
37
+ @url = 'http://github.com/quirkey/sammy/'
38
+ @fetcher = Downlow::Http.new(@url, {:tmp_dir => tmp_dir})
39
+ end
40
+
41
+ should "set the url" do
42
+ assert_equal @url, @fetcher.url.to_s
43
+ end
44
+
45
+ should "set the options" do
46
+ assert_equal tmp_dir, @fetcher.options[:tmp_dir]
47
+ end
48
+
49
+ end
50
+
51
+ context "fetch" do
52
+
53
+ context "git" do
54
+ setup do
55
+ class Downlow::Git
56
+ def git_clone
57
+ FileUtils.cp_r(File.join(File.dirname(__FILE__), 'fixtures', 'sammy.git'), destination)
58
+ end
59
+ end
60
+ @fetcher = Downlow::Git.new('git://github.com/quirkey/sammy.git', :tmp_dir => tmp_dir)
61
+ @path = @fetcher.fetch
62
+ end
63
+
64
+ should 'clone a git repo to the temp dir' do
65
+ assert @path.is_a?(Pathname)
66
+ assert @path.directory?
67
+ assert_match(/tmp/, @path.to_s)
68
+ assert (@path + 'lib/sammy.js').readable?
69
+ end
70
+
71
+ should "set the local path" do
72
+ assert_equal @path, @fetcher.local_path
73
+ end
74
+
75
+ end
76
+
77
+ context "http" do
78
+ setup do
79
+ @fetcher = Downlow::Http.new(@url, :tmp_dir => tmp_dir)
80
+ @path = @fetcher.fetch
81
+ end
82
+
83
+ should 'download the file to the temp dir' do
84
+ assert @path.is_a?(Pathname)
85
+ assert @path.file?
86
+ assert_match(/tmp/, @path.to_s)
87
+ assert_match(/quirkey/, @path.read)
88
+ end
89
+
90
+ should "set the local path" do
91
+ assert_equal @path, @fetcher.local_path
92
+ end
93
+
94
+ should "respect content-disposition headers" do
95
+ gist_url = "http://gist.github.com/gists/290151/download"
96
+ FakeWeb.register_uri(:get, gist_url, :response => fixture('gist_response'))
97
+ @fetcher = Downlow::Http.new(gist_url, :tmp_dir => tmp_dir)
98
+ @path = @fetcher.fetch
99
+ assert @path.is_a?(Pathname)
100
+ assert @path.file?
101
+ assert_match(/\.tar\.gz$/, @path.to_s)
102
+ end
103
+
104
+ should "respect location headers" do
105
+ url = "http://github.com/quirkey/lighthouse_stats/tarball/master"
106
+ location_url = "http://github.com/quirkey-lighthouse_stats-e9012c9.tar.gz"
107
+ FakeWeb.register_uri(:get, url, :response => fixture('location_response'))
108
+ FakeWeb.register_uri(:get, location_url, :body => "BODY")
109
+ @fetcher = Downlow::Http.new(url, :tmp_dir => tmp_dir)
110
+ @path = @fetcher.fetch
111
+ assert @path.is_a?(Pathname)
112
+ assert @path.file?
113
+ assert_match(/\.tar\.gz$/, @path.to_s)
114
+ end
115
+
116
+ end
117
+
118
+ context "github" do
119
+
120
+ setup do
121
+ @url = 'gh://quirkey/sammy'
122
+ @real_url = "http://github.com/quirkey/sammy/tarball/master"
123
+ FakeWeb.register_uri(:get, @real_url, :response => fixture('gist_response'))
124
+ @fetcher = Downlow::Github.new(@url, :tmp_dir => tmp_dir)
125
+ @path = @fetcher.fetch
126
+ end
127
+
128
+ should "reset the url to the tarball url" do
129
+ assert_equal @real_url, @fetcher.url
130
+ end
131
+ end
132
+
133
+ context "file" do
134
+ setup do
135
+ @fetcher = Downlow::Local.new(File.join(File.dirname(__FILE__), '..', 'lib'), :tmp_dir => tmp_dir)
136
+ @path = @fetcher.fetch
137
+ end
138
+
139
+ should "move directory into temp dir" do
140
+ assert @path.is_a?(Pathname)
141
+ assert @path.directory?
142
+ assert_match(/tmp/, @path.to_s)
143
+ assert (@path + 'downlow.rb').readable?
144
+ end
145
+
146
+ should "set the local path" do
147
+ assert_equal @path, @fetcher.local_path
148
+ end
149
+
150
+ end
151
+
152
+ context "ftp" do
153
+ should_eventually "download via ftp" do
154
+
155
+ end
156
+ end
157
+ end
158
+
159
+ end
160
+
161
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: downlow
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Aaron Quint
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-31 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rubyzip
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.9.4
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: archive-tar-minitar
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.5.2
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: shoulda
37
+ type: :development
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: fakeweb
47
+ type: :development
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "1.2"
54
+ version:
55
+ description: Downlow provides an easy way to fetch files or archives and extract them with minimal hassle.
56
+ email: aaron@quirkey.com
57
+ executables: []
58
+
59
+ extensions: []
60
+
61
+ extra_rdoc_files:
62
+ - LICENSE
63
+ - README.rdoc
64
+ files:
65
+ - .document
66
+ - .gitignore
67
+ - LICENSE
68
+ - README.rdoc
69
+ - Rakefile
70
+ - downlow.gemspec
71
+ - lib/downlow.rb
72
+ - lib/downlow/ext/pathname.rb
73
+ - lib/downlow/extractor.rb
74
+ - lib/downlow/extractors/dir.rb
75
+ - lib/downlow/extractors/tar_gz.rb
76
+ - lib/downlow/extractors/zip.rb
77
+ - lib/downlow/fetcher.rb
78
+ - lib/downlow/fetchers/git.rb
79
+ - lib/downlow/fetchers/github.rb
80
+ - lib/downlow/fetchers/http.rb
81
+ - lib/downlow/fetchers/local.rb
82
+ - test/fixtures/gist_response
83
+ - test/fixtures/location_response
84
+ - test/fixtures/test.tar.gz
85
+ - test/fixtures/test.zip
86
+ - test/helper.rb
87
+ - test/test_downlow.rb
88
+ - test/test_downlow_extractor.rb
89
+ - test/test_downlow_fetcher.rb
90
+ has_rdoc: true
91
+ homepage: http://github.com/quirkey/downlow
92
+ licenses: []
93
+
94
+ post_install_message:
95
+ rdoc_options:
96
+ - --charset=UTF-8
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: "0"
104
+ version:
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: "0"
110
+ version:
111
+ requirements: []
112
+
113
+ rubyforge_project:
114
+ rubygems_version: 1.3.5
115
+ signing_key:
116
+ specification_version: 3
117
+ summary: easy downloading and extracting API
118
+ test_files:
119
+ - test/helper.rb
120
+ - test/test_downlow.rb
121
+ - test/test_downlow_extractor.rb
122
+ - test/test_downlow_fetcher.rb