libelule 0.0.1
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/.gitignore +5 -0
- data/CHANGELOG +5 -0
- data/Gemfile +4 -0
- data/LICENSE +24 -0
- data/README +5 -0
- data/Rakefile +14 -0
- data/lib/libelule.rb +186 -0
- data/lib/libelule/version.rb +3 -0
- data/libelule.gemspec +20 -0
- data/test/libelule_test.rb +187 -0
- data/test/test_helper.rb +7 -0
- metadata +118 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Copyright (c) 2011 Patrick Huesler
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
MOST OF ALL: USE IT FOR GOOD, NOT EVIL
|
data/README
ADDED
data/Rakefile
ADDED
data/lib/libelule.rb
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require "libelule/version"
|
3
|
+
require 'net/ftp'
|
4
|
+
module Libelule
|
5
|
+
class Client
|
6
|
+
class << self
|
7
|
+
attr_accessor :silence_log_messages
|
8
|
+
silence_log_messages = true
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :ftp_client
|
12
|
+
|
13
|
+
def initialize(host, user, password)
|
14
|
+
@host = host
|
15
|
+
@user = user
|
16
|
+
@password = password
|
17
|
+
@ftp_client = Net::FTP
|
18
|
+
end
|
19
|
+
|
20
|
+
def ftp
|
21
|
+
if @ftp.nil? || @ftp.closed?
|
22
|
+
@ftp = ftp_client.send(:new, @host)
|
23
|
+
@ftp.passive = true
|
24
|
+
end
|
25
|
+
@ftp
|
26
|
+
end
|
27
|
+
|
28
|
+
def one_way_sync_from_local_to_remote(local_dir, remote_dir)
|
29
|
+
begin
|
30
|
+
ftp.login(@user, @password)
|
31
|
+
log "logged in, start syncing..."
|
32
|
+
sync_folder(local_dir, remote_dir)
|
33
|
+
log "sync finished"
|
34
|
+
return true
|
35
|
+
rescue Net::FTPPermError => e
|
36
|
+
log "Failed: #{e.message}"
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def clean_up(target_dir, options)
|
42
|
+
suffix_to_keep = options[:suffix_to_keep]
|
43
|
+
dry_run = options[:dry_run]
|
44
|
+
raise ArgumentError, 'please provide a suffix to keep' unless /[A-Z]/ =~ suffix_to_keep
|
45
|
+
ftp.login(@user, @password)
|
46
|
+
clean_up_folder(target_dir, suffix_to_keep, dry_run)
|
47
|
+
end
|
48
|
+
|
49
|
+
def clean_up_with_manifest(target_dir, manifest, options = {})
|
50
|
+
ftp.login(@user, @password)
|
51
|
+
dry_run = options[:dry_run]
|
52
|
+
clean_up_folder_with_manifest(target_dir, manifest, dry_run)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def clean_up_folder_with_manifest(target_dir, manifest, dry_run = false)
|
58
|
+
ftp.chdir(target_dir)
|
59
|
+
remote_dirs, remote_files = get_remote_dir_and_file_names
|
60
|
+
remote_files.each do |file|
|
61
|
+
full_path = File.join(ftp.pwd, file)
|
62
|
+
if manifest.include?(full_path)
|
63
|
+
log "[KEEP] file #{full_path}"
|
64
|
+
else
|
65
|
+
ftp.delete(file) unless dry_run
|
66
|
+
log "[DELETE] file #{full_path}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
remote_dirs.each do |dir|
|
70
|
+
clean_up_folder_with_manifest(dir, manifest, dry_run)
|
71
|
+
end
|
72
|
+
ftp.chdir("..")
|
73
|
+
end
|
74
|
+
|
75
|
+
def clean_up_folder(target_dir, suffix_to_keep, dry_run = false)
|
76
|
+
ftp.chdir(target_dir)
|
77
|
+
remote_dirs, remote_files = get_remote_dir_and_file_names
|
78
|
+
remote_files.each do |file|
|
79
|
+
if file =~ /\.[0-9]{1,5}[^#{suffix_to_keep}]?\./
|
80
|
+
log "deleting file #{target_dir}/#{file}"
|
81
|
+
ftp.delete(file) unless dry_run
|
82
|
+
end
|
83
|
+
end
|
84
|
+
remote_dirs.each do |dir|
|
85
|
+
clean_up_folder(dir, suffix_to_keep, dry_run)
|
86
|
+
end
|
87
|
+
ftp.chdir("..")
|
88
|
+
end
|
89
|
+
|
90
|
+
def put_title(title)
|
91
|
+
log "#{'-'*80}\n#{title}:\n\n"
|
92
|
+
end
|
93
|
+
|
94
|
+
def full_file_path(file)
|
95
|
+
File.join(Dir.pwd, file)
|
96
|
+
end
|
97
|
+
|
98
|
+
def upload_file(file)
|
99
|
+
put_title "upload file: #{full_file_path(file)}"
|
100
|
+
ftp.put(file)
|
101
|
+
end
|
102
|
+
|
103
|
+
def upload_folder(dir)
|
104
|
+
put_title "upload folder: #{full_file_path(dir)}"
|
105
|
+
Dir.chdir dir
|
106
|
+
ftp.mkdir dir
|
107
|
+
ftp.chdir dir
|
108
|
+
|
109
|
+
local_dirs, local_files = get_local_dir_and_file_names
|
110
|
+
|
111
|
+
local_dirs.each do |subdir|
|
112
|
+
upload_folder(subdir)
|
113
|
+
end
|
114
|
+
|
115
|
+
local_files.each do |file|
|
116
|
+
upload_file(file)
|
117
|
+
end
|
118
|
+
|
119
|
+
Dir.chdir("..")
|
120
|
+
ftp.chdir("..")
|
121
|
+
end
|
122
|
+
|
123
|
+
def sync_folder(local_dir, remote_dir)
|
124
|
+
Dir.chdir local_dir
|
125
|
+
ftp.chdir remote_dir
|
126
|
+
|
127
|
+
put_title "process folder: #{Dir.pwd}"
|
128
|
+
|
129
|
+
local_dirs, local_files = get_local_dir_and_file_names
|
130
|
+
remote_dirs, remote_files = get_remote_dir_and_file_names
|
131
|
+
|
132
|
+
new_dirs = local_dirs - remote_dirs
|
133
|
+
new_files = local_files - remote_files
|
134
|
+
existing_dirs = local_dirs - new_dirs
|
135
|
+
existing_files = local_files - new_files
|
136
|
+
|
137
|
+
new_files.each do |file|
|
138
|
+
upload_file(file)
|
139
|
+
end
|
140
|
+
|
141
|
+
new_dirs.each do |dir|
|
142
|
+
upload_folder(dir)
|
143
|
+
end
|
144
|
+
|
145
|
+
existing_dirs.each do |dir|
|
146
|
+
sync_folder(dir, dir)
|
147
|
+
end
|
148
|
+
|
149
|
+
Dir.chdir("..")
|
150
|
+
ftp.chdir("..")
|
151
|
+
end
|
152
|
+
|
153
|
+
def get_local_dir_and_file_names
|
154
|
+
dirs = []
|
155
|
+
files = []
|
156
|
+
Dir.glob("*").each do |file|
|
157
|
+
if File.file?(file)
|
158
|
+
files << file
|
159
|
+
else
|
160
|
+
dirs << file
|
161
|
+
end
|
162
|
+
end
|
163
|
+
return [dirs, files]
|
164
|
+
end
|
165
|
+
|
166
|
+
def get_remote_dir_and_file_names
|
167
|
+
dirs = []
|
168
|
+
files = []
|
169
|
+
ftp.ls do |file|
|
170
|
+
#-rw-r--r-- 1 james staff 6 Jan 07 03:54 hello.txt
|
171
|
+
file_name = file.gsub(/\S+\s+\d+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+/, '')
|
172
|
+
case file[0, 1]
|
173
|
+
when "-"
|
174
|
+
files << file_name
|
175
|
+
when "d"
|
176
|
+
dirs << file_name
|
177
|
+
end
|
178
|
+
end
|
179
|
+
return [dirs, files]
|
180
|
+
end
|
181
|
+
|
182
|
+
def log(message)
|
183
|
+
puts message unless self.class.silence_log_messages
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
data/libelule.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "libelule/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "libelule"
|
7
|
+
s.version = Libelule::VERSION
|
8
|
+
s.authors = ["Patrick Huesler"]
|
9
|
+
s.email = ["patrick.huesler@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/phuesler/ftpsync"
|
11
|
+
s.summary = "Syncing files over ftp/sftp"
|
12
|
+
s.description = ""
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.add_development_dependency "rake"
|
18
|
+
s.add_development_dependency "shoulda-context"
|
19
|
+
s.add_development_dependency "mocha"
|
20
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
class Libelule::ClientTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
FTP_SOURCE_DIR = File.join("/tmp","libelule_test")
|
7
|
+
FTP_TARGET_DIR = "remote_assets"
|
8
|
+
|
9
|
+
def ftp_string_for_filename(file_name)
|
10
|
+
"-rw-r--r-- 1 james staff 6 Jan 07 03:54 #{file_name}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def ftp_string_for_directory(directory_name)
|
14
|
+
"drw-r--r-- 1 james staff 6 Jan 07 03:54 #{directory_name}"
|
15
|
+
end
|
16
|
+
|
17
|
+
Libelule::Client.silence_log_messages = true
|
18
|
+
|
19
|
+
context "one_way_sync_from_local_to_remote" do
|
20
|
+
setup do
|
21
|
+
FileUtils.mkdir_p(FTP_SOURCE_DIR)
|
22
|
+
|
23
|
+
@ftp_client_test_double = stub_everything('fake_ftp_client')
|
24
|
+
@ftp_fake_client_class = stub("FakeFTPClient", :new => @ftp_client_test_double)
|
25
|
+
|
26
|
+
@ftp = Libelule::Client.new("ftp.test.host", "ftpuser", "ftppassword")
|
27
|
+
@ftp.ftp_client = @ftp_fake_client_class
|
28
|
+
end
|
29
|
+
|
30
|
+
teardown do
|
31
|
+
FileUtils.rm_rf(FTP_SOURCE_DIR)
|
32
|
+
FileUtils.rm_rf(FTP_TARGET_DIR)
|
33
|
+
end
|
34
|
+
|
35
|
+
should "open a connection to the host" do
|
36
|
+
@ftp_fake_client_class.expects(:new).with('ftp.test.host').returns(@ftp_client_test_double)
|
37
|
+
@ftp.one_way_sync_from_local_to_remote(FTP_SOURCE_DIR, FTP_TARGET_DIR)
|
38
|
+
end
|
39
|
+
|
40
|
+
should "login into the ftp server" do
|
41
|
+
@ftp_client_test_double.expects(:login).with('ftpuser','ftppassword')
|
42
|
+
@ftp.one_way_sync_from_local_to_remote(FTP_SOURCE_DIR, FTP_TARGET_DIR)
|
43
|
+
end
|
44
|
+
|
45
|
+
should 'copy new files to the remote host' do
|
46
|
+
FileUtils.touch(File.join(FTP_SOURCE_DIR, 'test.txt'))
|
47
|
+
@ftp_client_test_double.expects(:put).with("test.txt")
|
48
|
+
@ftp.one_way_sync_from_local_to_remote(FTP_SOURCE_DIR, FTP_TARGET_DIR)
|
49
|
+
end
|
50
|
+
|
51
|
+
should 'copy new directories' do
|
52
|
+
FileUtils.mkdir(File.join(FTP_SOURCE_DIR, 'testdirectory'))
|
53
|
+
@ftp_client_test_double.expects(:mkdir).with('testdirectory')
|
54
|
+
@ftp.one_way_sync_from_local_to_remote(FTP_SOURCE_DIR, FTP_TARGET_DIR)
|
55
|
+
end
|
56
|
+
|
57
|
+
should 'copy files of subdirectories' do
|
58
|
+
FileUtils.mkdir(File.join(FTP_SOURCE_DIR, 'testdirectory'))
|
59
|
+
FileUtils.touch File.join(FTP_SOURCE_DIR, 'testdirectory', 'subfile.txt')
|
60
|
+
@ftp_client_test_double.expects(:put).with('subfile.txt')
|
61
|
+
@ftp.one_way_sync_from_local_to_remote(FTP_SOURCE_DIR, FTP_TARGET_DIR)
|
62
|
+
end
|
63
|
+
|
64
|
+
should 'not copy a file to the remote host if it exists already' do
|
65
|
+
FileUtils.touch(File.join(FTP_SOURCE_DIR, 'test.txt'))
|
66
|
+
@ftp_client_test_double.stubs(:ls).yields(ftp_string_for_filename("test.txt"))
|
67
|
+
@ftp_client_test_double.expects(:put).with('test.txt').times(0)
|
68
|
+
@ftp.one_way_sync_from_local_to_remote(FTP_SOURCE_DIR, FTP_TARGET_DIR)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'clean up resources with a manifest' do
|
73
|
+
setup do
|
74
|
+
@ftp_client_test_double = stub_everything('fake_ftp_client')
|
75
|
+
@ftp_fake_client_class = stub("FakeFTPClient", :new => @ftp_client_test_double)
|
76
|
+
|
77
|
+
@ftp = Libelule::Client.new("ftp.test.host", "ftpuser", "ftppassword")
|
78
|
+
@ftp.ftp_client = @ftp_fake_client_class
|
79
|
+
|
80
|
+
@ftp_client_test_double.stubs(:ls).multiple_yields(
|
81
|
+
ftp_string_for_filename("fake.3219C.txt"),
|
82
|
+
ftp_string_for_filename("fake.3211C.txt"),
|
83
|
+
ftp_string_for_directory("subdir1")
|
84
|
+
).then.multiple_yields(
|
85
|
+
ftp_string_for_filename("fake.2223C.txt"),
|
86
|
+
ftp_string_for_filename("fake.2222C.txt"),
|
87
|
+
ftp_string_for_directory("subdir2")
|
88
|
+
).then.multiple_yields(
|
89
|
+
ftp_string_for_filename("fake.3333C.txt"),
|
90
|
+
ftp_string_for_filename("fake.3332C.txt")
|
91
|
+
)
|
92
|
+
@ftp_client_test_double.stubs(:pwd).returns('/published', '/published', '/published/subdir1', '/published/subdir1', '/published/subdir1/subdir2')
|
93
|
+
@manifest = ["/published/fake.3219C.txt", "/published/subdir1/fake.2223C.txt", "/published/subdir1/subdir2/fake.3333C.txt"]
|
94
|
+
end
|
95
|
+
|
96
|
+
should 'delete versions not in the manifest' do
|
97
|
+
@ftp_client_test_double.expects(:delete).with('fake.3211C.txt')
|
98
|
+
@ftp.clean_up_with_manifest(FTP_TARGET_DIR, @manifest)
|
99
|
+
end
|
100
|
+
|
101
|
+
should 'keep files in the manifest' do
|
102
|
+
@ftp_client_test_double.expects(:delete).with('fake.3219C.txt').never
|
103
|
+
@ftp.clean_up_with_manifest(FTP_TARGET_DIR, @manifest)
|
104
|
+
end
|
105
|
+
|
106
|
+
should 'keep files in subdirectories' do
|
107
|
+
@ftp_client_test_double.expects(:delete).with('fake.3219C.txt').never
|
108
|
+
@ftp_client_test_double.expects(:delete).with('fake.2223C.txt').never
|
109
|
+
@ftp_client_test_double.expects(:delete).with('fake.3333C.txt').never
|
110
|
+
@ftp.clean_up_with_manifest(FTP_TARGET_DIR, @manifest)
|
111
|
+
end
|
112
|
+
|
113
|
+
should 'descend into subdirectories and delete' do
|
114
|
+
@ftp_client_test_double.expects(:delete).with('fake.2222C.txt')
|
115
|
+
@ftp_client_test_double.expects(:delete).with('fake.3332C.txt')
|
116
|
+
@ftp.clean_up_with_manifest(FTP_TARGET_DIR, @manifest)
|
117
|
+
end
|
118
|
+
|
119
|
+
should 'have a dry run option' do
|
120
|
+
@ftp_client_test_double.expects(:delete).never
|
121
|
+
@ftp.clean_up_with_manifest(FTP_TARGET_DIR, @manifest, :dry_run => true)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'clean up resources by index' do
|
126
|
+
setup do
|
127
|
+
@ftp_client_test_double = stub_everything('fake_ftp_client')
|
128
|
+
@ftp_fake_client_class = stub("FakeFTPClient", :new => @ftp_client_test_double)
|
129
|
+
|
130
|
+
@ftp = Libelule::Client.new("ftp.test.host", "ftpuser", "ftppassword")
|
131
|
+
@ftp.ftp_client = @ftp_fake_client_class
|
132
|
+
|
133
|
+
@ftp_client_test_double.stubs(:ls).multiple_yields(
|
134
|
+
ftp_string_for_filename("flash.14695871.css"),
|
135
|
+
ftp_string_for_filename("512K"),
|
136
|
+
ftp_string_for_filename("test.txt"),
|
137
|
+
ftp_string_for_filename("test.1000.txt"),
|
138
|
+
ftp_string_for_filename("test.1001A.txt"),
|
139
|
+
ftp_string_for_filename("test.1002B.txt"),
|
140
|
+
ftp_string_for_filename("test.1003C.txt"),
|
141
|
+
ftp_string_for_filename("test.1004C.txt")
|
142
|
+
)
|
143
|
+
end
|
144
|
+
|
145
|
+
should 'delete old versions' do
|
146
|
+
@ftp_client_test_double.expects(:delete).with('test.1000.txt')
|
147
|
+
@ftp_client_test_double.expects(:delete).with('test.1001A.txt')
|
148
|
+
@ftp_client_test_double.expects(:delete).with('test.1002B.txt')
|
149
|
+
@ftp.clean_up(FTP_TARGET_DIR, :suffix_to_keep => 'C')
|
150
|
+
end
|
151
|
+
|
152
|
+
should 'keep unrevisioned files' do
|
153
|
+
@ftp_client_test_double.expects(:delete).with('test.txt').never
|
154
|
+
@ftp_client_test_double.expects(:delete).with('512K').never
|
155
|
+
@ftp.clean_up(FTP_TARGET_DIR, :suffix_to_keep => 'C')
|
156
|
+
end
|
157
|
+
|
158
|
+
should 'keep files with suffix C' do
|
159
|
+
@ftp_client_test_double.expects(:delete).with('test.1004C.txt').never
|
160
|
+
@ftp_client_test_double.expects(:delete).with('test.1003C.txt').never
|
161
|
+
@ftp.clean_up(FTP_TARGET_DIR, :suffix_to_keep => 'C')
|
162
|
+
end
|
163
|
+
|
164
|
+
should 'keep files with a different versioning schema' do
|
165
|
+
@ftp_client_test_double.expects(:delete).with('flash.14695871.css').never
|
166
|
+
@ftp.clean_up(FTP_TARGET_DIR, :suffix_to_keep => 'C')
|
167
|
+
end
|
168
|
+
|
169
|
+
should 'make the suffix to keep configurable' do
|
170
|
+
@ftp_client_test_double.expects(:delete).with('test.1001A.txt').never
|
171
|
+
@ftp.clean_up(FTP_TARGET_DIR, :suffix_to_keep => 'A')
|
172
|
+
end
|
173
|
+
|
174
|
+
should 'raise an exception if suffix to keep is blank' do
|
175
|
+
assert_raises(ArgumentError){ @ftp.clean_up(FTP_TARGET_DIR, :suffix_to_keep => nil) }
|
176
|
+
end
|
177
|
+
|
178
|
+
should 'raise an exception if suffix is not a single uppercase letter' do
|
179
|
+
assert_raises(ArgumentError){ @ftp.clean_up(FTP_TARGET_DIR, :suffix_to_keep => 'foo') }
|
180
|
+
end
|
181
|
+
|
182
|
+
should 'not delete files when the dry run option is set' do
|
183
|
+
@ftp_client_test_double.expects(:delete).never
|
184
|
+
@ftp.clean_up(FTP_TARGET_DIR, :suffix_to_keep => 'C', :dry_run => true)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: libelule
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Patrick Huesler
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-09-28 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rake
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: shoulda-context
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: mocha
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
type: :development
|
61
|
+
version_requirements: *id003
|
62
|
+
description: ""
|
63
|
+
email:
|
64
|
+
- patrick.huesler@gmail.com
|
65
|
+
executables: []
|
66
|
+
|
67
|
+
extensions: []
|
68
|
+
|
69
|
+
extra_rdoc_files: []
|
70
|
+
|
71
|
+
files:
|
72
|
+
- .gitignore
|
73
|
+
- CHANGELOG
|
74
|
+
- Gemfile
|
75
|
+
- LICENSE
|
76
|
+
- README
|
77
|
+
- Rakefile
|
78
|
+
- lib/libelule.rb
|
79
|
+
- lib/libelule/version.rb
|
80
|
+
- libelule.gemspec
|
81
|
+
- test/libelule_test.rb
|
82
|
+
- test/test_helper.rb
|
83
|
+
homepage: https://github.com/phuesler/ftpsync
|
84
|
+
licenses: []
|
85
|
+
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
hash: 3
|
97
|
+
segments:
|
98
|
+
- 0
|
99
|
+
version: "0"
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
hash: 3
|
106
|
+
segments:
|
107
|
+
- 0
|
108
|
+
version: "0"
|
109
|
+
requirements: []
|
110
|
+
|
111
|
+
rubyforge_project:
|
112
|
+
rubygems_version: 1.8.6
|
113
|
+
signing_key:
|
114
|
+
specification_version: 3
|
115
|
+
summary: Syncing files over ftp/sftp
|
116
|
+
test_files:
|
117
|
+
- test/libelule_test.rb
|
118
|
+
- test/test_helper.rb
|