downlow 0.1.0

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.
@@ -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