ftp-ext 0.1.1 → 0.1.3
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 +1 -1
- data/LICENSE +1 -1
- data/README.mdown +66 -0
- data/ftp-ext.gemspec +8 -9
- data/lib/ftp-ext.rb +125 -34
- data/test/test_ftp-ext.rb +17 -3
- metadata +7 -8
- data/README.rdoc +0 -22
data/.document
CHANGED
data/LICENSE
CHANGED
data/README.mdown
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
FTPExt
|
2
|
+
======
|
3
|
+
A simple gem for copying, synching, and deleting entire folders over FTP. Adds
|
4
|
+
two new public methods to Net::FTP — <code>put\_dir</code> and
|
5
|
+
<code>rmrf\_dir</code>. <code>put\_dir</code> takes a hash of arguments:
|
6
|
+
|
7
|
+
* local - (_string_) The relative or absolute path to the local directory to
|
8
|
+
be copied.
|
9
|
+
* remote - (_string_) The relative or absolute remote path that the local
|
10
|
+
directory will be copied to.
|
11
|
+
* verbose - (_bool_: defaults to false) Will output progress if set to true.
|
12
|
+
* erase - (_bool_: defaults to false) If set to true, <code>put_dir</code>
|
13
|
+
will overwrite any existing directories (default behavior is to update
|
14
|
+
changed files only).
|
15
|
+
* exclude - (_array_) A list of any files in local directory to be excluded.
|
16
|
+
All file paths in <code>exclude</code> should be relative to <code>local
|
17
|
+
</code>
|
18
|
+
|
19
|
+
<code>rmrf\_dir</code> takes two arguments: the path to the remote directory
|
20
|
+
to be deleted, and a verbose boolean flag (defaulted to false).
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
To use FTPExt, require it and use Net::FTP as normal. Because FTPExt requires
|
24
|
+
Net::FTP for you, you don't need to include it separately.
|
25
|
+
|
26
|
+
require 'rubygems'
|
27
|
+
require 'ftp-ext'
|
28
|
+
|
29
|
+
ftp = Net::FTP.new('ftp/server/address')
|
30
|
+
ftp.login('username', 'password')
|
31
|
+
|
32
|
+
ftp.put_dir(:local => 'path/to/local', :remote => 'path/to/remote')
|
33
|
+
|
34
|
+
For example, imagine I have a local directory,
|
35
|
+
<code>/Users/zpendleton/code</code>, that I want to upload on my server to the
|
36
|
+
<code>/home/zpendleton</code> directory. I'd run the following command:
|
37
|
+
|
38
|
+
ftp.put_dir('/Users/zpendleton/code', '/home/zpendleton/code')
|
39
|
+
|
40
|
+
If the <code>code</code> directory already exists in
|
41
|
+
<code>/home/zpendleton</code>, it will be updated (the script checks the
|
42
|
+
last modified timestamps of all files). If the <code>code</code> directory
|
43
|
+
doesn't exist, it will be created. To overwrite the <code>code</code>
|
44
|
+
directory on the server, set the <code>:erase</code> option in
|
45
|
+
<code>put\_dir</code> to <code>true</code>.
|
46
|
+
|
47
|
+
To later delete the <code>/home/zpendleton/code</code> directory (in verbose
|
48
|
+
mode), you would use:
|
49
|
+
|
50
|
+
ftp.rmrf_dir('/home/zpendleton/code', true)
|
51
|
+
|
52
|
+
To leave verbose mode turned off, you would just omit the second parameter.
|
53
|
+
|
54
|
+
## Contributing
|
55
|
+
1. Fork the project.
|
56
|
+
2. Create a branch (<code>git checkout -b my_edit</code>).
|
57
|
+
3. Commit your changes (<code>git commit -am "Added feature"</code>).
|
58
|
+
4. Push to the branch (<code>git push origin my_edit</code>).
|
59
|
+
5. Create an [issue](http://github.com/zpendleton/ftp-ext/issues) with a link
|
60
|
+
to your branch.
|
61
|
+
6. Kick it old school to [this](http://www.youtube.com/watch?v=jEa1BYBgeQI)
|
62
|
+
while you wait.
|
63
|
+
|
64
|
+
## Copyright
|
65
|
+
|
66
|
+
Copyright (c) 2010 Zach Pendleton. See LICENSE for details.
|
data/ftp-ext.gemspec
CHANGED
@@ -5,22 +5,21 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{ftp-ext}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.3"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Zach Pendleton"]
|
12
|
-
s.date = %q{2010-
|
13
|
-
s.description = %q{
|
12
|
+
s.date = %q{2010-12-30}
|
13
|
+
s.description = %q{FTPExt adds two methods (put_dir and rmrf_dir) to Net::FTP to copy, sync, and delete whole folders over FTP.}
|
14
14
|
s.email = %q{zachpendleton@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
16
|
-
"LICENSE"
|
17
|
-
"README.rdoc"
|
16
|
+
"LICENSE"
|
18
17
|
]
|
19
18
|
s.files = [
|
20
19
|
".document",
|
21
20
|
".gitignore",
|
22
21
|
"LICENSE",
|
23
|
-
"README.
|
22
|
+
"README.mdown",
|
24
23
|
"Rakefile",
|
25
24
|
"VERSION",
|
26
25
|
"ftp-ext.gemspec",
|
@@ -32,7 +31,7 @@ Gem::Specification.new do |s|
|
|
32
31
|
s.rdoc_options = ["--charset=UTF-8"]
|
33
32
|
s.require_paths = ["lib"]
|
34
33
|
s.rubygems_version = %q{1.3.7}
|
35
|
-
s.summary = %q{Folder copying methods for Net::FTP}
|
34
|
+
s.summary = %q{Folder copying and deletion methods for Net::FTP}
|
36
35
|
s.test_files = [
|
37
36
|
"test/helper.rb",
|
38
37
|
"test/test_ftp-ext.rb"
|
@@ -46,11 +45,11 @@ Gem::Specification.new do |s|
|
|
46
45
|
s.add_development_dependency(%q<shoulda>, [">= 2.11.3"])
|
47
46
|
s.add_dependency(%q<ptools>, [">= 1.1.9"])
|
48
47
|
else
|
49
|
-
s.
|
48
|
+
s.add_development_dependency(%q<shoulda>, [">= 2.11.3"])
|
50
49
|
s.add_dependency(%q<ptools>, [">= 1.1.9"])
|
51
50
|
end
|
52
51
|
else
|
53
|
-
s.
|
52
|
+
s.add_development_dependency(%q<shoulda>, [">= 2.11.3"])
|
54
53
|
s.add_dependency(%q<ptools>, [">= 1.1.9"])
|
55
54
|
end
|
56
55
|
end
|
data/lib/ftp-ext.rb
CHANGED
@@ -1,56 +1,147 @@
|
|
1
|
-
%w
|
1
|
+
%w(rubygems net/ftp ptools).each do |lib|
|
2
2
|
require lib
|
3
3
|
end
|
4
4
|
|
5
5
|
module Net
|
6
6
|
class FTP
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
def put_dir(opts = {})
|
8
|
+
# Set default options
|
9
|
+
options = {
|
10
|
+
:local => ".",
|
11
|
+
:remote => ".",
|
12
|
+
:verbose => false,
|
13
|
+
:erase => false,
|
14
|
+
:exclude => []
|
15
|
+
}.merge!(opts)
|
10
16
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
# Format options[:exclude]
|
18
|
+
unless options[:preformatted]
|
19
|
+
options[:exclude].map! { |f| File.join(options[:local], f) }
|
20
|
+
options[:preformatted] = true
|
21
|
+
end
|
22
|
+
|
23
|
+
# Check for existence of directory on remote server
|
24
|
+
# and create it if it isn't there. If it does exist,
|
25
|
+
# call sync_dir instead.
|
26
|
+
if !remote_file_exists?(options[:remote])
|
27
|
+
file_segments = options[:remote].split(File::SEPARATOR)
|
28
|
+
|
29
|
+
# Make directory and all missing parent directories
|
30
|
+
file_segments.length.times do |n|
|
31
|
+
fpath = File.join(file_segments[0..n])
|
32
|
+
|
33
|
+
unless remote_file_exists?(fpath) || fpath == ""
|
34
|
+
puts "mkdir #{fpath}" if options[:verbose] == true
|
35
|
+
mkdir(fpath)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
else
|
39
|
+
options[:erase] ? (rmrf_dir(options[:remote], options[:verbose]); put_dir(options)) : sync_dir(options)
|
40
|
+
return
|
41
|
+
end
|
42
|
+
|
43
|
+
# Upload files recursively
|
44
|
+
Dir.foreach(options[:local]) do |f|
|
45
|
+
local = File.join(options[:local], f)
|
46
|
+
remote = File.join(options[:remote], f)
|
47
|
+
|
48
|
+
(puts "excluding #{local}"; next) if options[:exclude].include?(local)
|
49
|
+
|
50
|
+
if File.file?(local)
|
51
|
+
puts "cp #{remote}" if options[:verbose] == true
|
52
|
+
File.binary?(local) ? putbinaryfile(local, remote) : puttextfile(local, remote)
|
53
|
+
|
54
|
+
# ignore . and .., but upload other directories
|
20
55
|
elsif !f.match(/^\.+$/)
|
21
|
-
put_dir(
|
56
|
+
put_dir(options.merge({ :local => local, :remote => remote }))
|
22
57
|
end
|
58
|
+
|
23
59
|
end
|
24
|
-
|
25
|
-
Dir.chdir('..')
|
26
|
-
chdir('..')
|
60
|
+
|
27
61
|
end
|
28
|
-
|
29
|
-
def
|
30
|
-
|
62
|
+
|
63
|
+
def rmrf_dir(dir, verbose = false)
|
64
|
+
return if !remote_file_exists?(dir)
|
65
|
+
|
66
|
+
# Get file list from the server and begin deleting files
|
67
|
+
begin
|
68
|
+
flist = nlst(dir)
|
69
|
+
|
70
|
+
flist.each do |f|
|
71
|
+
# Ignore . and ..
|
72
|
+
next if f.match(/^\.+$/)
|
73
|
+
|
74
|
+
# Try to delete the file. If we fail then it's a directory
|
75
|
+
begin
|
76
|
+
delete(f)
|
77
|
+
puts "rm #{f}" if verbose
|
78
|
+
rescue Exception => e
|
79
|
+
rmrf_dir(f, verbose)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
rescue Exception => e
|
84
|
+
# If the directory is empty, error silently and continue execution
|
85
|
+
end
|
31
86
|
|
32
|
-
|
33
|
-
|
87
|
+
rmdir(dir)
|
88
|
+
puts "rm -rf #{dir}" if verbose
|
34
89
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
90
|
+
end
|
91
|
+
|
92
|
+
protected
|
93
|
+
|
94
|
+
def sync_dir(options = {})
|
95
|
+
# Loop through each directory and compare/update the files
|
96
|
+
Dir.foreach(options[:local]) do |f|
|
97
|
+
local = File.join(options[:local], f)
|
98
|
+
remote = File.join(options[:remote], f)
|
99
|
+
|
100
|
+
(puts "excluding #{local}" if options[:verbose] == true; next) if options[:exclude].include?(local)
|
101
|
+
|
102
|
+
if File.file?(local)
|
103
|
+
if !remote_file_exists?(remote) || mtime(remote) < File.mtime(local)
|
104
|
+
puts "updating #{remote}" if options[:verbose] == true
|
105
|
+
File.binary?(local) ? putbinaryfile(local, remote) : puttextfile(local, remote)
|
106
|
+
|
107
|
+
else
|
108
|
+
puts "skipping #{remote}" if options[:verbose] == true
|
39
109
|
end
|
40
110
|
elsif !f.match(/^\.+$/)
|
41
|
-
|
42
|
-
sync_dir(File.path(f), f)
|
111
|
+
put_dir(options.merge(:local => local, :remote => remote))
|
43
112
|
end
|
113
|
+
|
44
114
|
end
|
45
115
|
|
46
|
-
Dir.chdir('..')
|
47
|
-
chdir('..')
|
48
116
|
end
|
49
117
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
118
|
+
def remote_file_exists?(file)
|
119
|
+
fpath = file.split(File::SEPARATOR)
|
120
|
+
|
121
|
+
# If we're only one directory deep, don't try to join anything
|
122
|
+
if fpath.length == 1
|
123
|
+
# starting at file system root?
|
124
|
+
if file.match(/^#{File::SEPARATOR}/)
|
125
|
+
return nlst(File::SEPARATOR).include?(file)
|
126
|
+
# starting relative to FTP dir
|
127
|
+
else
|
128
|
+
return nlst.include?(file)
|
129
|
+
end
|
130
|
+
# Nested directories
|
131
|
+
else
|
132
|
+
fname = fpath.pop
|
133
|
+
fpath.map! { |v| v == "" ? File::SEPARATOR : v }
|
134
|
+
fpath = fpath.join(File::SEPARATOR)
|
135
|
+
|
136
|
+
# Wrap return in begin..rescue because nlst errors on empty directory
|
137
|
+
begin
|
138
|
+
return nlst(fpath).include?(File.join(fpath, fname))
|
139
|
+
rescue Exception => e
|
140
|
+
return false if e.message.match("450")
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
54
145
|
end
|
55
146
|
|
56
147
|
end
|
data/test/test_ftp-ext.rb
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
|
-
class
|
4
|
-
|
5
|
-
|
3
|
+
class FTPExtTest < Test::Unit::TestCase
|
4
|
+
context "FTPExt should" do
|
5
|
+
|
6
|
+
setup do
|
7
|
+
@src = 'htdocs'
|
8
|
+
end
|
9
|
+
|
10
|
+
should "upload a directory to a linux machine" do
|
11
|
+
ftp = Net::FTP.new('localhost')
|
12
|
+
ftp.login('testuser', 'testpass')
|
13
|
+
|
14
|
+
ftp.put_dir(@src)
|
15
|
+
puts ftp.nlst
|
16
|
+
assert true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ftp-ext
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 29
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 3
|
10
|
+
version: 0.1.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Zach Pendleton
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-12-30 00:00:00 -07:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -50,7 +50,7 @@ dependencies:
|
|
50
50
|
version: 1.1.9
|
51
51
|
type: :runtime
|
52
52
|
version_requirements: *id002
|
53
|
-
description:
|
53
|
+
description: FTPExt adds two methods (put_dir and rmrf_dir) to Net::FTP to copy, sync, and delete whole folders over FTP.
|
54
54
|
email: zachpendleton@gmail.com
|
55
55
|
executables: []
|
56
56
|
|
@@ -58,12 +58,11 @@ extensions: []
|
|
58
58
|
|
59
59
|
extra_rdoc_files:
|
60
60
|
- LICENSE
|
61
|
-
- README.rdoc
|
62
61
|
files:
|
63
62
|
- .document
|
64
63
|
- .gitignore
|
65
64
|
- LICENSE
|
66
|
-
- README.
|
65
|
+
- README.mdown
|
67
66
|
- Rakefile
|
68
67
|
- VERSION
|
69
68
|
- ftp-ext.gemspec
|
@@ -103,7 +102,7 @@ rubyforge_project:
|
|
103
102
|
rubygems_version: 1.3.7
|
104
103
|
signing_key:
|
105
104
|
specification_version: 3
|
106
|
-
summary: Folder copying methods for Net::FTP
|
105
|
+
summary: Folder copying and deletion methods for Net::FTP
|
107
106
|
test_files:
|
108
107
|
- test/helper.rb
|
109
108
|
- test/test_ftp-ext.rb
|
data/README.rdoc
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
= ftp-ext
|
2
|
-
|
3
|
-
A simple gem for pushing and syncing entire folders over FTP. Adds two new
|
4
|
-
public methods to Net::FTP -- put_dir and sync_dir. Both take two arguments:
|
5
|
-
the local folder to be pushed, and the remote folder to be synced to.
|
6
|
-
|
7
|
-
Right now it's missing any error checking, but is useful for basic operations
|
8
|
-
in controlled environments.
|
9
|
-
|
10
|
-
== Note on Patches/Pull Requests
|
11
|
-
|
12
|
-
* Fork the project.
|
13
|
-
* Make your feature addition or bug fix.
|
14
|
-
* Add tests for it. This is important so I don't break it in a
|
15
|
-
future version unintentionally.
|
16
|
-
* Commit, do not mess with rakefile, version, or history.
|
17
|
-
(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)
|
18
|
-
* Send me a pull request. Bonus points for topic branches.
|
19
|
-
|
20
|
-
== Copyright
|
21
|
-
|
22
|
-
Copyright (c) 2010 Zach Pendleton. See LICENSE for details.
|