jmstacey-cfbackup 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.markdown CHANGED
@@ -1,6 +1,19 @@
1
1
  CFBackup ChangeLog
2
2
  ==================
3
3
 
4
+ 0.6.0 2009-05-03
5
+ -----------------
6
+ * Added example_scripts to RDoc.
7
+ * gem creates cfconfig.yml in /etc for reference
8
+ * CFBackup looks in multiple places for config file
9
+ * Hidden directory in $HOME ($HOME/.cfconfig.yml)
10
+ * Non-hidden in current directory (./cfconfig.yml)
11
+ * In /etc/cfconfig.yml
12
+ * Refactoring and new usage with --action push|pull|delete
13
+ * Pulling files implemented
14
+ * Deleting files implemented
15
+ * Initial unit tests completed
16
+
4
17
  0.5.0 2009-04-18
5
18
  -----------------
6
19
 
data/README.markdown CHANGED
@@ -1,9 +1,7 @@
1
1
  CFBackup
2
2
  =========
3
3
 
4
- CFBackup is a small ruby program that transfers files or directories from the
5
- local machine to a Cloud Files container. It is meant to serve as a useful tool
6
- for automated backups.
4
+ CFBackup is a small ruby program that transfers files or directories from the local machine to a Cloud Files container. It is meant to serve as a useful tool for automated backups.
7
5
 
8
6
  Features
9
7
  -----------
@@ -11,21 +9,50 @@ Features
11
9
  * Backup a single file or directory (recursion uses pseudo directories)
12
10
  * Pipe data straight to container
13
11
  * Free transfers over local Rackspace network for Slicehost/Cloud Server
14
- customers in DFW1 datacenter
12
+ customers in DFW1 datacenter
15
13
 
16
14
  Requirements
17
15
  --------------
18
16
 
19
- TODO: Complete this area
20
17
  * ruby-cloudfiles
21
18
 
19
+ Notes:
20
+ * If you install CFBackup as a gem, all of the dependencies _should_ automatically be installed for you.
21
+ * Ubuntu Users: The Ubuntu rubygems package will installs executables outside your normal PATH. You will
22
+ need to update it or create a symlink to access cfbackup from anywhere. See the wiki for more information.reating a symlink.
23
+
22
24
  Install
23
25
  -----------
24
26
 
25
27
  * gem sources -a http://gems.github.com
26
28
  * sudo gem install jmstacey-cfbackup
27
29
 
28
- Depending on what Operating System you're using, you may be required to install libs that are outside of the gem world.
30
+ Configuration
31
+ -----------
32
+
33
+ CFBackup will look in the following places (in order) for the configuration file named cfconfig.yml
34
+
35
+ * Hidden in home directory (~/.cfbackup.yml)
36
+ * Non-hidden in present working directory (./cfbackup.yml)
37
+ * In etc (/etc/cfbackup.yml)
38
+
39
+ The configuration file can be overridden at any time with the --config_file option
40
+
41
+ Configuration
42
+ -----------
43
+
44
+ Usage: cfbackup.rb --action push|pull|delete options --container CONTAINER
45
+ --action ACTION Action to perform: push, pull, or delete.
46
+ --pipe_data Pipe data from another application and stream it to Cloud Files
47
+ -r, --recursive Traverse local path recursivley
48
+ -v, --verbose Run verbosely
49
+ --local_path LOCAL_PATH Local path or file
50
+ --container CONTAINER Cloud Files container name
51
+ --version Show current version
52
+ --config_file PATH Use specified config file, rather than the default
53
+ --local_net Use unmetered connection in DFW1 (only applicable to Slicehost or Mosso Cloud Server customers)
54
+
55
+ The wiki has usage examples and some sample automation scripts can be found in the example_scripts directory.
29
56
 
30
57
  Copyright
31
58
  ------------
data/Rakefile CHANGED
@@ -10,7 +10,7 @@ begin
10
10
  gem.homepage = "http://github.com/jmstacey/cfbackup"
11
11
  gem.authors = ["Jon Stacey"]
12
12
 
13
- # dependencies
13
+ # Dependencies
14
14
  gem.add_dependency('jmstacey-ruby-cloudfiles', '>=1.3.3')
15
15
 
16
16
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
@@ -39,7 +39,6 @@ rescue LoadError
39
39
  end
40
40
  end
41
41
 
42
-
43
42
  task :default => :test
44
43
 
45
44
  require 'rake/rdoctask'
@@ -54,6 +53,7 @@ Rake::RDocTask.new do |rdoc|
54
53
  rdoc.rdoc_dir = 'rdoc'
55
54
  rdoc.title = "cfbackup #{version}"
56
55
  rdoc.rdoc_files.include('README*')
56
+ rdoc.rdoc_files.include('example_scripts/**')
57
57
  rdoc.rdoc_files.include('lib/**/*.rb')
58
58
  end
59
59
 
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
- :minor: 5
4
- :patch: 0
3
+ :minor: 6
4
+ :patch: 0
data/bin/cfbackup CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'rubygems'
3
- require 'CFBackup'
3
+ require 'cfbackup'
4
4
 
5
5
  backup = CFBackup.new(ARGV)
6
6
  backup::run
data/lib/OptCFBackup.rb CHANGED
@@ -12,14 +12,14 @@ class OptCFBackup
12
12
  # Initializes object with command line arguments passed
13
13
  def initialize(args)
14
14
 
15
- @banner = "Usage: cfbackup.rb [options] --pipe_data|--local_path PATH --container CONTAINER"
15
+ @banner = "Usage: cfbackup.rb --action push|pull|delete options --container CONTAINER"
16
16
 
17
17
  @options = OpenStruct.new
18
- self.options.config = ['~/.cfconfig.yml', './cfconfig.yml', '/etc/cfconfig.yml']
18
+ self.options.config = ["#{ENV['HOME']}/.cfconfig.yml", './cfconfig.yml', '/etc/cfconfig.yml']
19
+ self.options.action = ''
19
20
  self.options.pipe_data = false
20
21
  self.options.show_ver = false
21
22
  self.options.recursive = false
22
- self.options.restore = false
23
23
  self.options.local_net = false
24
24
  self.options.container = ''
25
25
  self.options.local_path = ''
@@ -29,6 +29,10 @@ class OptCFBackup
29
29
  opts = OptionParser.new do |opts|
30
30
  opts.banner = self.banner
31
31
 
32
+ opts.on("--action ACTION", "Action to perform: push, pull, or delete.") do |action|
33
+ self.options.action = action
34
+ end
35
+
32
36
  opts.on("--pipe_data", "Pipe data from another application and stream it to Cloud Files") do |pipe|
33
37
  self.options.pipe_data = pipe
34
38
  end
@@ -50,15 +54,12 @@ class OptCFBackup
50
54
  clean_remote_path unless (self.options.remote_path.nil?)
51
55
  end
52
56
 
53
- opts.on("--restore", "Restore files to local path") do |restore|
54
- self.options.restore = restore
55
- end
56
-
57
57
  opts.on("--version", "Show current version") do |version|
58
58
  self.options.show_ver = version
59
59
  end
60
60
 
61
- opts.on("--config_file PATH", "Use specified config file, rather than the default") do |config|
61
+ opts.on("--config_file PATH", "Use specified config file, rather than the default") do |config|
62
+ self.options.config = Array.new()
62
63
  self.options.config << config
63
64
  end
64
65
 
@@ -70,7 +71,7 @@ class OptCFBackup
70
71
 
71
72
  opts.parse!(args)
72
73
 
73
- end # parse()
74
+ end # initialize()
74
75
 
75
76
  private
76
77
 
@@ -78,7 +79,7 @@ class OptCFBackup
78
79
  if self.options.remote_path[0,1] == "/"
79
80
  self.options.remote_path.slice!(0)
80
81
  end
81
- # Won't work for piped data. Might result in "text.txt/"
82
+ # Follwoig won't work for piped data. Might result in "text.txt/"
82
83
  # self.options.remote_path = self.options.remote_path + "/" unless (self.options.remote_path[-1,1] == "/")
83
84
  end
84
85
 
data/lib/cfbackup.rb CHANGED
@@ -15,8 +15,9 @@
15
15
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
 
17
17
  require 'rubygems'
18
+ require 'ftools'
18
19
  require 'cloudfiles'
19
- require 'OptCFBackup'
20
+ require 'optcfbackup'
20
21
  require 'yaml'
21
22
 
22
23
  class CFBackup
@@ -27,8 +28,13 @@ class CFBackup
27
28
  # Special case if the version is requested
28
29
  if @opts.options.show_ver
29
30
  version_file = File.join(File.dirname(__FILE__), '..', 'VERSION.yml')
30
- config = YAML::load(File.open(version_file))
31
- show_error("CFBackup v#{config[:major]}.#{config[:minor]}.#{config[:patch]}")
31
+ if File.exist?(version_file)
32
+ config = YAML.load(File.read(version_file))
33
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
34
+ else
35
+ version = "unkown version"
36
+ end
37
+ show_error("CFBackup #{version}")
32
38
  end
33
39
 
34
40
  # Locate and load config file
@@ -40,23 +46,26 @@ class CFBackup
40
46
  end
41
47
  show_error('Error: Unable to locate config file.') unless (@conf != nil)
42
48
 
49
+ prep_connection
50
+
43
51
  end # initialize()
44
52
 
45
53
  def run
46
54
 
47
55
  show_error() unless (@opts.options.container != "")
48
56
 
49
- if @opts.options.pipe_data
50
- prep_container
51
- run_piped_data
52
- elsif @opts.options.local_path != ""
53
- prep_container
54
-
55
- if @opts.options.restore
56
- run_restore
57
+ # Run appropriate action
58
+ case @opts.options.action
59
+ when 'push'
60
+ if @opts.options.pipe_data
61
+ push_piped_data
57
62
  else
58
- run_backup
63
+ push_files
59
64
  end
65
+ when 'pull'
66
+ pull_files()
67
+ when 'delete'
68
+ delete_files
60
69
  else
61
70
  show_error()
62
71
  end
@@ -65,8 +74,7 @@ class CFBackup
65
74
 
66
75
  private
67
76
 
68
- def prep_container
69
-
77
+ def prep_connection
70
78
  # Establish connection
71
79
  show_verbose "Establishing connection...", false
72
80
  @cf = CloudFiles::Connection.new(@conf["username"], @conf["api_key"]);
@@ -76,30 +84,38 @@ class CFBackup
76
84
  if @opts.options.local_net
77
85
  @cf.storagehost = 'snet-storage.clouddrive.com'
78
86
  end
87
+ end # prep_connection()
79
88
 
80
- # Check for the container. If it doesn't exist, create it.
81
- unless @cf.container_exists?(@opts.options.container)
82
- show_verbose "Conainer '#{@opts.options.container}' does not exist. Creating it...", false
83
- @cf.create_container(@opts.options.container)
84
- show_verbose " done."
89
+ def prep_container(create_container = true)
90
+ # Check for the container. If it doesn't exist, create it if allowed
91
+ if !@cf.container_exists?(@opts.options.container)
92
+ if create_container
93
+ show_verbose "Container '#{@opts.options.container}' does not exist. Creating it...", false
94
+ @cf.create_container(@opts.options.container)
95
+ show_verbose " done."
96
+ else
97
+ show_error("Error: Container '#{@opts.options.container}' does not exist.")
98
+ end
85
99
  end
86
100
 
87
101
  @container = @cf.container(@opts.options.container)
88
-
89
- end # prepConnection()
102
+ end # prep_cnnection()
90
103
 
91
- def run_piped_data
92
- puts "Warning: 5GB per stream cap"
104
+ def push_piped_data
105
+ prep_container
106
+
107
+ puts "Warning: 5GB maximum filesize"
93
108
  object = @container.create_object(@opts.options.remote_path, true)
94
109
  object.write("STDIN")
95
- end
110
+ end # push_piped_data()
96
111
 
97
- def run_backup
112
+ def push_files
113
+ prep_container
98
114
 
99
115
  path = @opts.options.local_path
100
-
116
+ pwd = Dir.getwd # Save current directory so we can come back
117
+
101
118
  if FileTest::file?(path)
102
- Dir.chdir(File::dirname(path))
103
119
  glob_options = File.join(File::basename(path))
104
120
  elsif @opts.options.recursive
105
121
  Dir.chdir(path)
@@ -111,41 +127,173 @@ class CFBackup
111
127
  files = Dir.glob(glob_options)
112
128
 
113
129
  # Upload file(s)
114
- files.each do |file|
130
+ counter = 1
131
+ show_verbose "There are #{files.length} files to process."
132
+ files.each do |file|
115
133
  file = file.sub(/\.\//, '')
116
134
  if file == "" || file[0,1] == "." || FileTest.directory?(file)
135
+ show_verbose "(#{counter}/#{files.length}) Skipping #{file}"
136
+ counter += 1
117
137
  next
118
138
  end
119
139
 
120
- show_verbose "Uploading #{file}...", false
121
- object = @container.create_object(file, true)
140
+ show_verbose "(#{counter}/#{files.length}) Pushing file #{file}...", false
141
+
142
+ if @opts.options.remote_path.to_s == ''
143
+ remote_path = file
144
+ else
145
+ remote_path = File.join(@opts.options.remote_path, file)
146
+ end
147
+
148
+ object = @container.create_object(remote_path, true)
122
149
  object.load_from_filename(file)
150
+
123
151
  show_verbose " done."
152
+ counter += 1
124
153
  end # files.each
125
154
 
126
- end # run_backup()
155
+ Dir.chdir(pwd) # Go back to original directory
156
+
157
+ end # push_files()
158
+
159
+ def pull_files
160
+ prep_container(false)
161
+
162
+ file = object_file?
163
+ objects = get_objects_array
164
+
165
+ # Process objects
166
+ counter = 1
167
+ show_verbose "There are #{objects.length} objects to process."
168
+ objects.each do |object_name|
169
+ object = @container.object(object_name)
170
+ if object.content_type == "application/directory"
171
+ show_verbose "(#{counter}/#{objects.length}) Pseudo directory #{object.name}"
172
+ counter += 1
173
+ next
174
+ end
175
+
176
+ path_info = File.split(@opts.options.local_path.to_s)
177
+ file_info = File.split(object.name.to_s)
178
+
179
+ if file # Dealing with a single file pull
180
+ if @opts.options.local_path.to_s == ''
181
+ filepath = file_info[1].to_s # Use current directory and original name
182
+ else
183
+ if File.exist?(@opts.options.local_path.to_s)
184
+ # The file exists, so we will overwrite it
185
+ filepath = File.join(@opts.options.local_path.to_s)
186
+ else
187
+ # If the file doesn't exist, a new name may have been given.
188
+ # Test the path.
189
+ if File.exist?(path_info[0])
190
+ # A new name was given with a valid path
191
+ filepath = File.join(path_info[0], path_info[1])
192
+ else
193
+ # The given path is not valid
194
+ show_error("cfbackup: #{file_info[0]}: No such file or directory.")
195
+ end
196
+ end
197
+ end
198
+ else # Dealing with a multi-object pull
199
+ if @opts.options.local_path.to_s == ''
200
+ filepath = object.name.to_s # Use current directory with object name
201
+ dir_path = file_info[0]
202
+ else
203
+ if File.directory?(@opts.options.local_path.to_s)
204
+ filepath = File.join(@opts.options.local_path.to_s, object.name.to_s)
205
+ dir_path = File.join(@opts.options.local_path, file_info[0])
206
+ else
207
+ # We can't copy a directory to a file...
208
+ show_error("cfbackup: #{@container.name}:#{@opts.options.remote_path.to_s}/ is a directory (not copied).")
209
+ end
210
+ end
211
+ File.makedirs(dir_path) # Create subdirectories as needed
212
+ end
213
+
214
+ show_verbose "(#{counter}/#{objects.length}) Pulling object #{object.name}...", false
215
+ object.save_to_filename(filepath)
216
+ show_verbose " done"
217
+ counter += 1
218
+ end
219
+ end # pull_files()
220
+
221
+ def delete_files
222
+ prep_container(false)
223
+
224
+ if object_file?
225
+ show_verbose "Deleting object #{@opts.options.remote_path.to_s}"
226
+ @container.delete_object(@opts.options.remote_path.to_s)
227
+ else
228
+ if !@opts.options.recursive
229
+ show_error("Error: #{@opts.options.remote_path} is a directory but the the recursive option was not specified. Doing nothing.")
230
+ else
231
+ objects = get_objects_array
232
+
233
+ # Process objects
234
+ counter = 1
235
+ show_verbose "There are #{objects.length} objects to process."
236
+ objects.each do |object_name|
237
+ show_verbose "(#{counter}/#{objects.length}) Deleting object #{object_name}...", false
238
+ @container.delete_object(object_name)
239
+ show_verbose " done"
240
+ counter += 1
241
+ end
242
+ end
243
+ end
244
+
245
+ end # delete_files()
246
+
247
+
248
+ # Helper methods below
249
+
250
+ def object_file?
251
+
252
+ file = false
253
+ unless @opts.options.remote_path.to_s == ''
254
+ if @container.object_exists?(@opts.options.remote_path)
255
+ if @container.object(@opts.options.remote_path).content_type != "application/directory"
256
+ file = true
257
+ if @opts.options.recursive
258
+ puts "Warning: This is a file so the recursive option is meaningless."
259
+ end
260
+ end
261
+ else
262
+ show_error("The object #{@opts.options.remote_path} does not exist")
263
+ end
264
+ end
265
+
266
+ return file
267
+ end
127
268
 
128
- def run_restore
269
+ def get_objects_array
129
270
 
130
- # TODO: Implement run_restore
131
- # We have to do a bit of fancy footwork to make directories work
132
- puts "Oops! Restore hasn't been implemented yet. Help me out and submit a patch :-)"
271
+ file = object_file?
133
272
 
134
- end # run_restore()
273
+ # Get array of objects to process
274
+ objects = Array.new
275
+ if file
276
+ objects << @opts.options.remote_path.to_s
277
+ elsif @opts.options.recursive
278
+ # Use prefix instead of path so that "subdirectories" are included
279
+ objects = @container.objects(:prefix => @opts.options.remote_path)
280
+ else
281
+ objects = @container.objects(:path => @opts.options.remote_path)
282
+ end
283
+
284
+ return objects
285
+ end
135
286
 
136
287
  # Shows given message if verbose output is turned on
137
288
  def show_verbose(message, line_break = true)
138
-
139
289
  unless !@opts.options.verbose
140
290
  if line_break
141
291
  puts message
142
292
  else
143
293
  print message
144
294
  end
145
-
146
295
  $stdout.flush
147
296
  end
148
-
149
297
  end # show_verbose()
150
298
 
151
299
  # Show error message, banner and exit
@@ -155,8 +303,4 @@ class CFBackup
155
303
  exit
156
304
  end # show_error()
157
305
 
158
- def parse_container_path(container)
159
- # Split based on :
160
- end # parse_container_path()
161
-
162
306
  end # class CFBackup
@@ -1,7 +1,107 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class CfbackupTest < Test::Unit::TestCase
4
- should "probably rename this file and start testing for real" do
5
- flunk "hey buddy, you should probably rename this file and start testing for real"
4
+ TEST_DIR = 'test/tmp'
5
+
6
+ context "A backup" do
7
+
8
+ context "with a single file push" do
9
+ setup do
10
+ mock_ARGV = ['--action', 'push', '--local_path', 'test/data/data.txt', '--container', 'test', '--config_file', 'test/cfconfig.yml', '-v']
11
+ @backup = CFBackup.new(mock_ARGV)
12
+ end
13
+
14
+ should "return true when file sucessfully sent" do
15
+ assert @backup.run
16
+ end
17
+ end
18
+
19
+ context "with a recursive directory push" do
20
+ setup do
21
+ mock_ARGV = ['--action', 'push', '-r', '--local_path', 'test/data', '--container', 'test', '--config_file', 'test/cfconfig.yml', '-v']
22
+ @backup = CFBackup.new(mock_ARGV)
23
+ end
24
+
25
+ should "return true when all files/directories successfully sent" do
26
+ assert @backup.run
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+ context "A restore" do
33
+
34
+ context "with a single file pull" do
35
+ file = 'data.txt'
36
+ filepath = File.join(TEST_DIR, file)
37
+
38
+ setup do
39
+ mock_ARGV = ['--action', 'pull', '--container', "test:#{file}", '--local_path', "#{filepath}", '--config_file', 'test/cfconfig.yml', '-v']
40
+ @backup = CFBackup.new(mock_ARGV)
41
+ end
42
+
43
+ should "return true when file succesfully pulled" do
44
+ assert @backup.run
45
+ end
46
+
47
+ # I don't know why this doesn't work. It's like File is caching results
48
+ # and not updating until the applicaiton exists. Overcoming by
49
+ # asserting the file deletion during teardown at the loss of shoulda
50
+ #
51
+ # should "result in test/tmp/#{file} existing" do
52
+ # assert File.exist?(filepath)
53
+ # end
54
+
55
+ teardown do
56
+ assert File.delete(filepath)
57
+ end
58
+ end
59
+
60
+ context "with a recursive pull" do
61
+ setup do
62
+ mock_ARGV = ['--action', 'pull', '--container', "test", '--local_path', "test/tmp", '--config_file', 'test/cfconfig.yml', '-v']
63
+ @backup = CFBackup.new(mock_ARGV)
64
+ end
65
+
66
+ should "return true when all files pulled" do
67
+ assert @backup.run
68
+ end
69
+
70
+ # Todo compare directories
71
+
72
+ teardown do
73
+ system "rm -rf test/tmp/*"
74
+ # I didn't feel safe with TEST_DIR + '/*'
75
+ # Disaster waiting to happen. rm -rf is already bad enough.
76
+ end
77
+ end # context "A recursive pull"
78
+
6
79
  end
80
+
81
+ context "A deletion" do
82
+
83
+ context "of a single file" do
84
+ setup do
85
+ mock_ARGV = ['--action', 'delete', '--container', "test:folder_1/file1.txt", '--config_file', 'test/cfconfig.yml', '-v']
86
+ @backup = CFBackup.new(mock_ARGV)
87
+ end
88
+
89
+ should "return true when file deleted" do
90
+ assert @backup.run
91
+ end
92
+ end
93
+
94
+ context "of a pseudo directory" do
95
+ setup do
96
+ mock_ARGV = ['--action', 'delete', '-r', '--container', "test:folder_2", '--config_file', 'test/cfconfig.yml', '-v']
97
+ @backup = CFBackup.new(mock_ARGV)
98
+ end
99
+
100
+ should "return true when directory deleted" do
101
+ assert @backup.run
102
+ end
103
+ end
104
+
105
+ end # context "A deletion"
106
+
7
107
  end
data/test/cfconfig.yml ADDED
@@ -0,0 +1,2 @@
1
+ username: xxxxxxxxxxxxx
2
+ api_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
File without changes
@@ -0,0 +1,2 @@
1
+ Folder 1
2
+ Test File 1
@@ -0,0 +1,2 @@
1
+ Folder 1
2
+ Test File 1
@@ -0,0 +1,2 @@
1
+ Folder 1/Folder 3
2
+ Test File 1
@@ -0,0 +1,2 @@
1
+ Folder 2
2
+ Test File 1
@@ -0,0 +1,2 @@
1
+ Folder 2
2
+ Test File 1
data/test/test_helper.rb CHANGED
@@ -6,5 +6,5 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
6
  $LOAD_PATH.unshift(File.dirname(__FILE__))
7
7
  require 'cfbackup'
8
8
 
9
- class Test::Unit::TestCase
9
+ class Test::Unit::TestCase
10
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jmstacey-cfbackup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Stacey
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-04-19 00:00:00 -07:00
12
+ date: 2009-05-03 00:00:00 -07:00
13
13
  default_executable: cfbackup
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -41,7 +41,13 @@ files:
41
41
  - lib/OptCFBackup.rb
42
42
  - lib/cfbackup.rb
43
43
  - test/cfbackup_test.rb
44
- - test/data.txt
44
+ - test/cfconfig.yml
45
+ - test/data/data.txt
46
+ - test/data/folder_1/file1.txt
47
+ - test/data/folder_1/file2.txt
48
+ - test/data/folder_1/folder_3/file1.txt
49
+ - test/data/folder_2/file1.txt
50
+ - test/data/folder_2/file2.txt
45
51
  - test/test_helper.rb
46
52
  has_rdoc: true
47
53
  homepage: http://github.com/jmstacey/cfbackup