ftputils 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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
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
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Bruz Marzolf
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,17 @@
1
+ = ftputils
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (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)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Bruz Marzolf. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,46 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "ftputils"
8
+ gem.summary = %Q{Like FileUtils for FTP}
9
+ gem.description = %Q{Implements a subset of the methods in FileUtils for FTP URIs, falling back on FileUtils when a path doesn't look like FTP}
10
+ gem.email = "bmarzolf@systemsbiology.org"
11
+ gem.homepage = "http://github.com/bmarzolf/ftputils"
12
+ gem.authors = ["Bruz Marzolf"]
13
+ gem.add_dependency "ftpfxp", ">= 0.0.4"
14
+ gem.add_development_dependency "rspec", ">= 1.2.9"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'spec/rake/spectask'
23
+ Spec::Rake::SpecTask.new(:spec) do |spec|
24
+ spec.libs << 'lib' << 'spec'
25
+ spec.spec_files = FileList['spec/**/*_spec.rb']
26
+ end
27
+
28
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.pattern = 'spec/**/*_spec.rb'
31
+ spec.rcov = true
32
+ end
33
+
34
+ task :spec => :check_dependencies
35
+
36
+ task :default => :spec
37
+
38
+ require 'rake/rdoctask'
39
+ Rake::RDocTask.new do |rdoc|
40
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
41
+
42
+ rdoc.rdoc_dir = 'rdoc'
43
+ rdoc.title = "ftputils #{version}"
44
+ rdoc.rdoc_files.include('README*')
45
+ rdoc.rdoc_files.include('lib/**/*.rb')
46
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,48 @@
1
+ class Class
2
+ def cattr_reader(*syms)
3
+ syms.flatten.each do |sym|
4
+ next if sym.is_a?(Hash)
5
+ class_eval(<<-EOS, __FILE__, __LINE__)
6
+ unless defined? @@#{sym}
7
+ @@#{sym} = nil
8
+ end
9
+
10
+ def self.#{sym}
11
+ @@#{sym}
12
+ end
13
+
14
+ def #{sym}
15
+ @@#{sym}
16
+ end
17
+ EOS
18
+ end
19
+ end
20
+
21
+ def cattr_writer(*syms)
22
+ options = syms.last.is_a?(Hash) ? syms.pop : {}
23
+ syms.flatten.each do |sym|
24
+ class_eval(<<-EOS, __FILE__, __LINE__)
25
+ unless defined? @@#{sym}
26
+ @@#{sym} = nil
27
+ end
28
+
29
+ def self.#{sym}=(obj)
30
+ @@#{sym} = obj
31
+ end
32
+
33
+ #{"
34
+ def #{sym}=(obj)
35
+ @@#{sym} = obj
36
+ end
37
+ " unless options[:instance_writer] == false }
38
+ EOS
39
+ end
40
+ end
41
+
42
+ def cattr_accessor(*syms)
43
+ cattr_reader(*syms)
44
+ cattr_writer(*syms)
45
+ end
46
+ end
47
+
48
+
@@ -0,0 +1,50 @@
1
+ class FTPUtils
2
+ class FTPConnection
3
+ cattr_accessor :connections
4
+
5
+ def self.connect(uri)
6
+ if uri.match(/^ftp:\/\/(.*?):(.*?)@(.*?)(\/.*)*$/)
7
+ username = $1
8
+ password = $2
9
+ host = $3
10
+
11
+ connection = self.establish_connection(host, username, password)
12
+
13
+ # need to reset to the top directory since connections are cached and
14
+ # could have been left elsewhere. this also provides a way to see if the
15
+ # connection has expired and needs to be reloaded
16
+ begin
17
+ connection.chdir "/"
18
+ rescue Net::FTPTempError
19
+ connection = self.establish_connection(host, username, password, true)
20
+ retry
21
+ end
22
+
23
+ return connection
24
+ else
25
+ raise "Invalid FTP URL provided: #{uri}"
26
+ end
27
+ end
28
+
29
+ def self.clear_connection_cache
30
+ self.connections = Hash.new
31
+ end
32
+
33
+ private
34
+
35
+ def self.establish_connection(host, username, password, reload = false)
36
+ self.connections ||= Hash.new
37
+
38
+ return connections[host] if connections[host] && !reload
39
+
40
+ connection = Net::FTPFXP.new
41
+ connection.passive = true
42
+ connection.connect(host)
43
+ connection.login(username, password) if username && password
44
+
45
+ connections[host] = connection
46
+
47
+ return connection
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,106 @@
1
+ class FTPUtils
2
+ class FTPFile
3
+
4
+ def self.basename(path, suffix=nil)
5
+ if ftp_uri = FTPUtils::FTPURI.parse(path)
6
+ if suffix
7
+ return ftp_uri.filename.gsub!(/#{suffix}\Z/,'')
8
+ else
9
+ return ftp_uri.filename
10
+ end
11
+ else
12
+ if suffix
13
+ return File.basename(path, suffix)
14
+ else
15
+ return File.basename(path)
16
+ end
17
+ end
18
+ end
19
+
20
+ def self.directory?(path)
21
+ if ftp_uri = FTPUtils::FTPURI.parse(path)
22
+ begin
23
+ connection = FTPUtils::FTPConnection.connect(path)
24
+ connection.chdir(ftp_uri.path)
25
+
26
+ return true
27
+ rescue Net::FTPPermError
28
+ return false
29
+ end
30
+ else
31
+ return File.directory? path
32
+ end
33
+ end
34
+
35
+ def self.dirname(path)
36
+ if ftp_uri = FTPUtils::FTPURI.parse(path)
37
+ return ftp_uri.dirname
38
+ else
39
+ return File.dirname(path)
40
+ end
41
+ end
42
+
43
+ def self.exists?(path)
44
+ if ftp_uri = FTPUtils::FTPURI.parse(path)
45
+ connection = FTPUtils::FTPConnection.connect(path)
46
+ connection.chdir ftp_uri.dirname
47
+ if connection.size(ftp_uri.filename) > 0
48
+ return true
49
+ else
50
+ return false
51
+ end
52
+ else
53
+ return File.exists?(path)
54
+ end
55
+ end
56
+
57
+ def self.expand_path(path)
58
+ if ftp_uri = FTPUtils::FTPURI.parse(path)
59
+ return path
60
+ else
61
+ return File.expand_path(path)
62
+ end
63
+ end
64
+
65
+ def self.file?(path)
66
+ if ftp_uri = FTPUtils::FTPURI.parse(path)
67
+ connection = FTPUtils::FTPConnection.connect(path)
68
+ connection.chdir(ftp_uri.dirname)
69
+
70
+ begin
71
+ connection.size(ftp_uri.filename)
72
+ return true
73
+ rescue Net::FTPPermError
74
+ return false
75
+ end
76
+ else
77
+ return File.file? path
78
+ end
79
+ end
80
+
81
+ def self.mtime(path)
82
+ if ftp_uri = FTPUtils::FTPURI.parse(path)
83
+ connection = FTPUtils::FTPConnection.connect(path)
84
+ connection.chdir(ftp_uri.dirname)
85
+
86
+ return connection.mtime(ftp_uri.filename)
87
+ else
88
+ return File.mtime path
89
+ end
90
+ end
91
+
92
+ def self.relative_path(path)
93
+ if ftp_uri = FTPUtils::FTPURI.parse(path)
94
+ return ftp_uri.path
95
+ else
96
+ return nil
97
+ end
98
+ end
99
+
100
+ private
101
+
102
+ def self.ftp_url?(str)
103
+ str.match(/^ftp:\/\//i) ? true : false
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,25 @@
1
+ class FTPUtils
2
+ class FTPURI
3
+ attr_accessor :dirname, :filename, :path
4
+
5
+ def self.parse(uri)
6
+ if uri.match(/^ftp:\/\/.*?:.*?@.*?(\/.*)*$/)
7
+ path = $1 || "/"
8
+ parts = path.split(/\//)
9
+ filename = parts[-1]
10
+ dirname = parts[0..-2].join("\/")
11
+ dirname = "/" if dirname.empty?
12
+
13
+ ftp_uri = FTPURI.new(dirname, filename, path)
14
+ else
15
+ return nil
16
+ end
17
+ end
18
+
19
+ def initialize(dirname, filename, path)
20
+ self.dirname = dirname
21
+ self.filename = filename
22
+ self.path = path
23
+ end
24
+ end
25
+ end
data/lib/ftputils.rb ADDED
@@ -0,0 +1,152 @@
1
+ begin
2
+ require 'ftpfxp'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require 'ftpfxp'
6
+ end
7
+
8
+ require 'fileutils'
9
+ require 'open-uri'
10
+
11
+ class FTPUtils
12
+ def self.cp(src, dest, options = {})
13
+ # handle all combinations of copying to/from FTP and local files
14
+ case [ftp_url?(src), ftp_url?(dest)]
15
+ when [true, true]
16
+ raise "src should be a filename, not a directory" if FTPFile.directory?(src)
17
+
18
+ dest_path = FTPFile.dirname(dest) + "/" + ( FTPFile.basename(dest) || FTPFile.basename(src) )
19
+ FTPConnection.connect(src).fxpto(FTPConnection.connect(dest), dest_path, FTPFile.relative_path(src))
20
+ when [true, false]
21
+ raise "src should be a filename, not a directory" if FTPFile.directory?(src)
22
+
23
+ filename = FTPFile.basename(src)
24
+
25
+ if File.directory? dest
26
+ dest += "/#{filename}"
27
+ end
28
+
29
+ connection = FTPConnection.connect(src)
30
+ connection.chdir FTPFile.dirname(src)
31
+ connection.getbinaryfile filename, dest, 1024
32
+ when [false, true]
33
+ raise "src should be a filename, not a directory" if File.directory? src
34
+
35
+ dest_path = FTPFile.relative_path(dest)
36
+
37
+ if FTPFile.directory?(dest)
38
+ dest_path += "/#{File.basename(src)}"
39
+ end
40
+
41
+ connection = FTPConnection.connect(dest)
42
+ connection.chdir FTPFile.dirname(dest)
43
+ connection.putbinaryfile src, dest_path, 1024
44
+ when [false, false]
45
+ FileUtils.cp src, dest, options
46
+ end
47
+ end
48
+
49
+ def self.rm(path)
50
+ if ftp_url?(path)
51
+ raise "Can't use FTPUtils.rm on directories. Instead use FTPUtils.rm_r" if FTPFile.directory?(path)
52
+
53
+ connection = FTPConnection.connect(path)
54
+ connection.chdir FTPFile.dirname(path)
55
+ connection.delete FTPFile.basename(path)
56
+ else
57
+ FileUtils.rm path
58
+ end
59
+ end
60
+
61
+ def self.mv(src, dest, options = {})
62
+ cp(src, dest, options)
63
+ rm(src)
64
+ end
65
+
66
+ def self.rm_r(path)
67
+ if ftp_url?(path)
68
+ if FTPFile.directory?(path)
69
+ connection = FTPConnection.connect(path)
70
+ connection.chdir FTPFile.relative_path(path)
71
+
72
+ files = connection.nlst
73
+ files.each {|file| rm_r "#{path}/#{file}"}
74
+
75
+ connection.rmdir FTPFile.relative_path(path)
76
+ else
77
+ rm(path)
78
+ end
79
+ else
80
+ FileUtils.rm_r path
81
+ end
82
+ end
83
+
84
+ def self.mkdir_p(path)
85
+ if ftp_url?(path)
86
+ connection = FTPConnection.connect(path)
87
+
88
+ subdirs = FTPFile.relative_path(path).split(/\//)
89
+ subdirs.each do |subdir|
90
+ next if subdir == ""
91
+
92
+ connection.mkdir subdir
93
+ connection.chdir subdir
94
+ end
95
+ else
96
+ FileUtils.mkdir_p path
97
+ end
98
+ end
99
+
100
+ def self.cp_r(src, dest, options = {})
101
+ # handle all combinations of copying to/from FTP and local files
102
+ if ftp_url?(src)
103
+ if FTPFile.directory?(src)
104
+ mkdir_p dest
105
+
106
+ connection = FTPConnection.connect(src)
107
+ files = connection.nlst
108
+ files.each {|file| cp_r "#{src}/#{file}", "#{dest}/#{file}", options}
109
+ else
110
+ cp(src, dest, options)
111
+ end
112
+ elsif ftp_url?(dest)
113
+ if FTPFile.directory?(dest)
114
+ mkdir_p dest
115
+
116
+ files = Dir.entries(src)
117
+ files.each {|file| cp_r "#{src}/#{file}", "#{dest}/#{file}", options}
118
+ else
119
+ cp(src, dest, options)
120
+ end
121
+ else
122
+ FileUtils.cp_r src, dest
123
+ end
124
+ end
125
+
126
+ def self.ls(path)
127
+ if ftp_url?(path)
128
+ if FTPFile.directory?(path)
129
+ connection = FTPConnection.connect(path)
130
+ connection.chdir FTPFile.relative_path(path)
131
+
132
+ return connection.nlst
133
+ else
134
+ nil
135
+ end
136
+ else
137
+ Dir.entries path
138
+ end
139
+ end
140
+
141
+ private
142
+
143
+ def self.ftp_url?(str)
144
+ str.match(/^ftp:\/\//i) ? true : false
145
+ end
146
+
147
+ end
148
+
149
+ require 'ftputils/ext/class'
150
+ require 'ftputils/ftpconnection'
151
+ require 'ftputils/ftpuri'
152
+ require 'ftputils/ftpfile'
@@ -0,0 +1,56 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe FTPUtils::FTPConnection do
4
+ before(:each) do
5
+ FTPUtils::FTPConnection.clear_connection_cache
6
+ end
7
+
8
+ describe "initializing a new FTP connection" do
9
+ it "should raise an error if it doesn't look like an FTP connection" do
10
+ lambda do
11
+ FTPUtils::FTPConnection.connect("path/to/file.txt")
12
+ end.should raise_error("Invalid FTP URL provided: path/to/file.txt")
13
+ end
14
+
15
+ it "should establish a connection to a directory" do
16
+ mock_connection = mock(Net::FTPFXP)
17
+ Net::FTPFXP.should_receive(:new).and_return(mock_connection)
18
+ mock_connection.should_receive(:"passive=").with(true)
19
+ mock_connection.should_receive(:connect).with("myhost")
20
+ mock_connection.should_receive(:login).with("admin","test")
21
+ mock_connection.should_receive(:chdir).with("/")
22
+
23
+ FTPUtils::FTPConnection.connect("ftp://admin:test@myhost/path/to/directory")
24
+ end
25
+
26
+ it "should establish a connection to a host without a directory name" do
27
+ mock_connection = mock(Net::FTPFXP)
28
+ Net::FTPFXP.should_receive(:new).and_return(mock_connection)
29
+ mock_connection.should_receive(:"passive=").with(true)
30
+ mock_connection.should_receive(:connect).with("myhost")
31
+ mock_connection.should_receive(:login).with("admin","test")
32
+ mock_connection.should_receive(:chdir).with("/")
33
+
34
+ FTPUtils::FTPConnection.connect("ftp://admin:test@myhost")
35
+ end
36
+
37
+ it "should attempt to reconnect once if there is an error" do
38
+ mock_connection = mock(Net::FTPFXP)
39
+ Net::FTPFXP.should_receive(:new).and_return(mock_connection)
40
+ mock_connection.should_receive(:"passive=").with(true)
41
+ mock_connection.should_receive(:connect).with("myhost")
42
+ mock_connection.should_receive(:login).with("admin","test")
43
+ mock_connection.should_receive(:chdir).with("/").and_raise(Net::FTPTempError)
44
+ mock_connection_2 = mock(Net::FTPFXP)
45
+ Net::FTPFXP.should_receive(:new).and_return(mock_connection_2)
46
+ mock_connection_2.should_receive(:"passive=").with(true)
47
+ mock_connection_2.should_receive(:connect).with("myhost")
48
+ mock_connection_2.should_receive(:login).with("admin","test")
49
+ mock_connection_2.should_receive(:chdir).with("/").and_raise(Net::FTPPermError)
50
+
51
+ lambda do
52
+ FTPUtils::FTPConnection.connect("ftp://admin:test@myhost/path/to/file.txt")
53
+ end.should raise_error(Net::FTPPermError)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,177 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe FTPUtils::FTPURI do
4
+ describe "checking to see if a path is a directory" do
5
+ it "should be true when the path is an FTP directory" do
6
+ mock_uri = mock(FTPUtils::FTPURI, :path => "/path/to/directory")
7
+ FTPUtils::FTPURI.should_receive(:parse).with("ftp://admin:test@myhost/path/to/directory").and_return(mock_uri)
8
+ mock_connection = mock(FTPUtils::FTPConnection)
9
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@myhost/path/to/directory").and_return(mock_connection)
10
+ mock_connection.should_receive(:chdir).with("/path/to/directory")
11
+
12
+ FTPUtils::FTPFile.directory?("ftp://admin:test@myhost/path/to/directory").should be_true
13
+ end
14
+
15
+ it "should be false when the path is an FTP file" do
16
+ mock_uri = mock(FTPUtils::FTPURI, :path => "/path/to/file.txt")
17
+ FTPUtils::FTPURI.should_receive(:parse).with("ftp://admin:test@myhost/path/to/file.txt").and_return(mock_uri)
18
+ mock_connection = mock(FTPUtils::FTPConnection)
19
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@myhost/path/to/file.txt").and_return(mock_connection)
20
+ mock_connection.should_receive(:chdir).with("/path/to/file.txt").and_raise(Net::FTPPermError)
21
+
22
+ FTPUtils::FTPFile.directory?("ftp://admin:test@myhost/path/to/file.txt").should be_false
23
+ end
24
+
25
+ it "should check the path using File.directory? if it doesn't look like an FTP URI" do
26
+ FTPUtils::FTPURI.should_receive(:parse).with("/path/to/file.txt").and_return(nil)
27
+ File.should_receive(:directory?).with("/path/to/file.txt")
28
+
29
+ FTPUtils::FTPFile.directory?("/path/to/file.txt")
30
+ end
31
+ end
32
+
33
+ describe "providing the directory name of a path" do
34
+ it "should provide the directory name of an FTP URI" do
35
+ mock_uri = mock(FTPUtils::FTPURI, :dirname => "/path/to")
36
+ FTPUtils::FTPURI.should_receive(:parse).with("ftp://admin:test@myhost/path/to/file.txt").and_return(mock_uri)
37
+
38
+ FTPUtils::FTPFile.dirname("ftp://admin:test@myhost/path/to/file.txt").should == "/path/to"
39
+ end
40
+
41
+ it "should provide the directory name of a non-FTP path" do
42
+ FTPUtils::FTPURI.should_receive(:parse).with("/path/to/file.txt").and_return(nil)
43
+ File.should_receive(:dirname).with("/path/to/file.txt").and_return("/path/to")
44
+
45
+ FTPUtils::FTPFile.dirname("/path/to/file.txt").should == "/path/to"
46
+ end
47
+ end
48
+
49
+ describe "checking to see if a file exists" do
50
+ it "should be true when an FTP URI has size greater than 0" do
51
+ mock_uri = mock(FTPUtils::FTPURI, :path => "/path/to/file.txt", :dirname => "/path/to", :filename => "file.txt")
52
+ FTPUtils::FTPURI.should_receive(:parse).with("ftp://admin:test@myhost/path/to/file.txt").and_return(mock_uri)
53
+ mock_connection = mock(FTPUtils::FTPConnection)
54
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@myhost/path/to/file.txt").and_return(mock_connection)
55
+ mock_connection.should_receive(:chdir).with("/path/to")
56
+ mock_connection.should_receive(:size).with("file.txt").and_return(100)
57
+
58
+ FTPUtils::FTPFile.exists?("ftp://admin:test@myhost/path/to/file.txt").should be_true
59
+ end
60
+
61
+ it "should be false when an FTP URI has size less than 0" do
62
+ mock_uri = mock(FTPUtils::FTPURI, :path => "/path/to/file.txt", :dirname => "/path/to", :filename => "file.txt")
63
+ FTPUtils::FTPURI.should_receive(:parse).with("ftp://admin:test@myhost/path/to/file.txt").and_return(mock_uri)
64
+ mock_connection = mock(FTPUtils::FTPConnection)
65
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@myhost/path/to/file.txt").and_return(mock_connection)
66
+ mock_connection.should_receive(:chdir).with("/path/to")
67
+ mock_connection.should_receive(:size).with("file.txt").and_return(-1)
68
+
69
+ FTPUtils::FTPFile.exists?("ftp://admin:test@myhost/path/to/file.txt").should be_false
70
+ end
71
+
72
+ it "should pass the path to File.exists? if it's not an FTP URI" do
73
+ FTPUtils::FTPURI.should_receive(:parse).with("/path/to/file.txt").and_return(nil)
74
+ File.should_receive(:exists?)
75
+
76
+ FTPUtils::FTPFile.exists?("/path/to/file.txt")
77
+ end
78
+ end
79
+
80
+ describe "providing the basename" do
81
+ it "should provide the basename of an FTP URI" do
82
+ mock_uri = mock(FTPUtils::FTPURI, :filename => "file.txt")
83
+ FTPUtils::FTPURI.should_receive(:parse).with("ftp://admin:test@myhost/path/to/file.txt").and_return(mock_uri)
84
+
85
+ FTPUtils::FTPFile.basename("ftp://admin:test@myhost/path/to/file.txt").should == "file.txt"
86
+ end
87
+
88
+ it "should provide the basename of a non-FTP path" do
89
+ FTPUtils::FTPURI.should_receive(:parse).with("/path/to/file.txt").and_return(nil)
90
+ File.should_receive(:basename).with("/path/to/file.txt")
91
+
92
+ FTPUtils::FTPFile.basename("/path/to/file.txt")
93
+ end
94
+ end
95
+
96
+ describe "providing the relative path" do
97
+ it "should provide the relative path for an FTP URI" do
98
+ mock_uri = mock(FTPUtils::FTPURI, :path => "/path/to")
99
+ FTPUtils::FTPURI.should_receive(:parse).with("ftp://admin:test@myhost/path/to/file.txt").and_return(mock_uri)
100
+
101
+ FTPUtils::FTPFile.relative_path("ftp://admin:test@myhost/path/to/file.txt").should == "/path/to"
102
+ end
103
+
104
+ it "should provide the relative path for a non-FTP path" do
105
+ FTPUtils::FTPURI.should_receive(:parse).with("/path/to/file.txt").and_return(nil)
106
+
107
+ FTPUtils::FTPFile.relative_path("/path/to/file.txt").should be_nil
108
+ end
109
+ end
110
+
111
+ describe "determining whether a path is a file" do
112
+ it "should be true for an FTP URI of a file" do
113
+ mock_uri = mock(FTPUtils::FTPURI, :dirname => "/path/to", :filename => "file.txt")
114
+ FTPUtils::FTPURI.should_receive(:parse).with("ftp://admin:test@myhost/path/to/file.txt").and_return(mock_uri)
115
+ mock_connection = mock(FTPUtils::FTPConnection)
116
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@myhost/path/to/file.txt").and_return(mock_connection)
117
+ mock_connection.should_receive(:chdir).with("/path/to")
118
+ mock_connection.should_receive(:size).with("file.txt").and_return(100)
119
+
120
+ FTPUtils::FTPFile.file?("ftp://admin:test@myhost/path/to/file.txt").should be_true
121
+ end
122
+
123
+ it "should be false for an FTP URI of a directory" do
124
+ mock_uri = mock(FTPUtils::FTPURI, :dirname => "/path/to", :filename => "directory")
125
+ FTPUtils::FTPURI.should_receive(:parse).with("ftp://admin:test@myhost/path/to/directory").and_return(mock_uri)
126
+ mock_connection = mock(FTPUtils::FTPConnection)
127
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@myhost/path/to/directory").and_return(mock_connection)
128
+ mock_connection.should_receive(:chdir).with("/path/to")
129
+ mock_connection.should_receive(:size).with("directory").and_raise(Net::FTPPermError)
130
+
131
+ FTPUtils::FTPFile.file?("ftp://admin:test@myhost/path/to/directory").should be_false
132
+ end
133
+
134
+ it "should use File.directory? if the path doesn't look like an FTP URI" do
135
+ FTPUtils::FTPURI.should_receive(:parse).with("/path/to/file.txt").and_return(nil)
136
+ File.should_receive(:file?).with("/path/to/file.txt")
137
+
138
+ FTPUtils::FTPFile.file?("/path/to/file.txt")
139
+ end
140
+ end
141
+
142
+ describe "determining the modification time of a file" do
143
+ it "should find the modification of an FTP URI" do
144
+ mock_uri = mock(FTPUtils::FTPURI, :dirname => "/path/to", :filename => "file.txt")
145
+ FTPUtils::FTPURI.should_receive(:parse).with("ftp://admin:test@myhost/path/to/file.txt").and_return(mock_uri)
146
+ mock_connection = mock(FTPUtils::FTPConnection)
147
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@myhost/path/to/file.txt").and_return(mock_connection)
148
+ mock_connection.should_receive(:chdir).with("/path/to")
149
+ mock_connection.should_receive(:mtime).with("file.txt").and_return("10:10")
150
+
151
+ FTPUtils::FTPFile.mtime("ftp://admin:test@myhost/path/to/file.txt").should == "10:10"
152
+ end
153
+
154
+ it "should use File.mtime if the path is not an FTP URI" do
155
+ FTPUtils::FTPURI.should_receive(:parse).with("/path/to/file.txt").and_return(nil)
156
+ File.should_receive(:mtime).with("/path/to/file.txt")
157
+
158
+ FTPUtils::FTPFile.mtime("/path/to/file.txt")
159
+ end
160
+ end
161
+
162
+ describe "expanding the path" do
163
+ it "should do nothing to the path if it's an FTP URI" do
164
+ mock_uri = mock(FTPUtils::FTPURI, :dirname => "/path/to", :filename => "file.txt")
165
+ FTPUtils::FTPURI.should_receive(:parse).with("ftp://admin:test@myhost/path/to/file.txt").and_return(mock_uri)
166
+
167
+ FTPUtils::FTPFile.expand_path("ftp://admin:test@myhost/path/to/file.txt").should == "ftp://admin:test@myhost/path/to/file.txt"
168
+ end
169
+
170
+ it "should use File.expand_path if it's not an FTP URI" do
171
+ FTPUtils::FTPURI.should_receive(:parse).with("/path/to/file.txt").and_return(nil)
172
+ File.should_receive(:expand_path).with("/path/to/file.txt")
173
+
174
+ FTPUtils::FTPFile.expand_path("/path/to/file.txt")
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,25 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe FTPUtils::FTPURI do
4
+ it "should create a new URI if a valid FTP directory is provided" do
5
+ uri = FTPUtils::FTPURI.parse("ftp://admin:test@myhost/path/to/directory")
6
+ uri.dirname.should == "/path/to"
7
+ uri.filename.should == "directory"
8
+ uri.path.should == "/path/to/directory"
9
+ end
10
+
11
+ it "should create a new URI if just the hostname is provided" do
12
+ uri = FTPUtils::FTPURI.parse("ftp://admin:test@myhost")
13
+ uri.dirname.should == "/"
14
+ uri.filename.should be_nil
15
+ uri.path.should == "/"
16
+ end
17
+
18
+ it "should create a new URI if a valid FTP file is provided" do
19
+ uri = FTPUtils::FTPURI.parse("ftp://admin:test@myhost/path/to/file.txt")
20
+ uri.dirname.should == "/path/to"
21
+ uri.filename.should == "file.txt"
22
+ uri.path.should == "/path/to/file.txt"
23
+ end
24
+
25
+ end
@@ -0,0 +1,387 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "FTPUtils" do
4
+ describe "copying a file" do
5
+ describe "from FTP to FTP" do
6
+ it "should work if a file URIs are specified for the source and destination" do
7
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/file1.txt").
8
+ and_return(false)
9
+ FTPUtils::FTPFile.should_receive(:dirname).with("ftp://admin:test@host2/file2.txt").
10
+ and_return("/")
11
+ FTPUtils::FTPFile.should_receive(:basename).with("ftp://admin:test@host2/file2.txt").
12
+ and_return("file2.txt")
13
+
14
+ mock_src_connection = mock(FTPUtils::FTPConnection)
15
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/file1.txt").
16
+ and_return(mock_src_connection)
17
+ mock_dest_connection = mock(FTPUtils::FTPConnection)
18
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host2/file2.txt").
19
+ and_return(mock_dest_connection)
20
+ FTPUtils::FTPFile.should_receive(:relative_path).with("ftp://admin:test@host1/file1.txt").
21
+ and_return("/file1.txt")
22
+ mock_src_connection.should_receive(:fxpto).with(mock_dest_connection, "//file2.txt", "/file1.txt")
23
+
24
+ FTPUtils.cp "ftp://admin:test@host1/file1.txt", "ftp://admin:test@host2/file2.txt"
25
+ end
26
+
27
+ it "should work if a file URI with subdirnames are specified for the source and destination" do
28
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1/file1.txt").
29
+ and_return(false)
30
+ FTPUtils::FTPFile.should_receive(:dirname).with("ftp://admin:test@host2/subdir2/file2.txt").
31
+ and_return("/subdir2")
32
+ FTPUtils::FTPFile.should_receive(:basename).with("ftp://admin:test@host2/subdir2/file2.txt").
33
+ and_return("file2.txt")
34
+
35
+ mock_src_connection = mock(FTPUtils::FTPConnection)
36
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/subdir1/file1.txt").
37
+ and_return(mock_src_connection)
38
+ mock_dest_connection = mock(FTPUtils::FTPConnection)
39
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host2/subdir2/file2.txt").
40
+ and_return(mock_dest_connection)
41
+ FTPUtils::FTPFile.should_receive(:relative_path).with("ftp://admin:test@host1/subdir1/file1.txt").
42
+ and_return("/subdir1/file1.txt")
43
+ mock_src_connection.should_receive(:fxpto).with(mock_dest_connection, "/subdir2/file2.txt", "/subdir1/file1.txt")
44
+
45
+ FTPUtils.cp "ftp://admin:test@host1/subdir1/file1.txt", "ftp://admin:test@host2/subdir2/file2.txt"
46
+ end
47
+
48
+ it "should work if a file URI is specified for the source and a directory for the destination" do
49
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1/file1.txt").
50
+ and_return(false)
51
+ FTPUtils::FTPFile.should_receive(:dirname).with("ftp://admin:test@host2/subdir2").
52
+ and_return("/subdir2")
53
+ FTPUtils::FTPFile.should_receive(:basename).with("ftp://admin:test@host2/subdir2").
54
+ and_return(nil)
55
+ FTPUtils::FTPFile.should_receive(:basename).with("ftp://admin:test@host1/subdir1/file1.txt").
56
+ and_return("file1.txt")
57
+
58
+ mock_src_connection = mock(FTPUtils::FTPConnection)
59
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/subdir1/file1.txt").
60
+ and_return(mock_src_connection)
61
+ mock_dest_connection = mock(FTPUtils::FTPConnection)
62
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host2/subdir2").
63
+ and_return(mock_dest_connection)
64
+ FTPUtils::FTPFile.should_receive(:relative_path).with("ftp://admin:test@host1/subdir1/file1.txt").
65
+ and_return("/subdir1/file1.txt")
66
+ mock_src_connection.should_receive(:fxpto).with(mock_dest_connection, "/subdir2/file1.txt", "/subdir1/file1.txt")
67
+
68
+ FTPUtils.cp "ftp://admin:test@host1/subdir1/file1.txt", "ftp://admin:test@host2/subdir2"
69
+ end
70
+
71
+ it "should raise an error if the source URI is a directory" do
72
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1").
73
+ and_return(true)
74
+
75
+ lambda do
76
+ FTPUtils.cp "ftp://admin:test@host1/subdir1", "ftp://admin:test@host2/subdir2"
77
+ end.should raise_error("src should be a filename, not a directory")
78
+ end
79
+ end
80
+
81
+ describe "from FTP to the local filesystem" do
82
+ it "should work if the destination filename is given" do
83
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1/file1.txt").
84
+ and_return(false)
85
+
86
+ FTPUtils::FTPFile.should_receive(:basename).with("ftp://admin:test@host1/subdir1/file1.txt").
87
+ and_return("file1.txt")
88
+
89
+ File.should_receive(:directory?).with("/home/me/file2.txt").and_return(false)
90
+
91
+ mock_src_connection = mock(FTPUtils::FTPConnection)
92
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/subdir1/file1.txt").
93
+ and_return(mock_src_connection)
94
+ FTPUtils::FTPFile.should_receive(:dirname).with("ftp://admin:test@host1/subdir1/file1.txt").
95
+ and_return("/subdir1")
96
+ mock_src_connection.should_receive(:chdir).with("/subdir1")
97
+ mock_src_connection.should_receive(:getbinaryfile).with("file1.txt", "/home/me/file2.txt", 1024)
98
+
99
+ FTPUtils.cp "ftp://admin:test@host1/subdir1/file1.txt", "/home/me/file2.txt"
100
+ end
101
+
102
+ it "should work if the destination directory is given" do
103
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1/file1.txt").
104
+ and_return(false)
105
+
106
+ FTPUtils::FTPFile.should_receive(:basename).with("ftp://admin:test@host1/subdir1/file1.txt").
107
+ and_return("file1.txt")
108
+
109
+ File.should_receive(:directory?).with("/home/me").and_return(true)
110
+
111
+ mock_src_connection = mock(FTPUtils::FTPConnection)
112
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/subdir1/file1.txt").
113
+ and_return(mock_src_connection)
114
+ FTPUtils::FTPFile.should_receive(:dirname).with("ftp://admin:test@host1/subdir1/file1.txt").
115
+ and_return("/subdir1")
116
+ mock_src_connection.should_receive(:chdir).with("/subdir1")
117
+ mock_src_connection.should_receive(:getbinaryfile).with("file1.txt", "/home/me/file1.txt", 1024)
118
+
119
+ FTPUtils.cp "ftp://admin:test@host1/subdir1/file1.txt", "/home/me"
120
+ end
121
+
122
+ it "should raise an error if the source URI is a directory" do
123
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1").
124
+ and_return(true)
125
+
126
+ lambda do
127
+ FTPUtils.cp "ftp://admin:test@host1/subdir1", "/home/me"
128
+ end.should raise_error("src should be a filename, not a directory")
129
+ end
130
+ end
131
+
132
+ describe "from local filesystem to FTP" do
133
+ it "should work if the source is a file and the destination is a file" do
134
+ File.should_receive(:directory?).with("/home/me/file1.txt").and_return(false)
135
+ FTPUtils::FTPFile.should_receive(:relative_path).with("ftp://admin:test@host2/file2.txt").
136
+ and_return("/file2.txt")
137
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host2/file2.txt").
138
+ and_return(false)
139
+ mock_dest_connection = mock(FTPUtils::FTPConnection)
140
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host2/file2.txt").
141
+ and_return(mock_dest_connection)
142
+ FTPUtils::FTPFile.should_receive(:dirname).with("ftp://admin:test@host2/file2.txt").and_return("/")
143
+ mock_dest_connection.should_receive(:chdir).with("/")
144
+ mock_dest_connection.should_receive(:putbinaryfile).with("/home/me/file1.txt", "/file2.txt", 1024)
145
+
146
+ FTPUtils.cp "/home/me/file1.txt", "ftp://admin:test@host2/file2.txt"
147
+ end
148
+
149
+ it "should work if the source is a file and the destination is a directory" do
150
+ File.should_receive(:directory?).with("/home/me/file1.txt").and_return(false)
151
+ FTPUtils::FTPFile.should_receive(:relative_path).with("ftp://admin:test@host2").
152
+ and_return("")
153
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host2").
154
+ and_return(true)
155
+ File.should_receive(:basename).with("/home/me/file1.txt").and_return("file1.txt")
156
+ mock_dest_connection = mock(FTPUtils::FTPConnection)
157
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host2").
158
+ and_return(mock_dest_connection)
159
+ FTPUtils::FTPFile.should_receive(:dirname).with("ftp://admin:test@host2").and_return("/")
160
+ mock_dest_connection.should_receive(:chdir).with("/")
161
+ mock_dest_connection.should_receive(:putbinaryfile).with("/home/me/file1.txt", "/file1.txt", 1024)
162
+
163
+ FTPUtils.cp "/home/me/file1.txt", "ftp://admin:test@host2"
164
+ end
165
+
166
+ it "should raise an error if the source file is a directory" do
167
+ File.should_receive(:directory?).with("/home/me").and_return(true)
168
+
169
+ lambda do
170
+ FTPUtils.cp "/home/me", "ftp://admin:test@host1/subdir1"
171
+ end.should raise_error("src should be a filename, not a directory")
172
+ end
173
+ end
174
+
175
+ describe "from local filesystem to local filesystem" do
176
+ it "should fall back on FileUtils.cp" do
177
+ FileUtils.should_receive(:cp).with("file1.txt", "file2.txt", {})
178
+
179
+ FTPUtils.cp "file1.txt", "file2.txt"
180
+ end
181
+ end
182
+ end
183
+
184
+ describe "removing a file" do
185
+ it "should remove an FTP directory" do
186
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1/file.txt").
187
+ and_return(false)
188
+ mock_connection = mock(FTPUtils::FTPConnection)
189
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/subdir1/file.txt").
190
+ and_return(mock_connection)
191
+ FTPUtils::FTPFile.should_receive(:dirname).with("ftp://admin:test@host1/subdir1/file.txt").and_return("/subdir1")
192
+ mock_connection.should_receive(:chdir).with("/subdir1")
193
+ mock_connection.should_receive(:delete).with("file.txt")
194
+
195
+ FTPUtils.rm "ftp://admin:test@host1/subdir1/file.txt"
196
+ end
197
+
198
+ it "should raise an error if an FTP directory is provided" do
199
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1").
200
+ and_return(true)
201
+
202
+ lambda do
203
+ FTPUtils.rm "ftp://admin:test@host1/subdir1"
204
+ end.should raise_error("Can't use FTPUtils.rm on directories. Instead use FTPUtils.rm_r")
205
+ end
206
+
207
+ it "should fall back on FileUtils.rm if an FTP URI is not provided" do
208
+ FileUtils.should_receive(:rm).with("file.txt")
209
+
210
+ FTPUtils.rm "file.txt"
211
+ end
212
+ end
213
+
214
+ describe "removing recursively" do
215
+ it "should remove a directory with nested dirnames and files" do
216
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1").
217
+ and_return(true)
218
+ mock_connection = mock(FTPUtils::FTPConnection)
219
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/subdir1").
220
+ and_return(mock_connection)
221
+ mock_connection.should_receive(:chdir).with("/subdir1")
222
+ mock_connection.should_receive(:nlst).and_return( ["subdir2", "file.txt"] )
223
+
224
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1/subdir2").
225
+ and_return(true)
226
+ mock_subdir_connection = mock(FTPUtils::FTPConnection)
227
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/subdir1/subdir2").
228
+ and_return(mock_subdir_connection)
229
+ mock_subdir_connection.should_receive(:chdir).with("/subdir1/subdir2")
230
+ mock_subdir_connection.should_receive(:nlst).and_return( [] )
231
+ FTPUtils::FTPFile.should_receive(:relative_path).with("ftp://admin:test@host1/subdir1/subdir2").twice.
232
+ and_return("/subdir1/subdir2")
233
+ mock_subdir_connection.should_receive(:rmdir).with("/subdir1/subdir2")
234
+
235
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1/file.txt").
236
+ and_return(false)
237
+ FTPUtils.should_receive(:rm).with("ftp://admin:test@host1/subdir1/file.txt")
238
+
239
+ FTPUtils::FTPFile.should_receive(:relative_path).with("ftp://admin:test@host1/subdir1").twice.
240
+ and_return("/subdir1")
241
+ mock_connection.should_receive(:rmdir).with("/subdir1")
242
+
243
+ FTPUtils.rm_r "ftp://admin:test@host1/subdir1"
244
+ end
245
+
246
+ it "should remove a file" do
247
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1/file.txt").
248
+ and_return(false)
249
+ FTPUtils.should_receive(:rm).with("ftp://admin:test@host1/subdir1/file.txt")
250
+
251
+ FTPUtils.rm_r "ftp://admin:test@host1/subdir1/file.txt"
252
+ end
253
+
254
+ it "should fall back on FileUtils.rm_r if a non-FTP URI is provided" do
255
+ FileUtils.should_receive(:rm_r).with("dir")
256
+
257
+ FTPUtils.rm_r "dir"
258
+ end
259
+ end
260
+
261
+ describe "create a directory and all its parents" do
262
+ it "should work on an FTP URI" do
263
+ mock_connection = mock(FTPUtils::FTPConnection)
264
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/subdir1/subdir2").
265
+ and_return(mock_connection)
266
+
267
+ FTPUtils::FTPFile.should_receive(:relative_path).with("ftp://admin:test@host1/subdir1/subdir2").
268
+ and_return("/subdir1/subdir2")
269
+
270
+ mock_connection.should_receive(:mkdir).with("subdir1")
271
+ mock_connection.should_receive(:chdir).with("subdir1")
272
+ mock_connection.should_receive(:mkdir).with("subdir2")
273
+ mock_connection.should_receive(:chdir).with("subdir2")
274
+
275
+ FTPUtils.mkdir_p "ftp://admin:test@host1/subdir1/subdir2"
276
+ end
277
+
278
+ it "should fall back on FileUtils for a non-FTP URI" do
279
+ FileUtils.should_receive(:mkdir_p).with("subdir1/subdir2")
280
+
281
+ FTPUtils.mkdir_p("subdir1/subdir2")
282
+ end
283
+ end
284
+
285
+ describe "copying recursively" do
286
+ it "should copy an FTP directory to another FTP directory" do
287
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1").
288
+ and_return(true)
289
+ FTPUtils.should_receive(:mkdir_p).with("ftp://admin:test@host2/subdir2")
290
+ mock_src_connection = mock(FTPUtils::FTPConnection)
291
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/subdir1").
292
+ and_return(mock_src_connection)
293
+ mock_src_connection.should_receive(:nlst).and_return( ["subdira", "file.txt"] )
294
+
295
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1/subdira").
296
+ and_return(true)
297
+ FTPUtils.should_receive(:mkdir_p).with("ftp://admin:test@host2/subdir2/subdira")
298
+ mock_subdira_connection = mock(FTPUtils::FTPConnection)
299
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/subdir1/subdira").
300
+ and_return(mock_subdira_connection)
301
+ mock_subdira_connection.should_receive(:nlst).and_return([])
302
+
303
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1/file.txt").
304
+ and_return(false)
305
+ FTPUtils.should_receive(:cp).with("ftp://admin:test@host1/subdir1/file.txt",
306
+ "ftp://admin:test@host2/subdir2/file.txt", {})
307
+
308
+ FTPUtils.cp_r "ftp://admin:test@host1/subdir1", "ftp://admin:test@host2/subdir2"
309
+ end
310
+
311
+ it "should copy an FTP directory to a local directory" do
312
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1").
313
+ and_return(true)
314
+ FTPUtils.should_receive(:mkdir_p).with("/home/me/subdir2")
315
+ mock_src_connection = mock(FTPUtils::FTPConnection)
316
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/subdir1").
317
+ and_return(mock_src_connection)
318
+ mock_src_connection.should_receive(:nlst).and_return( ["subdira", "file.txt"] )
319
+
320
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1/subdira").
321
+ and_return(true)
322
+ FTPUtils.should_receive(:mkdir_p).with("/home/me/subdir2/subdira")
323
+ mock_subdira_connection = mock(FTPUtils::FTPConnection)
324
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/subdir1/subdira").
325
+ and_return(mock_subdira_connection)
326
+ mock_subdira_connection.should_receive(:nlst).and_return([])
327
+
328
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1/file.txt").
329
+ and_return(false)
330
+ FTPUtils.should_receive(:cp).with("ftp://admin:test@host1/subdir1/file.txt",
331
+ "/home/me/subdir2/file.txt", {})
332
+
333
+ FTPUtils.cp_r "ftp://admin:test@host1/subdir1", "/home/me/subdir2"
334
+ end
335
+
336
+ it "should copy a local directory to an FTP directory" do
337
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host2/subdir2").
338
+ and_return(true)
339
+ FTPUtils.should_receive(:mkdir_p).with("ftp://admin:test@host2/subdir2")
340
+ Dir.should_receive(:entries).with("/home/me/subdir1").and_return( ["subdira", "file.txt"] )
341
+
342
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host2/subdir2/subdira").
343
+ and_return(true)
344
+ FTPUtils.should_receive(:mkdir_p).with("ftp://admin:test@host2/subdir2/subdira")
345
+ Dir.should_receive(:entries).with("/home/me/subdir1/subdira").and_return([])
346
+
347
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host2/subdir2/file.txt").
348
+ and_return(false)
349
+ FTPUtils.should_receive(:cp).with("/home/me/subdir1/file.txt", "ftp://admin:test@host2/subdir2/file.txt", {})
350
+
351
+ FTPUtils.cp_r "/home/me/subdir1", "ftp://admin:test@host2/subdir2"
352
+ end
353
+
354
+ it "should copy a local directory to another local directory" do
355
+ FileUtils.should_receive(:cp_r).with("/home/me/subdir1", "/home/me/subdir2")
356
+
357
+ FTPUtils.cp_r "/home/me/subdir1", "/home/me/subdir2"
358
+ end
359
+ end
360
+
361
+ describe "listing the entries in a directory" do
362
+ it "should return a list of entries in an FTP directory" do
363
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1").
364
+ and_return(true)
365
+ mock_connection = mock(FTPUtils::FTPConnection)
366
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/subdir1").
367
+ and_return(mock_connection)
368
+ mock_connection.should_receive(:chdir).with("/subdir1")
369
+ mock_connection.should_receive(:nlst).and_return( ["subdir2", "file.txt"] )
370
+
371
+ FTPUtils.ls("ftp://admin:test@host1/subdir1").should == ["subdir2", "file.txt"]
372
+ end
373
+
374
+ it "should return nil for an FTP URI that isn't a directory" do
375
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/file.txt").
376
+ and_return(false)
377
+
378
+ FTPUtils.ls("ftp://admin:test@host1/file.txt").should == nil
379
+ end
380
+
381
+ it "should use Dir.entries for a non-FTP URI" do
382
+ Dir.should_receive(:entries).with("/home/me/subdir1")
383
+
384
+ FTPUtils.ls("/home/me/subdir1")
385
+ end
386
+ end
387
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'ftputils'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ftputils
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Bruz Marzolf
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-05-25 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: ftpfxp
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ - 0
30
+ - 4
31
+ version: 0.0.4
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rspec
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 1
43
+ - 2
44
+ - 9
45
+ version: 1.2.9
46
+ type: :development
47
+ version_requirements: *id002
48
+ description: Implements a subset of the methods in FileUtils for FTP URIs, falling back on FileUtils when a path doesn't look like FTP
49
+ email: bmarzolf@systemsbiology.org
50
+ executables: []
51
+
52
+ extensions: []
53
+
54
+ extra_rdoc_files:
55
+ - LICENSE
56
+ - README.rdoc
57
+ files:
58
+ - .document
59
+ - .gitignore
60
+ - LICENSE
61
+ - README.rdoc
62
+ - Rakefile
63
+ - VERSION
64
+ - lib/ftputils.rb
65
+ - lib/ftputils/ext/class.rb
66
+ - lib/ftputils/ftpconnection.rb
67
+ - lib/ftputils/ftpfile.rb
68
+ - lib/ftputils/ftpuri.rb
69
+ - spec/ftpconnection_spec.rb
70
+ - spec/ftpfile_spec.rb
71
+ - spec/ftpuri_spec.rb
72
+ - spec/ftputils_spec.rb
73
+ - spec/spec.opts
74
+ - spec/spec_helper.rb
75
+ has_rdoc: true
76
+ homepage: http://github.com/bmarzolf/ftputils
77
+ licenses: []
78
+
79
+ post_install_message:
80
+ rdoc_options:
81
+ - --charset=UTF-8
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ requirements: []
99
+
100
+ rubyforge_project:
101
+ rubygems_version: 1.3.6
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: Like FileUtils for FTP
105
+ test_files:
106
+ - spec/ftpconnection_spec.rb
107
+ - spec/ftpfile_spec.rb
108
+ - spec/ftpuri_spec.rb
109
+ - spec/ftputils_spec.rb
110
+ - spec/spec_helper.rb