raz 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +57 -0
- data/bin/raz +63 -0
- data/lib/raz/backuper.rb +208 -0
- data/lib/raz/config_file.rb +74 -0
- data/lib/raz/constants.rb +22 -0
- data/lib/raz/entries.rb +139 -0
- data/lib/raz/file_operations.rb +39 -0
- data/lib/raz/items/application.rb +9 -0
- data/lib/raz/items/group.rb +43 -0
- data/lib/raz/restorer.rb +72 -0
- data/lib/raz/version.rb +3 -0
- data/lib/raz.rb +11 -0
- data/raz.gemspec +31 -0
- metadata +145 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cba3b8783105105a189f73b386f5423d4d9caf02
|
4
|
+
data.tar.gz: 9e7e5e1d8d571359eaf8bbd01fcdb208b1dc2d5b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 53214d6e8ecffa2a2837bf516aa370ceb74a3a6fa5e8c9e5eb15951d7a18d60ba67d93745c0eb2e8525d6fc09a0c501227716c15080a73c57ae943495d103dc3
|
7
|
+
data.tar.gz: 1f63bae43ca5203167fba5b8ae74b3ace9f9ce976ac9dc0417f269023007a7a7eb3a41822924e16ab13a60f2e7f8e9fb535c1470d26f354d095757113bbd0019
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Roman Kříž
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Raz
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/samnung/raz.svg)](https://travis-ci.org/samnung/raz)
|
4
|
+
|
5
|
+
Raz is simple tool to backup all files to some external hard drive and restoring with only four commands.
|
6
|
+
|
7
|
+
```bash
|
8
|
+
sudo gem install raz
|
9
|
+
raz backup /Volumes/Fry/Backup-2015-07-22
|
10
|
+
|
11
|
+
# ... and on another machine
|
12
|
+
sudo gem install raz
|
13
|
+
raz restore /Volumes/Fry/Backup-2015-07-22
|
14
|
+
```
|
15
|
+
|
16
|
+
Raz is designed to work on OS X and theoretically should work on Linux too.
|
17
|
+
|
18
|
+
|
19
|
+
## Motivation
|
20
|
+
|
21
|
+
I hate [mackup](https://github.com/lra/mackup) because of creating links to Dropbox or any other place and I am not big fan of Python.
|
22
|
+
|
23
|
+
I hate [Time Machine](https://en.wikipedia.org/wiki/Time_Machine_(OS_X)) because of backing up all files instead of specific ones (so when you want to reinstall to resolve some problems it doesn't work).
|
24
|
+
|
25
|
+
So I've created simple tool but with possibility to be powerfull in near future.
|
26
|
+
|
27
|
+
|
28
|
+
## Configuration file
|
29
|
+
|
30
|
+
Core of this tool is text file containing all files and directories should be backed up. There is no magic YAML, JSON, Config files with strange structure. Files uses Ruby syntax to define applications, their preferences and other valuable files. File must be located in home folder, exactly `~/.raz/config.rb`. Here is simple example of this tool can do:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
group 'Dot files' do
|
34
|
+
path '~/.zprofile'
|
35
|
+
path '~/.zshrc'
|
36
|
+
# ...
|
37
|
+
path '~/.git*' # everything starting with '.git' in home folder
|
38
|
+
end
|
39
|
+
|
40
|
+
app 'Sublime Text 3' do
|
41
|
+
path '~/Library/Application Support/Sublime Text 3/'
|
42
|
+
# ...
|
43
|
+
end
|
44
|
+
|
45
|
+
app 'Xcode' do
|
46
|
+
path '~/Library/Developer/Xcode/UserData/CodeSnippets/'
|
47
|
+
path '~/Library/Developer/Xcode/UserData/FontAndColorThemes/'
|
48
|
+
# ...
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
`group` and `app` is same for now. Both supports adding path to files or folders to be backed up. You can also use wildcard expressions same as [`Dir.glob`](http://ruby-doc.org/core-2.2.0/Dir.html#method-c-glob) supports.
|
53
|
+
|
54
|
+
|
55
|
+
## License
|
56
|
+
|
57
|
+
The tool is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/bin/raz
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', File.dirname(__FILE__))
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'raz'
|
7
|
+
require 'gli'
|
8
|
+
|
9
|
+
include GLI::App
|
10
|
+
|
11
|
+
program_desc 'Tool to backup and restore files.'
|
12
|
+
|
13
|
+
|
14
|
+
on_error do |e|
|
15
|
+
$stderr.puts "#{e.message}".red
|
16
|
+
$stderr.puts "#{e.backtrace_locations.join("\n")}".red
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# ----------------------- backup -----------------------------------
|
21
|
+
|
22
|
+
desc 'Backup all files to destination folder'
|
23
|
+
arg :destination_path
|
24
|
+
|
25
|
+
command :backup do |c|
|
26
|
+
# c.desc 'Path to configuration file'
|
27
|
+
# c.flag :c, :conf
|
28
|
+
|
29
|
+
c.action do |global_options, options, args|
|
30
|
+
help_now! 'Specify at least one destination path' if args.size == 0
|
31
|
+
help_now! 'Specify only one destination path' if args.size > 1
|
32
|
+
|
33
|
+
config_path = Raz::config_path
|
34
|
+
help_now! "Missing configuration file at #{Raz::config_path}" unless File.file?(config_path)
|
35
|
+
|
36
|
+
backuper = Raz::Backuper.new(Raz::ConfigFile.new(config_path), args.first)
|
37
|
+
backuper.backup
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
# ----------------------- restore -----------------------------------
|
45
|
+
|
46
|
+
desc 'Restore all files from source folder'
|
47
|
+
arg :source_path
|
48
|
+
|
49
|
+
command :restore do |c|
|
50
|
+
c.action do |global_options, options, args|
|
51
|
+
help_now! 'Specify at least one source path' if args.size == 0
|
52
|
+
help_now! 'Specify only one source path' if args.size > 1
|
53
|
+
|
54
|
+
restorer = Raz::Restorer.new(args.first)
|
55
|
+
restorer.restore
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
# ---------------------------------------------------------
|
62
|
+
|
63
|
+
exit run(ARGV)
|
data/lib/raz/backuper.rb
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
|
2
|
+
require 'yaml'
|
3
|
+
require 'colorize'
|
4
|
+
require_relative 'file_operations'
|
5
|
+
require_relative 'entries'
|
6
|
+
|
7
|
+
|
8
|
+
module Raz
|
9
|
+
class Backuper
|
10
|
+
# @return [String]
|
11
|
+
#
|
12
|
+
attr_reader :destination_path
|
13
|
+
|
14
|
+
# @return [Array<DirEntry | FileEntry>]
|
15
|
+
#
|
16
|
+
attr_reader :parsed_entries
|
17
|
+
|
18
|
+
# @param [Config] config
|
19
|
+
#
|
20
|
+
def initialize(config, destination_path)
|
21
|
+
@config = config
|
22
|
+
@destination_path = File.expand_path(destination_path)
|
23
|
+
end
|
24
|
+
|
25
|
+
def investigate
|
26
|
+
@file_system = FileSystem.new
|
27
|
+
@parsed_entries = []
|
28
|
+
|
29
|
+
# process all items from configuration file
|
30
|
+
@config.items.each do |item|
|
31
|
+
process_item(item)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Main method to back all files from configuration
|
36
|
+
#
|
37
|
+
def backup
|
38
|
+
FileUtils.mkdir_p(@destination_path)
|
39
|
+
|
40
|
+
dest_contents = FileOperations.dir_entries(@destination_path)
|
41
|
+
|
42
|
+
unless dest_contents.empty?
|
43
|
+
raise "Can only operate on empty or non-existing directory! Directory #{@destination_path} contains: #{dest_contents}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# run before procs
|
47
|
+
(@config.procs[:before_backup] || []).each do |proc|
|
48
|
+
instance_eval &proc
|
49
|
+
end
|
50
|
+
|
51
|
+
investigate if @parsed_entries.nil?
|
52
|
+
|
53
|
+
@copied_paths = []
|
54
|
+
|
55
|
+
@parsed_entries.each do |entry|
|
56
|
+
copy_entry_to_dest(entry)
|
57
|
+
end
|
58
|
+
|
59
|
+
# save info for restorer
|
60
|
+
save_info
|
61
|
+
|
62
|
+
# backup config folder
|
63
|
+
FileOperations.copy_item(File.dirname(@config.path), @destination_path)
|
64
|
+
FileUtils.mv(File.join(@destination_path, CONFIG_FOLDER_BASE_PATH), File.join(@destination_path, BACKUP_CONFIG_FOLDER_BASE_PATH))
|
65
|
+
|
66
|
+
# run after procs
|
67
|
+
(@config.procs[:after_backup] || []).each do |proc|
|
68
|
+
instance_eval &proc
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# @param item [Raz::Items::Group] item to process
|
75
|
+
#
|
76
|
+
def process_item(item)
|
77
|
+
@current_item = item
|
78
|
+
@ignored_items = (@config.ignored_paths + @current_item.ignored_paths).map { |p| File.expand_path(p) }
|
79
|
+
|
80
|
+
puts "Processing group #{item.name}"
|
81
|
+
|
82
|
+
item.paths.each do |requirement_path|
|
83
|
+
print " Processing requirement path #{requirement_path} ... "
|
84
|
+
|
85
|
+
abs_path = File.expand_path(requirement_path)
|
86
|
+
|
87
|
+
if File.directory?(abs_path)
|
88
|
+
process_directory(abs_path)
|
89
|
+
puts 'Success'.green
|
90
|
+
elsif File.file?(abs_path)
|
91
|
+
process_file(abs_path)
|
92
|
+
puts 'Success'.green
|
93
|
+
elsif %w(* ? { } [ ]).any? { |sym| abs_path.include?(sym) }
|
94
|
+
found = Dir.glob(abs_path)
|
95
|
+
found.each do |file|
|
96
|
+
process_file(file)
|
97
|
+
end
|
98
|
+
|
99
|
+
puts 'Success'.green unless found.empty?
|
100
|
+
puts 'Noting found'.yellow if found.empty?
|
101
|
+
else
|
102
|
+
puts "Doesn't exist -> skipping".yellow
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
@ignored_items = nil
|
107
|
+
@current_item = nil
|
108
|
+
end
|
109
|
+
|
110
|
+
def _ok_path?(path)
|
111
|
+
@ignored_items.all? do |ignore_path|
|
112
|
+
!File.fnmatch(ignore_path, path, File::FNM_PATHNAME)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def _process_directory(path)
|
117
|
+
return unless File.directory?(path)
|
118
|
+
|
119
|
+
dir = @file_system.add_dir(path)
|
120
|
+
|
121
|
+
FileOperations.dir_entries(path).each do |subitem|
|
122
|
+
subitem_abs_path = File.join(path, subitem)
|
123
|
+
|
124
|
+
if File.directory?(subitem_abs_path)
|
125
|
+
dir[subitem] = _process_directory(subitem_abs_path)
|
126
|
+
elsif File.file?(subitem_abs_path)
|
127
|
+
file_entry = _process_file(subitem_abs_path)
|
128
|
+
|
129
|
+
if file_entry.nil?
|
130
|
+
dir.ignored_entries[subitem] = FileEntry.new(subitem, subitem_abs_path)
|
131
|
+
else
|
132
|
+
dir[subitem] = file_entry
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
dir
|
138
|
+
end
|
139
|
+
|
140
|
+
# @param [String] path
|
141
|
+
#
|
142
|
+
def process_directory(path)
|
143
|
+
entry = _process_directory(path)
|
144
|
+
@parsed_entries << entry unless entry.nil?
|
145
|
+
end
|
146
|
+
|
147
|
+
# @param [String] path
|
148
|
+
#
|
149
|
+
def _process_file(path)
|
150
|
+
return unless File.file?(path)
|
151
|
+
|
152
|
+
@file_system.add_file(path) if _ok_path?(path)
|
153
|
+
end
|
154
|
+
|
155
|
+
# @param [String] path
|
156
|
+
#
|
157
|
+
def process_file(path)
|
158
|
+
entry = _process_file(path)
|
159
|
+
@parsed_entries << entry unless entry.nil?
|
160
|
+
end
|
161
|
+
|
162
|
+
def copy_entry_to_dest(entry)
|
163
|
+
dest_path = destination_path_from(entry.absolute_path)
|
164
|
+
dest_dir = File.dirname(dest_path)
|
165
|
+
src_path = entry.absolute_path
|
166
|
+
FileUtils.mkdir_p(dest_dir)
|
167
|
+
|
168
|
+
case entry
|
169
|
+
when FileEntry
|
170
|
+
puts "Copying file #{src_path}".green
|
171
|
+
FileOperations.copy_item(src_path, dest_path)
|
172
|
+
@copied_paths << src_path
|
173
|
+
when DirEntry
|
174
|
+
if entry.recursive_ignored_empty?
|
175
|
+
puts "Copying directory #{src_path}".green
|
176
|
+
FileOperations.copy_item(src_path, dest_path)
|
177
|
+
@copied_paths << src_path
|
178
|
+
else
|
179
|
+
sub_entries = entry.recursive_entries
|
180
|
+
return if sub_entries.empty?
|
181
|
+
|
182
|
+
puts "Start copying all files in directory #{src_path}".green
|
183
|
+
sub_entries.each do |sub_entry|
|
184
|
+
copy_entry_to_dest(sub_entry)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# @param source_path [String] path to file/folder
|
191
|
+
#
|
192
|
+
# @return [String]
|
193
|
+
#
|
194
|
+
def destination_path_from(source_path)
|
195
|
+
File.join(@destination_path, BACKUP_DATA_BASE_PATH, source_path)
|
196
|
+
end
|
197
|
+
|
198
|
+
def save_info
|
199
|
+
info = {
|
200
|
+
env: ENV.to_hash,
|
201
|
+
copied_paths: @copied_paths,
|
202
|
+
orig_config_path: @config.path,
|
203
|
+
}
|
204
|
+
|
205
|
+
File.write(File.join(@destination_path, BACKUP_INFO_BASE_PATH), info.to_yaml)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
|
2
|
+
require_relative 'items/group'
|
3
|
+
require_relative 'items/application'
|
4
|
+
|
5
|
+
module Raz
|
6
|
+
class ConfigFile
|
7
|
+
|
8
|
+
# @return [String]
|
9
|
+
#
|
10
|
+
attr_reader :path
|
11
|
+
|
12
|
+
# @return [Array<Raz::Items::Group>]
|
13
|
+
#
|
14
|
+
attr_reader :items
|
15
|
+
|
16
|
+
# @return [Array<String>]
|
17
|
+
#
|
18
|
+
attr_reader :ignored_paths
|
19
|
+
|
20
|
+
# @return [Hash<Symbol, Array<Proc>>]
|
21
|
+
#
|
22
|
+
attr_reader :procs
|
23
|
+
|
24
|
+
# @param path [String] path to configuration file
|
25
|
+
#
|
26
|
+
def initialize(path = nil, &block)
|
27
|
+
@path = path
|
28
|
+
@items = []
|
29
|
+
@ignored_paths = []
|
30
|
+
@procs = {}
|
31
|
+
|
32
|
+
if block_given?
|
33
|
+
instance_eval(&block)
|
34
|
+
else
|
35
|
+
instance_eval(File.read(path), path)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# API
|
40
|
+
|
41
|
+
def group(name, &block)
|
42
|
+
@items << Raz::Items::Group.new(name, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def app(name, &block)
|
46
|
+
@items << Raz::Items::Application.new(name, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
def ignore_path(path)
|
50
|
+
@ignored_paths << path
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def before_backup(&block)
|
55
|
+
@procs[:before_backup] ||= []
|
56
|
+
@procs[:before_backup] << block
|
57
|
+
end
|
58
|
+
|
59
|
+
def after_backup(&block)
|
60
|
+
@procs[:after_backup] ||= []
|
61
|
+
@procs[:after_backup] << block
|
62
|
+
end
|
63
|
+
|
64
|
+
def before_restore(&block)
|
65
|
+
@procs[:before_restore] ||= []
|
66
|
+
@procs[:before_restore] << block
|
67
|
+
end
|
68
|
+
|
69
|
+
def after_restore(&block)
|
70
|
+
@procs[:after_restore] ||= []
|
71
|
+
@procs[:after_restore] << block
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
module Raz
|
3
|
+
module Constants
|
4
|
+
CONFIG_FOLDER_BASE_PATH = '.raz'
|
5
|
+
CONFIG_FILE_BASE_PATH = File.join(CONFIG_FOLDER_BASE_PATH, 'config.rb')
|
6
|
+
|
7
|
+
def config_path(home = Dir.home)
|
8
|
+
File.join(home, CONFIG_FILE_BASE_PATH)
|
9
|
+
end
|
10
|
+
|
11
|
+
# ======= backup paths ================
|
12
|
+
|
13
|
+
BACKUP_CONFIG_FOLDER_BASE_PATH = 'config'
|
14
|
+
|
15
|
+
def backup_config_file_path(backup_path)
|
16
|
+
File.join(backup_path, BACKUP_CONFIG_FOLDER_BASE_PATH, 'config.rb')
|
17
|
+
end
|
18
|
+
|
19
|
+
BACKUP_DATA_BASE_PATH = 'data'
|
20
|
+
BACKUP_INFO_BASE_PATH = 'raz_info.yaml'
|
21
|
+
end
|
22
|
+
end
|
data/lib/raz/entries.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
|
2
|
+
module Raz
|
3
|
+
class FileSystem
|
4
|
+
# @return [DirEntry]
|
5
|
+
#
|
6
|
+
attr_reader :root_entry
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@root_entry = DirEntry.new('/', '/')
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param [String | Array<String>] path path to folder to create
|
13
|
+
#
|
14
|
+
# @return [DirEntry] dir entry for given path
|
15
|
+
#
|
16
|
+
def make_dir_p(path)
|
17
|
+
components = path.split(File::SEPARATOR).reject(&:empty?)
|
18
|
+
|
19
|
+
current = root_entry
|
20
|
+
components.each do |dir|
|
21
|
+
entry = current[dir]
|
22
|
+
current[dir] = entry = DirEntry.new(dir, path) if entry.nil?
|
23
|
+
current = entry
|
24
|
+
end
|
25
|
+
|
26
|
+
current
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param [String] path
|
30
|
+
#
|
31
|
+
# @return [DirEntry]
|
32
|
+
#
|
33
|
+
def add_dir(path)
|
34
|
+
make_dir_p(path)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param [String] path
|
38
|
+
#
|
39
|
+
# @return [FileEntry]
|
40
|
+
#
|
41
|
+
def add_file(path)
|
42
|
+
entry = make_dir_p(File.dirname(path))
|
43
|
+
|
44
|
+
file_entry = FileEntry.new(File.basename(path), path)
|
45
|
+
entry[File.basename(path)] = file_entry
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class DirEntry
|
50
|
+
# @return [Hash<String, FileEntry | DirEntry>]
|
51
|
+
#
|
52
|
+
attr_accessor :entries
|
53
|
+
|
54
|
+
# @return [Hash<String, FileEntry | DirEntry>]
|
55
|
+
#
|
56
|
+
attr_accessor :ignored_entries
|
57
|
+
|
58
|
+
# @return [String]
|
59
|
+
#
|
60
|
+
attr_reader :name
|
61
|
+
|
62
|
+
# @return [String]
|
63
|
+
#
|
64
|
+
attr_reader :absolute_path
|
65
|
+
|
66
|
+
# @param [String] name
|
67
|
+
#
|
68
|
+
def initialize(name, absolute_path)
|
69
|
+
@name = name
|
70
|
+
@absolute_path = absolute_path
|
71
|
+
@entries = {}
|
72
|
+
@ignored_entries = {}
|
73
|
+
end
|
74
|
+
|
75
|
+
def [](key)
|
76
|
+
@entries[key]
|
77
|
+
end
|
78
|
+
|
79
|
+
def []=(key, value)
|
80
|
+
@entries[key] = value
|
81
|
+
end
|
82
|
+
|
83
|
+
def ==(other)
|
84
|
+
name == other.name && entries == other.entries
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [Bool]
|
88
|
+
#
|
89
|
+
def recursive_ignored_empty?
|
90
|
+
return false unless ignored_entries.empty?
|
91
|
+
|
92
|
+
dir_entries.all? do |key, entry|
|
93
|
+
entry.recursive_ignored_empty?
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# @return [Hash<String, FileEntry | DirEntry>]
|
98
|
+
#
|
99
|
+
def dir_entries
|
100
|
+
entries.select { |_k, v| v.is_a?(DirEntry) }
|
101
|
+
end
|
102
|
+
|
103
|
+
# @return [Hash<String, FileEntry | DirEntry>]
|
104
|
+
#
|
105
|
+
def file_entries
|
106
|
+
entries.select { |_k, v| v.is_a?(FileEntry) }
|
107
|
+
end
|
108
|
+
|
109
|
+
# @return [Array<FileEntry | DirEntry>]
|
110
|
+
#
|
111
|
+
def recursive_entries
|
112
|
+
all_entries = []
|
113
|
+
|
114
|
+
all_entries += entries.values
|
115
|
+
all_entries += dir_entries.values.flat_map(&:recursive_entries)
|
116
|
+
|
117
|
+
all_entries
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class FileEntry
|
122
|
+
attr_reader :name
|
123
|
+
|
124
|
+
attr_reader :absolute_path
|
125
|
+
|
126
|
+
def initialize(name, absolute_path)
|
127
|
+
@name = name
|
128
|
+
@absolute_path = absolute_path
|
129
|
+
end
|
130
|
+
|
131
|
+
def ==(other)
|
132
|
+
absolute_path == if other.is_a?(FileEntry)
|
133
|
+
other.absolute_path
|
134
|
+
else
|
135
|
+
other.to_s
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
module Raz
|
3
|
+
module FileOperations
|
4
|
+
class NotExistingFile < ::StandardError; end
|
5
|
+
class UnknownFileType < ::StandardError; end
|
6
|
+
|
7
|
+
# Method to copy file and keep same informations (owner, mtime, ...) as original file
|
8
|
+
#
|
9
|
+
# @param src [String]
|
10
|
+
# @param dest [String]
|
11
|
+
#
|
12
|
+
def copy_item(src, dest)
|
13
|
+
if !File.exist?(src)
|
14
|
+
raise NotExistingFile, "Unknown file type for source #{src}"
|
15
|
+
elsif File.directory?(src)
|
16
|
+
FileUtils.cp_r(src, dest, preserve: true)
|
17
|
+
elsif File.file?(src)
|
18
|
+
FileUtils.cp(src, dest, preserve: true)
|
19
|
+
else
|
20
|
+
raise UnknownFileType, "Unknown file type for source #{src}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module_function :copy_item
|
25
|
+
|
26
|
+
# @param path [String] path to folder
|
27
|
+
#
|
28
|
+
# @return [Array<String>]
|
29
|
+
#
|
30
|
+
def dir_entries(path)
|
31
|
+
entries = Dir.entries(path)
|
32
|
+
entries.delete('.')
|
33
|
+
entries.delete('..')
|
34
|
+
entries
|
35
|
+
end
|
36
|
+
|
37
|
+
module_function :dir_entries
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Raz
|
2
|
+
module Items
|
3
|
+
class Group
|
4
|
+
|
5
|
+
# @return [String]
|
6
|
+
#
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
# @return [Array<String>]
|
10
|
+
#
|
11
|
+
attr_reader :paths
|
12
|
+
|
13
|
+
# @return [Array<String>]
|
14
|
+
#
|
15
|
+
attr_reader :ignored_paths
|
16
|
+
|
17
|
+
# @param name [String] name of this group
|
18
|
+
# @param block [Proc] block where is specified all paths
|
19
|
+
#
|
20
|
+
def initialize(name, &block)
|
21
|
+
@name = name
|
22
|
+
@paths = []
|
23
|
+
@ignored_paths = []
|
24
|
+
|
25
|
+
instance_eval(&block) unless block.nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
## API
|
29
|
+
|
30
|
+
# @param path [String] path to file
|
31
|
+
#
|
32
|
+
def path(path)
|
33
|
+
@paths << path
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param path [String] path to file
|
37
|
+
#
|
38
|
+
def ignore_path(path)
|
39
|
+
@ignored_paths << path
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/raz/restorer.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
|
2
|
+
require 'yaml'
|
3
|
+
require_relative 'file_operations'
|
4
|
+
require_relative 'constants'
|
5
|
+
|
6
|
+
include Raz::Constants
|
7
|
+
|
8
|
+
|
9
|
+
module Raz
|
10
|
+
class Restorer
|
11
|
+
|
12
|
+
# @return [String]
|
13
|
+
#
|
14
|
+
attr_reader :source_path
|
15
|
+
|
16
|
+
# @param source_path [String]
|
17
|
+
#
|
18
|
+
def initialize(source_path)
|
19
|
+
@source_path = source_path
|
20
|
+
end
|
21
|
+
|
22
|
+
def restore
|
23
|
+
# load saved information from YAML
|
24
|
+
@info = YAML.load(File.read(File.join(@source_path, BACKUP_INFO_BASE_PATH)))
|
25
|
+
@orig_home_path = @info[:env]['HOME']
|
26
|
+
|
27
|
+
# load config file
|
28
|
+
config = ConfigFile.new(backup_config_file_path(@source_path))
|
29
|
+
|
30
|
+
# run before procs
|
31
|
+
(config.procs[:before_restore] || []).each do |proc|
|
32
|
+
instance_eval &proc
|
33
|
+
end
|
34
|
+
|
35
|
+
# restore all files
|
36
|
+
@info[:copied_paths].each do |src|
|
37
|
+
dest = destination_path_from_original(src)
|
38
|
+
src = File.join(@source_path, BACKUP_DATA_BASE_PATH, src)
|
39
|
+
|
40
|
+
if File.directory?(dest)
|
41
|
+
FileOperations.dir_entries(src).each do |item|
|
42
|
+
puts "Restoring file to #{File.join(src, item)}".green
|
43
|
+
FileOperations.copy_item(File.join(src, item), File.join(dest, item))
|
44
|
+
end
|
45
|
+
else
|
46
|
+
FileUtils.rmtree(dest) if File.exist?(dest)
|
47
|
+
|
48
|
+
puts "Restoring item to #{dest}".green
|
49
|
+
FileOperations.copy_item(src, dest)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# restore config file to previous location
|
54
|
+
FileOperations.copy_item(config.path, destination_path_from_original(@info[:orig_config_path]))
|
55
|
+
|
56
|
+
# run after procs
|
57
|
+
(config.procs[:after_restore] || []).each do |proc|
|
58
|
+
instance_eval &proc
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# @param path [String]
|
65
|
+
#
|
66
|
+
# @return [String]
|
67
|
+
#
|
68
|
+
def destination_path_from_original(path)
|
69
|
+
path.sub(/^#{Regexp.escape(@orig_home_path)}/, ENV['HOME'])
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/raz/version.rb
ADDED
data/lib/raz.rb
ADDED
data/raz.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'raz/version'
|
7
|
+
|
8
|
+
|
9
|
+
Gem::Specification.new do |spec|
|
10
|
+
spec.name = 'raz'
|
11
|
+
spec.version = Raz::VERSION
|
12
|
+
spec.authors = ['Roman Kříž']
|
13
|
+
spec.email = ['samnung@gmail.com']
|
14
|
+
|
15
|
+
spec.summary = 'Tool to backup and restore files.'
|
16
|
+
spec.homepage = 'https://github.com/samnung/raz'
|
17
|
+
spec.license = 'MIT'
|
18
|
+
|
19
|
+
spec.files = Dir['bin/**/*'] + Dir['lib/**/*.rb'] + %w(raz.gemspec Gemfile LICENSE.txt README.md)
|
20
|
+
spec.bindir = 'bin'
|
21
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ['lib']
|
23
|
+
|
24
|
+
spec.add_runtime_dependency 'gli', '~> 2.13'
|
25
|
+
spec.add_runtime_dependency 'colorize', '~> 0.7'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'bundler', '~> 1.10'
|
28
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
29
|
+
spec.add_development_dependency 'rspec', '~> 3.3'
|
30
|
+
spec.add_development_dependency 'fakefs', '~> 0.6'
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: raz
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Roman Kříž
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: gli
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.13'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.13'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: colorize
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.7'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.10'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.10'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.3'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.3'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: fakefs
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.6'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.6'
|
97
|
+
description:
|
98
|
+
email:
|
99
|
+
- samnung@gmail.com
|
100
|
+
executables:
|
101
|
+
- raz
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- Gemfile
|
106
|
+
- LICENSE.txt
|
107
|
+
- README.md
|
108
|
+
- bin/raz
|
109
|
+
- lib/raz.rb
|
110
|
+
- lib/raz/backuper.rb
|
111
|
+
- lib/raz/config_file.rb
|
112
|
+
- lib/raz/constants.rb
|
113
|
+
- lib/raz/entries.rb
|
114
|
+
- lib/raz/file_operations.rb
|
115
|
+
- lib/raz/items/application.rb
|
116
|
+
- lib/raz/items/group.rb
|
117
|
+
- lib/raz/restorer.rb
|
118
|
+
- lib/raz/version.rb
|
119
|
+
- raz.gemspec
|
120
|
+
homepage: https://github.com/samnung/raz
|
121
|
+
licenses:
|
122
|
+
- MIT
|
123
|
+
metadata: {}
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options: []
|
126
|
+
require_paths:
|
127
|
+
- lib
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
requirements: []
|
139
|
+
rubyforge_project:
|
140
|
+
rubygems_version: 2.4.5.1
|
141
|
+
signing_key:
|
142
|
+
specification_version: 4
|
143
|
+
summary: Tool to backup and restore files.
|
144
|
+
test_files: []
|
145
|
+
has_rdoc:
|