ruby_clone 1.0.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/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ruby_clone.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ notification :off
5
+
6
+ guard 'rspec', :cli => "--colour" do
7
+ watch(%r{^spec/.+_spec\.rb$})
8
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
9
+ watch('spec/spec_helper.rb') { "spec" }
10
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Frederico Benevides
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,329 @@
1
+ # **RubyClone** - Simplifying the use of Rsync
2
+
3
+ **RubyClone** was designed to be simple, easy, clean and a high level script for RSync.
4
+
5
+ ## Installation
6
+
7
+ Install the gem:
8
+
9
+ gem install ruby_clone
10
+
11
+ Create a file that is named '.ruby_clone' in your home folder and set up a profile.
12
+ **NOTE**: Don't forget to use the dot in the file.
13
+
14
+ And then execute:
15
+
16
+ $ ruby_clone my_profile
17
+
18
+ You can also specify another path of your **RubyClone** file:
19
+
20
+ $ ruby_clone -b /my/ruby_clone_file my_profile
21
+
22
+ ## Usage
23
+
24
+ ### Basic Configuration
25
+
26
+ To start using it, you'll need to create the following configuration in your **RubyClone** file.
27
+
28
+ **NOTE**: The folder of from configurations needs to have the last slash. If it doesn't have, it will copy the
29
+ 'source_folder' inside the 'destination_folder'. (This behaviour is from RSync)
30
+
31
+ **NOTE 2**: If your source folder/destination_folder has spaces as "My Source Folder", you need to set up it as
32
+ "My\\ Source\\ Folder" with **two back slashes** for **each space**. Spaces and only one back slash will not work.
33
+
34
+
35
+ profile :my_profile do
36
+ from "/my/source_folder/"
37
+ to "/my/destination_folder"
38
+ end
39
+
40
+ If you set up this configuration in your $HOME/.ruby_clone, you can run with:
41
+
42
+ $ ruby_clone my_profile
43
+
44
+ If is in another path, run with:
45
+
46
+ $ ruby_clone -b /my/backup_file my_profile
47
+
48
+ **NOTE**: This basic setup doesn't sync deleted files from the source folder. If you want to delete them, you need
49
+ to setup the option 'delete: true'.
50
+
51
+ ### SSH
52
+
53
+ **RubyClone** offers the option to use SSH in a easy way. You just need to use the option ':ssh' with the 'user@host'.
54
+ Example for SSH that is on source folder.
55
+
56
+ profile :my_profile do
57
+ from "/my/source_folder/", ssh: "user@source_server"
58
+ to "/my/destination_folder"
59
+ end
60
+
61
+ Example for SSH that is on destination folder:
62
+
63
+ profile :my_profile do
64
+ from "/my/source_folder/"
65
+ to "/my/destination_folder", ssh: "user@destination_server"
66
+ end
67
+
68
+ After running one of theses profiles, you just need to type the password.
69
+
70
+ ## Improving your **RubyClone** file
71
+
72
+ **RubyClone** aims to be simple and be readable for you when you set up it. All the new configurations that you
73
+ can set up was created with this in mind.
74
+
75
+ Here is all the configuration you can set up and improve your **RubyClone** file.
76
+
77
+ ### Excluding a Pattern - exclude_pattern "pattern"
78
+
79
+ If you need to exclude folders or files matching a pattern, use the 'exclude_pattern command.
80
+ This command can be used in two ways: On the top of **RubyClone** file and/or inside the 'from'.
81
+ Here a complete example:
82
+
83
+ exclude_pattern "top_pattern"
84
+
85
+ profile :my_profile1 do
86
+ from "/my/source_folder/"
87
+ to "/my/destination_folder"
88
+ end
89
+
90
+ profile :my_profile2 do
91
+ from "/my/source_folder/" do
92
+ exclude_pattern "from_pattern"
93
+ end
94
+ to "/my/destination_folder"
95
+ end
96
+
97
+ 1. 'my_profile1' will exclude only the "top_pattern".
98
+ 2. 'my_profile2' will exclude the "top_pattern" and "from_pattern"
99
+
100
+ ### Including a Pattern - include_pattern "pattern"
101
+
102
+ Same as the exclude_pattern. If you need to include folders or files matching a pattern, use the
103
+ include_pattern command. This command can be use in two ways: On the top of **RubyClone** file and/or
104
+ inside the 'from' block. Example:
105
+
106
+ include_pattern "top_pattern"
107
+
108
+ profile :my_profile1 do
109
+ from "/my/source_folder/"
110
+ to "/my/destination_folder"
111
+ end
112
+
113
+ profile :my_profile2 do
114
+ from "/my/source_folder/" do
115
+ include_pattern "from_pattern"
116
+ end
117
+ to "/my/destination_folder"
118
+ end
119
+
120
+ 1. 'my_profile1' will include only the "top_pattern".
121
+ 2. 'my_profile2' will include the "top_pattern" and "from_pattern"
122
+
123
+ Here an example using exclude_pattern and include_pattern together. In this example, :my_profile will exclude
124
+ all folders and will sync/include only the 'from_pattern'
125
+
126
+ profile :my_profile do
127
+ from "/my/source_folder/" do
128
+ exclude_pattern "*"
129
+ include_pattern "from_pattern"
130
+ end
131
+ to "/my/destination_folder"
132
+ end
133
+
134
+ ### Deleting files
135
+
136
+ 1. Deleting files that don't exist in source_folder but in destination folder - delete: true
137
+
138
+ The basic configuration was created to be a secure configuration. So if you really want to delete files/folders
139
+ in your destination folder that doesn't exist anymore in your source folder, you'll need to set up 'delete: true' in
140
+ your **RubyClone** file. Below an example how to do it:
141
+
142
+ profile :my_profile do
143
+ from "/my/source_folder/"
144
+ to "/my/destination_folder", delete: true
145
+ end
146
+
147
+ Now all files that exists in "/my/destination_folder/" but "/my/source_folder" will be deleted. If for some
148
+ reason you want to keep some files and delete others, just set up the exclude_pattern inside 'from' block.
149
+
150
+ 2. Deleting files that are excluded from source folder to destination folder - delete_excluded: true
151
+
152
+ If you decided to exclude files from the syncronization but they still in your destination folder, you can use
153
+ the 'delete_excluded' to delete this files inside destination folder that are excluded from the source_folder.
154
+ Example:
155
+
156
+ profile :my_profile do
157
+ from "/my/source_folder/" do
158
+ exclude_pattern "my_pattern"
159
+ end
160
+ to "/my/destination_folder", delete_excluded: true
161
+ end
162
+
163
+ Now all files that are inside destination_folder that have the pattern 'my_pattern' will be deleted.
164
+
165
+ **NOTE**: When you use 'delete_excluded :true' you don't need to set up 'delete: true' even the from
166
+ configuration doesn't have the 'exclude_pattern'
167
+
168
+ ### Backuping files - backup "folder"
169
+
170
+ 1. If you want to save all the files that get **updated** and **deleted**, you need to set up the 'backup'
171
+ configuration inside the 'to' block. Example:
172
+
173
+ profile :my_profile do
174
+ from "/my/source_folder/"
175
+ to "/my/destination_folder", delete: true do
176
+ backup "/my/backup_folder"
177
+ end
178
+ end
179
+
180
+ Now all the folders/files that get **updated** or **deleted** in the "/my/destination_folder" will be moved to
181
+ "/my/backup_folder". The files moved to "/my/destination_folder" will have a default suffix that is a **RubyClone**
182
+ control key plus the Date with the format: "yyyyMMdd".
183
+
184
+ All files that are saved in the "/my/backup_folder" will have a limit of 5 files. So if any file that passed this
185
+ limit, the old file will be deleted. You can change this limit using the option "limit".
186
+
187
+ **NOTE**: The **RubyClone** control key is a special key to use as a control to remove old duplicated files.
188
+
189
+ 2. Changing the suffix of the files inside the backup folder - backup "folder", suffix: "my_suffix"
190
+
191
+ If you want to change the Date suffix for the files that you backup, you need to set the 'suffix' as a parameter to 'backup'.
192
+ Example:
193
+
194
+ profile :my_profile do
195
+ from "/my/source_folder/"
196
+ to "/my/destination_folder", delete: true do
197
+ backup "/my/backup_folder", suffix: "my_suffix"
198
+ end
199
+ end
200
+
201
+ Another example using suffix with the Time class of Ruby to generate the date with timestamp "yyyyMMdd_hhmmss" :
202
+
203
+ profile :my_profile do
204
+ from "/my/source_folder/"
205
+ to "/my/destination_folder", delete: true do
206
+ backup "/my/backup_folder", suffix: "_#{Time.now.strftime("%Y%m%d_%H%M%S")}"
207
+ end
208
+ end
209
+
210
+ **NOTE**: Changing the suffix will not change the **RubyClone** control key. Since this key control how to remove the
211
+ old duplicated files.
212
+
213
+ 3. Disabling the default suffix - backup "folder", disable_suffix: true
214
+
215
+ If for some reason you want to disable the file suffix in the backup folder you can set true for 'default_true'. Doing
216
+ that all the files that are moved to "/my/backup_folder" will have the same name and it will get override by a new file.
217
+ Example:
218
+
219
+ profile :my_profile do
220
+ from "/my/source_folder/"
221
+ to "/my/destination_folder", delete: true do
222
+ backup "/my/backup_folder", disable_suffix: true
223
+ end
224
+ end
225
+
226
+ **NOTE**: This kind of setup will **backup only one file** and nothing more.
227
+
228
+ 4. Changing how many files you want to backup - backup "folder", limit: 10
229
+
230
+ **RubyClone** have a default to save 5 files "/my/backup_folder" that have the same name. If you want to change the
231
+ limit you just need to set up the option limit with the number of files to save.
232
+ Example:
233
+
234
+ profile :my_profile do
235
+ from "/my/source_folder/"
236
+ to "/my/destination_folder", delete: true do
237
+ backup "/my/backup_folder", limit: 20
238
+ end
239
+ end
240
+
241
+ Now "/my/backup_folder" will save 20 files with the same name.
242
+
243
+ 5. Disabling the limit of files to save. Save unlimited files - backup "folder", limit: :unlimited
244
+
245
+ If for some reason you want to backup all files that are **updated** or **deleted** you need to set up the 'limit'
246
+ option with ':unlimited'
247
+ Example:
248
+
249
+ profile :my_profile do
250
+ from "/my/source_folder/"
251
+ to "/my/destination_folder", delete: true do
252
+ backup "/my/backup_folder", limit: :unlimited
253
+ end
254
+ end
255
+
256
+ Now all files will be saved, until you change the 'limit' to a specific number to erased all the old files that passed
257
+ the new limit.
258
+
259
+ ### Disabling the output commands of RSync - config options
260
+
261
+ 1. **RubyClone** offers the possibility to not show the rsync command generated (show_command) and rsync output
262
+ (show_output). To use it, you need to set up in the top of your **RubyClone** file the 'config' and the commands you
263
+ want to disable. Example:
264
+
265
+ config show_command: false, show_output: false
266
+
267
+ profile :my_profile do
268
+ from "/my/source_folder/"
269
+ to "/my/destination_folder"
270
+ end
271
+
272
+ The above config will not show anymore the rsync command and rsync outputs. But errors will keep showing if
273
+ happen.
274
+
275
+ 2. Overriding the default configuration of Rsync command - config options: 'override_options'
276
+
277
+ If you need to override the default configurations for RSync you can set up the "config options: 'my_options'".
278
+ Example:
279
+
280
+ config options: '-Cav --stats'
281
+
282
+ profile :my_profile do
283
+ from "/my/source_folder/"
284
+ to "/my/destination_folder"
285
+ end
286
+
287
+ ## Running
288
+
289
+ **RubyClone** as default will try to read the **RubyClone** file in your home folder: $HOME/.ruby_clone. If you
290
+ need to specify another path of your **RubyClone** file don't forget to add the option -b
291
+
292
+ $ ruby_clone -b /my/ruby_clone_file profile
293
+
294
+ RSync offers the options to dry-run the command and just run the sincronization as a simulation. You can do this
295
+ too with **RubyClone**. Just pass the option '-d' to ruby_clone
296
+
297
+ $ ruby_clone -d profile
298
+
299
+ ## More interesting **RubyClone** file
300
+
301
+ Here a interesting **RubyClone** file that you can improve.
302
+
303
+ **NOTE**: Don't forget that the folder of from configuration needs to have the last slash. If it doesn't have,
304
+ it will copy the 'source_folder' inside the 'destination_folder. (This behaviour is from RSync)
305
+
306
+ **NOTE 2**: If your source folder/destination_folder has spaces as "/My Source Folder", you need to set up it as
307
+ "My\\ Source\\ Folder" with **two back slashes** for **each space**. Spaces and only one back slash will not work.
308
+
309
+ profile :my_profile do
310
+ from "/my/source_folder/" do
311
+ exclude_pattern "pattern"
312
+ end
313
+ to "/my/destination_folder", delete_excluded: true do
314
+ backup "/my/backup"
315
+ end
316
+ end
317
+
318
+ ## **WARNINGS**
319
+
320
+ Even this script were has been tested many times, you run this script entirely at your own risk.
321
+ **RubyClone** accepts no responsibility for any damage this script may cause.
322
+
323
+ ## Contributing to **RubyClone**
324
+
325
+ * Fork, fix, then send me a pull request.
326
+
327
+ ## Copyright
328
+
329
+ Copyright (c) 2012 Frederico Benevides. See MIT-LICENSE for further details.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/bin/ruby_clone ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ruby_clone'
4
+
5
+ extend RubyClone::DSL
6
+
7
+ options = RubyClone::Options.new(STDOUT)
8
+ options.parse(ARGV)
9
+
10
+ if !ARGV.empty?
11
+ load File.expand_path options.configurations[:backup_file]
12
+
13
+ rsync = current_rsync
14
+ rsync.dry_run = options.configurations[:dry_run]
15
+ run_backup ARGV[0]
16
+ end
@@ -0,0 +1,41 @@
1
+ require 'fileutils'
2
+ require 'find'
3
+
4
+ module RubyClone
5
+
6
+ module BackupUtils
7
+
8
+ def self.delete_files(path, ruby_clone_suffix, limit)
9
+
10
+ if File.exists?(path)
11
+ paths = find_files_with_same_name path, ruby_clone_suffix
12
+ delete_files_reached_the_limit paths, limit
13
+ end
14
+ end
15
+
16
+ def self.find_files_with_same_name(path, ruby_clone_suffix)
17
+ paths = {}
18
+
19
+ Find.find(path) do |path|
20
+ path_without_suffix = path.sub(/#{ruby_clone_suffix}.*/, '')
21
+
22
+ if FileTest.file? path
23
+ paths[path_without_suffix] ||= []
24
+ paths[path_without_suffix] << path
25
+ end
26
+ end
27
+
28
+ paths
29
+ end
30
+
31
+ def self.delete_files_reached_the_limit(paths, limit)
32
+ paths.each_value do |files|
33
+ if files.size > limit
34
+ files.slice(0, files.size - limit).each do |file|
35
+ FileUtils.remove_entry file
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,79 @@
1
+ module RubyClone
2
+
3
+ module DSL
4
+
5
+ lambda {
6
+
7
+ rsync = RubyClone::RSync.new(STDOUT)
8
+ current_object = rsync
9
+
10
+ define_method :current_rsync do
11
+ rsync
12
+ end
13
+
14
+ define_method :rsync_new_instance do
15
+ rsync = RubyClone::RSync.new(STDOUT)
16
+ current_object = rsync
17
+ end
18
+
19
+ define_method :run_backup do |profile|
20
+ rsync.run profile
21
+ end
22
+
23
+ define_method :profile do |name, &block|
24
+ profile = Profile.new(name)
25
+ current_object = profile
26
+
27
+ if block
28
+ rsync.profiles = profile
29
+ called_block = block.call
30
+
31
+ raise SyntaxError, 'Empty Profile not allowed' if not called_block
32
+ else
33
+ raise SyntaxError, 'Empty Profile not allowed'
34
+ end
35
+
36
+ current_object = rsync
37
+ end
38
+
39
+ define_method :from do |folder, options = {}, &block|
40
+ from_folder = FromFolder.new(folder, options)
41
+ current_object = from_folder
42
+
43
+ rsync.last_profile.from_folder = from_folder
44
+
45
+ block.call if block
46
+ current_object = rsync
47
+ end
48
+
49
+ define_method :to do |folder, options = {}, &block|
50
+ to_folder = ToFolder.new(folder, options)
51
+ current_object = to_folder
52
+
53
+ rsync.last_profile.to_folder = to_folder
54
+
55
+ block.call if block
56
+ current_object = rsync
57
+ end
58
+
59
+ define_method :backup do |path, options = {}|
60
+ backup = Backup.new(path, options)
61
+ current_object.backup = backup
62
+ end
63
+
64
+ define_method :exclude_pattern do |path|
65
+ current_object.exclude_pattern = path
66
+ end
67
+
68
+ define_method :include_pattern do |path|
69
+ current_object.include_pattern = path
70
+ end
71
+
72
+ define_method :config do |configurations|
73
+ current_object.update_configurations configurations
74
+ end
75
+
76
+ }.call
77
+
78
+ end
79
+ end
@@ -0,0 +1,38 @@
1
+ require 'optparse'
2
+
3
+ module RubyClone
4
+
5
+ class Options
6
+ attr_reader :configurations
7
+ attr_reader :profile
8
+
9
+ def initialize(output)
10
+ @output = output
11
+ @configurations = {}
12
+ @configurations[:backup_file] = '~/.ruby_clone'
13
+ end
14
+
15
+ def parse(argv)
16
+ opts = OptionParser.new do |opts|
17
+ opts.banner = "Usage: ruby_clone [options] profile"
18
+
19
+ opts.on("-b", "--backup-file path", String, "Change the path to backup file (default is #{ENV['HOME']}/.ruby_clone)") do |backup_file|
20
+ @configurations[:backup_file] = backup_file
21
+ end
22
+
23
+ opts.on("-d", "--dry-run", "Show what would have been transferred") do
24
+ @configurations[:dry_run] = true
25
+ end
26
+
27
+ opts.on("-h", "--help", "This message") do
28
+ @output.puts opts
29
+ end
30
+
31
+ argv = %w[-h] if argv.empty?
32
+ opts.parse!(argv)
33
+ end
34
+ opts
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,128 @@
1
+ module RubyClone
2
+
3
+ class FromFolder
4
+ attr_accessor :path
5
+
6
+ def initialize(path, options = {})
7
+ @exclude_patterns = []
8
+ @include_patterns = []
9
+ @options = options
10
+ @path = path
11
+ end
12
+
13
+ def exclude_pattern=(path)
14
+ @exclude_patterns << path
15
+ end
16
+
17
+ def include_pattern=(path)
18
+ @include_patterns << path
19
+ end
20
+
21
+ def to_command
22
+ command = ""
23
+ command << @include_patterns.map { |e| "--include=#{e}" }.join(" ") + " "
24
+ command << @exclude_patterns.map { |e| "--exclude=#{e}" }.join(" ")
25
+ command.strip
26
+ end
27
+
28
+ def ssh?
29
+ if @options[:ssh]
30
+ true
31
+ else
32
+ false
33
+ end
34
+ end
35
+
36
+ def to_s
37
+ return "#{@options[:ssh]}:#{@path}" if @options[:ssh]
38
+ return @path if not @options[:ssh]
39
+ end
40
+ end
41
+
42
+ class ToFolder
43
+ attr_accessor :backup
44
+ attr_accessor :path
45
+
46
+ def initialize(path, options = {})
47
+ @path = path
48
+ @options = options
49
+ end
50
+
51
+ def delete_files
52
+ @backup.delete_files if @backup
53
+ end
54
+
55
+ def to_command
56
+ command = ""
57
+ command << "--delete " if @options[:delete]
58
+ command << "--delete-excluded " if @options[:delete_excluded]
59
+
60
+ command << backup.to_command if backup
61
+ command.strip
62
+ end
63
+
64
+ def ssh?
65
+ if @options[:ssh]
66
+ true
67
+ else
68
+ false
69
+ end
70
+ end
71
+
72
+ def to_s
73
+ return "#{@options[:ssh]}:#{@path}" if @options[:ssh]
74
+ return @path if not @options[:ssh]
75
+ end
76
+ end
77
+
78
+ class Profile
79
+ attr_accessor :name
80
+ attr_accessor :from_folder
81
+ attr_accessor :to_folder
82
+
83
+ def initialize(name)
84
+ @name = name.to_s
85
+ end
86
+
87
+ def to_s
88
+ @name
89
+ end
90
+ end
91
+
92
+ class Backup
93
+ attr_accessor :path
94
+
95
+ def initialize(path, options = { })
96
+ @default_suffix = '_rbcl'
97
+ @options = { disable_suffix: false, limit: 5 }.merge(options)
98
+ @path = path
99
+
100
+ end
101
+
102
+ def delete_files
103
+ BackupUtils.delete_files @path, @default_suffix, @options[:limit] if @options[:limit] != :unlimited
104
+ end
105
+
106
+ def to_command
107
+ command = "-b "
108
+ command << "#{create_suffix} " if not @options[:disable_suffix]
109
+ command << "--backup-dir=#{path} "
110
+ command.strip
111
+ end
112
+
113
+ private
114
+
115
+ def create_suffix
116
+ time = @time || Time.now.strftime("%Y%m%d")
117
+ command = "--suffix="
118
+ command << "#{@default_suffix}_#{time}" if not @options[:suffix]
119
+ command << "#{@default_suffix}#{@options[:suffix]}" if @options[:suffix]
120
+ command
121
+ end
122
+
123
+ def to_s
124
+ @path
125
+ end
126
+ end
127
+
128
+ end