dotsync 0.1.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.
- checksums.yaml +7 -0
- data/.github/workflows/gem-push.yml +41 -0
- data/.github/workflows/gem-push.yml.bak +23 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +3 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +113 -0
- data/LICENSE.txt +21 -0
- data/README.md +98 -0
- data/Rakefile +53 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/dotsync.gemspec +38 -0
- data/lib/dotsync/actions/base_action.rb +43 -0
- data/lib/dotsync/actions/config/base_config.rb +68 -0
- data/lib/dotsync/actions/config/mapping_entry.rb +65 -0
- data/lib/dotsync/actions/config/pull_action_config.rb +33 -0
- data/lib/dotsync/actions/config/push_action_config.rb +27 -0
- data/lib/dotsync/actions/config/watch_action_config.rb +12 -0
- data/lib/dotsync/actions/config/xdg_base_directory_spec.rb +20 -0
- data/lib/dotsync/actions/pull_action.rb +74 -0
- data/lib/dotsync/actions/push_action.rb +30 -0
- data/lib/dotsync/actions/watch_action.rb +75 -0
- data/lib/dotsync/errors.rb +3 -0
- data/lib/dotsync/file_transfer.rb +49 -0
- data/lib/dotsync/logger.rb +61 -0
- data/lib/dotsync/path_utils.rb +26 -0
- data/lib/dotsync/runner.rb +36 -0
- data/lib/dotsync/tasks/actions.rake +10 -0
- data/lib/dotsync/version.rb +3 -0
- data/lib/dotsync.rb +45 -0
- metadata +219 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module Dotsync
|
|
2
|
+
class MappingEntry
|
|
3
|
+
include Dotsync::PathUtils
|
|
4
|
+
|
|
5
|
+
attr_reader :original_src, :original_dest, :original_ignores
|
|
6
|
+
|
|
7
|
+
def initialize(hash)
|
|
8
|
+
@original_src = hash["src"]
|
|
9
|
+
@original_dest = hash["dest"]
|
|
10
|
+
@original_ignores = Array(hash["ignore"])
|
|
11
|
+
@force = hash["force"] || false
|
|
12
|
+
|
|
13
|
+
@sanitized_src = sanitize_path(@original_src)
|
|
14
|
+
@sanitized_dest = sanitize_path(@original_dest)
|
|
15
|
+
@sanitized_ignore = @original_ignores.map { |path| File.join(@sanitized_src, path) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def src
|
|
19
|
+
@sanitized_src
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def dest
|
|
23
|
+
@sanitized_dest
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ignores
|
|
27
|
+
@sanitized_ignore
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def force?
|
|
31
|
+
@force
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def valid?
|
|
35
|
+
File.exist?(@sanitized_src) && File.exist?(@sanitized_dest)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_s
|
|
39
|
+
force_icon = force? ? " #{icon_delete}" : ""
|
|
40
|
+
"#{original_src} → #{original_dest}#{force_icon}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def applied_to(path)
|
|
44
|
+
relative_path = if Pathname.new(path).absolute?
|
|
45
|
+
path.delete_prefix(File.join(src, "/"))
|
|
46
|
+
else
|
|
47
|
+
path
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
Dotsync::MappingEntry.new(
|
|
51
|
+
"src" => File.join(@original_src, relative_path),
|
|
52
|
+
"dest" => File.join(@original_dest, relative_path),
|
|
53
|
+
"force" => @force,
|
|
54
|
+
"ignore" => @original_ignores
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def icon_delete
|
|
61
|
+
Dotsync::Logger::ICONS[:delete]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Dotsync
|
|
2
|
+
class PullActionConfig < BaseConfig
|
|
3
|
+
include XDGBaseDirectorySpec
|
|
4
|
+
|
|
5
|
+
def mappings
|
|
6
|
+
mappings_list = section["mappings"]
|
|
7
|
+
Array(mappings_list).map { |mapping| Dotsync::MappingEntry.new(mapping) }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def backups_root
|
|
11
|
+
File.join(xdg_data_home, "dotsync", "backups")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
SECTION_NAME = "pull"
|
|
17
|
+
|
|
18
|
+
def section_name
|
|
19
|
+
SECTION_NAME
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def validate!
|
|
23
|
+
validate_section_present!
|
|
24
|
+
validate_key_present! "mappings"
|
|
25
|
+
|
|
26
|
+
Array(section["mappings"]).each_with_index do |mapping, index|
|
|
27
|
+
unless mapping.is_a?(Hash) && mapping.key?("src") && mapping.key?("dest")
|
|
28
|
+
raise "Configuration error in mapping ##{index + 1}: Each mapping must have 'src' and 'dest' keys."
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Dotsync
|
|
2
|
+
class PushActionConfig < BaseConfig
|
|
3
|
+
def mappings
|
|
4
|
+
mappings_list = section["mappings"]
|
|
5
|
+
Array(mappings_list).map { |mapping| Dotsync::MappingEntry.new(mapping) }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
SECTION_NAME = "push"
|
|
11
|
+
|
|
12
|
+
def section_name
|
|
13
|
+
SECTION_NAME
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def validate!
|
|
17
|
+
validate_section_present!
|
|
18
|
+
validate_key_present! "mappings"
|
|
19
|
+
|
|
20
|
+
Array(section["mappings"]).each_with_index do |mapping, index|
|
|
21
|
+
unless mapping.is_a?(Hash) && mapping.key?("src") && mapping.key?("dest")
|
|
22
|
+
raise "Configuration error in mapping ##{index + 1}: Each mapping must have 'src' and 'dest' keys."
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Dotsync
|
|
2
|
+
# https://specifications.freedesktop.org/basedir-spec/latest/
|
|
3
|
+
module XDGBaseDirectorySpec
|
|
4
|
+
def xdg_data_home
|
|
5
|
+
File.expand_path(ENV["XDG_DATA_HOME"] || "$HOME/.local/share")
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def xdf_config_home
|
|
9
|
+
File.expand_path(ENV["XDG_CONFIG_HOME"] || "$HOME/.config")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def xdf_cache_home
|
|
13
|
+
File.expand_path(ENV["XDG_CACHE_HOME"] || "$HOME/.cache")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def xdf_bin_home
|
|
17
|
+
File.expand_path(ENV["XDG_BIN_HOME"] || "$HOME/.local/bin")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module Dotsync
|
|
2
|
+
class PullAction < BaseAction
|
|
3
|
+
def_delegator :@config, :mappings
|
|
4
|
+
def_delegator :@config, :backups_root
|
|
5
|
+
|
|
6
|
+
def execute
|
|
7
|
+
show_config
|
|
8
|
+
if create_backup
|
|
9
|
+
show_backup
|
|
10
|
+
purge_old_backups
|
|
11
|
+
end
|
|
12
|
+
pull_dotfiles
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def show_config
|
|
18
|
+
info("Mappings:", icon: :source_dest)
|
|
19
|
+
mappings.each do |mapping|
|
|
20
|
+
force_icon = mapping.force? ? " #{icon_delete}" : ""
|
|
21
|
+
info(" src: #{mapping.original_src} -> dest: #{mapping.original_dest}#{force_icon}", icon: :copy)
|
|
22
|
+
info(" ignores: #{mapping.ignores.join(', ')}", icon: :exclude) if mapping.ignores.any?
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def show_backup
|
|
27
|
+
action("Backup created:", icon: :backup)
|
|
28
|
+
info(" #{backup_path}")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def timestamp
|
|
32
|
+
Time.now.strftime('%Y%m%d%H%M%S')
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def backup_path
|
|
36
|
+
@backup_path ||= File.join(backups_root, timestamp)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def create_backup
|
|
40
|
+
return false unless mappings.any? { |mapping| File.exist?(mapping.dest) }
|
|
41
|
+
FileUtils.mkdir_p(backup_path)
|
|
42
|
+
mappings.each do |mapping|
|
|
43
|
+
next unless File.exist?(mapping.dest)
|
|
44
|
+
if File.file?(mapping.src)
|
|
45
|
+
FileUtils.cp(mapping.dest, File.join(backup_path, File.basename(mapping.dest)))
|
|
46
|
+
else
|
|
47
|
+
FileUtils.cp_r(mapping.dest, File.join(backup_path, File.basename(mapping.dest)))
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def purge_old_backups
|
|
54
|
+
backups = Dir[File.join(backups_root, '*')].sort.reverse
|
|
55
|
+
if backups.size > 10
|
|
56
|
+
info("Maximum of 10 backups retained")
|
|
57
|
+
|
|
58
|
+
backups[10..].each do |path|
|
|
59
|
+
FileUtils.rm_rf(path)
|
|
60
|
+
info("Old backup deleted: #{path}", icon: :delete)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def pull_dotfiles
|
|
66
|
+
mappings.each { |mapping| Dotsync::FileTransfer.new(mapping).transfer }
|
|
67
|
+
action("Dotfiles pulled", icon: :copy)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def icon_delete
|
|
71
|
+
Dotsync::Logger::ICONS[:delete]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Dotsync
|
|
2
|
+
class PushAction < BaseAction
|
|
3
|
+
def_delegator :@config, :mappings
|
|
4
|
+
|
|
5
|
+
def execute
|
|
6
|
+
show_config
|
|
7
|
+
push_dotfiles
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def show_config
|
|
13
|
+
info("Mappings:", icon: :source_dest)
|
|
14
|
+
mappings.each do |mapping|
|
|
15
|
+
force_icon = mapping.force? ? " #{icon_delete}" : ""
|
|
16
|
+
info(" src: #{mapping.original_src} -> dest: #{mapping.original_dest}#{force_icon}", icon: :copy)
|
|
17
|
+
info(" ignores: #{mapping.original_ignores.join(', ')}", icon: :exclude) if mapping.ignores.any?
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def push_dotfiles
|
|
22
|
+
mappings.each { |mapping| Dotsync::FileTransfer.new(mapping).transfer }
|
|
23
|
+
action("Dotfiles pushed", icon: :copy)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def icon_delete
|
|
27
|
+
Dotsync::Logger::ICONS[:delete]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module Dotsync
|
|
2
|
+
class WatchAction < BaseAction
|
|
3
|
+
def_delegator :@config, :mappings
|
|
4
|
+
|
|
5
|
+
def initialize(config, logger)
|
|
6
|
+
super
|
|
7
|
+
setup_listeners
|
|
8
|
+
setup_logger_thread
|
|
9
|
+
setup_signal_trap
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def execute
|
|
13
|
+
show_config
|
|
14
|
+
|
|
15
|
+
@listeners.each(&:start)
|
|
16
|
+
|
|
17
|
+
logger.action("Listening for changes...", icon: :listen)
|
|
18
|
+
info("Press Ctrl+C to exit.")
|
|
19
|
+
sleep
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def show_config
|
|
25
|
+
info("Mappings:", icon: :watch)
|
|
26
|
+
mappings.each do |mapping|
|
|
27
|
+
info(" #{mapping}", icon: :copy)
|
|
28
|
+
info(" Excludes: #{mapping.ignores.join(', ')}", icon: :exclude) if mapping.ignores.any?
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def setup_listeners
|
|
33
|
+
@listeners = mappings.map do |mapping|
|
|
34
|
+
src = mapping.src
|
|
35
|
+
|
|
36
|
+
# Determine the base directory to watch. If it's a directory, use it directly.
|
|
37
|
+
# Otherwise, use its parent directory.
|
|
38
|
+
base = File.directory?(src) ? src : File.dirname(src)
|
|
39
|
+
|
|
40
|
+
options = {}
|
|
41
|
+
# If the watched path is a file, create a pattern to match its name.
|
|
42
|
+
options[:pattern] = /^#{Regexp.escape(File.basename(src))}$/ unless File.directory?(src)
|
|
43
|
+
options[:ignore] = Regexp.union(mapping.ignores) if mapping.ignores.any?
|
|
44
|
+
|
|
45
|
+
Listen.to(base, options) do |modified, added, removed|
|
|
46
|
+
handle_file_changes(mapping, modified, added, removed)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def handle_file_changes(mapping, modified, added, removed)
|
|
52
|
+
(modified + added).each do |path|
|
|
53
|
+
new_mapping = mapping.applied_to(path)
|
|
54
|
+
logger.info("Copied file: #{new_mapping.original_src}", icon: :copy)
|
|
55
|
+
Dotsync::FileTransfer.new(new_mapping).transfer
|
|
56
|
+
end
|
|
57
|
+
removed.each do |path|
|
|
58
|
+
logger.info("File removed: #{path}", icon: :delete)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def setup_signal_trap
|
|
63
|
+
listeners = @listeners.dup
|
|
64
|
+
Signal.trap("INT") do
|
|
65
|
+
# Using a new thread to handle the signal trap context,
|
|
66
|
+
# as Signal.trap runs in a more restrictive environment
|
|
67
|
+
Thread.new do
|
|
68
|
+
logger.action("Shutting down listeners...", icon: :bell)
|
|
69
|
+
listeners.each(&:stop)
|
|
70
|
+
exit
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Dotsync
|
|
2
|
+
class FileTransfer
|
|
3
|
+
attr_reader :ignores
|
|
4
|
+
|
|
5
|
+
def initialize(config)
|
|
6
|
+
@src = config.src
|
|
7
|
+
@dest = config.dest
|
|
8
|
+
@force = config.force?
|
|
9
|
+
@ignores = config.ignores || []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def transfer
|
|
13
|
+
if File.file?(@src)
|
|
14
|
+
transfer_file(@src, @dest)
|
|
15
|
+
else
|
|
16
|
+
FileUtils.rm_rf(Dir.glob(File.join(@dest, '*'))) if @force
|
|
17
|
+
transfer_folder(@src, @dest)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def transfer_file(file_src, file_dest)
|
|
24
|
+
FileUtils.mkdir_p(File.dirname(file_dest))
|
|
25
|
+
FileUtils.cp(file_src, file_dest)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def transfer_folder(folder_src, folder_dest)
|
|
29
|
+
FileUtils.mkdir_p(folder_dest)
|
|
30
|
+
Dir.glob("#{folder_src}/*", File::FNM_DOTMATCH).each do |path|
|
|
31
|
+
next if ['.', '..'].include?(File.basename(path))
|
|
32
|
+
|
|
33
|
+
full_path = File.expand_path(path)
|
|
34
|
+
next if ignore?(full_path)
|
|
35
|
+
|
|
36
|
+
target = File.join(folder_dest, File.basename(path))
|
|
37
|
+
if File.file?(full_path)
|
|
38
|
+
FileUtils.cp(full_path, target)
|
|
39
|
+
else
|
|
40
|
+
transfer_folder(full_path, target)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def ignore?(path)
|
|
46
|
+
@ignores.any? { |ignore| path.start_with?(ignore) }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Dotsync
|
|
2
|
+
class Logger
|
|
3
|
+
attr_accessor :output
|
|
4
|
+
|
|
5
|
+
# 🎨 Nerd Font Icons
|
|
6
|
+
ICONS = {
|
|
7
|
+
info: " ",
|
|
8
|
+
listen: " ",
|
|
9
|
+
error: " ",
|
|
10
|
+
watch: " ",
|
|
11
|
+
source: " ", # ",
|
|
12
|
+
dest: " ", # " ",
|
|
13
|
+
delete: " ",
|
|
14
|
+
bell: " ",
|
|
15
|
+
copy: " ",
|
|
16
|
+
skip: " ",
|
|
17
|
+
done: " ",
|
|
18
|
+
backup: " ",
|
|
19
|
+
clean: " ",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def initialize(output = $stdout)
|
|
23
|
+
@output = output
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def info(message, options = {})
|
|
27
|
+
log(:info, message, options)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def action(message, options = {})
|
|
31
|
+
log(:action, message, options)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def success(message)
|
|
35
|
+
log(:success, message, icon: :done)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def error(message)
|
|
39
|
+
log(:error, message, icon: :error)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def warning(message, options = {})
|
|
43
|
+
log(:warning, message, options)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def log(type, message, options = {})
|
|
47
|
+
icon = options[:icon]
|
|
48
|
+
color = {
|
|
49
|
+
info: 103, action: 153, error: 196, event: 141, warning: 31, copy: 32,
|
|
50
|
+
skip: 33, done: 32, backup: 35,
|
|
51
|
+
clean: 34
|
|
52
|
+
}[type] || 0
|
|
53
|
+
|
|
54
|
+
if icon.nil?
|
|
55
|
+
@output.puts message
|
|
56
|
+
else
|
|
57
|
+
@output.puts "\e[38;5;#{color}m\e[1m#{ICONS[icon]}#{message}\e[0m"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Dotsync
|
|
2
|
+
module PathUtils
|
|
3
|
+
def expand_env_vars(path)
|
|
4
|
+
path.gsub(/\$(\w+)/) { ENV[$1] }
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Translates /tmp paths to /private/tmp paths on macOS
|
|
8
|
+
# Retains other paths as-is
|
|
9
|
+
# @param [String] path The input path to translate
|
|
10
|
+
# @return [String] The translated path
|
|
11
|
+
def translate_tmp_path(path)
|
|
12
|
+
if path.start_with?('/tmp') && RUBY_PLATFORM.include?('darwin')
|
|
13
|
+
path.sub('/tmp', '/private/tmp')
|
|
14
|
+
else
|
|
15
|
+
path
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Sanitizes a given path by expanding it and translating /tmp to /private/tmp
|
|
20
|
+
# @param [String] path The input path to sanitize
|
|
21
|
+
# @return [String] The sanitized path
|
|
22
|
+
def sanitize_path(path)
|
|
23
|
+
translate_tmp_path(File.expand_path(expand_env_vars(path)))
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Dotsync
|
|
2
|
+
class Runner
|
|
3
|
+
def initialize(logger: nil)
|
|
4
|
+
@logger = logger || Dotsync::Logger.new
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# action_name should be a symbol, e.g., :pull, :watch, :sync
|
|
8
|
+
def run(action_name)
|
|
9
|
+
begin
|
|
10
|
+
action_class = Dotsync.const_get("#{camelize(action_name.to_s)}Action")
|
|
11
|
+
config_class = Dotsync.const_get("#{camelize(action_name.to_s)}ActionConfig")
|
|
12
|
+
|
|
13
|
+
config = config_class.new(Dotsync.config_path)
|
|
14
|
+
action = action_class.new(config, @logger)
|
|
15
|
+
action.execute
|
|
16
|
+
rescue ConfigError => e
|
|
17
|
+
@logger.error("[#{action_name}] config error:")
|
|
18
|
+
@logger.info(e.message)
|
|
19
|
+
rescue NameError => e
|
|
20
|
+
@logger.error("Unknown action '#{action_name}':")
|
|
21
|
+
@logger.info(e.message)
|
|
22
|
+
rescue => e
|
|
23
|
+
@logger.error("Error running '#{action_name}':")
|
|
24
|
+
@logger.info(e.message)
|
|
25
|
+
raise
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# Utility to convert 'pull' to 'Pull', 'sync' to 'Sync', etc.
|
|
32
|
+
def camelize(str)
|
|
33
|
+
str.split('_').map(&:capitalize).join
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/dotsync.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Libs dependencies
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require 'listen'
|
|
4
|
+
require 'toml-rb'
|
|
5
|
+
require 'logger'
|
|
6
|
+
require 'forwardable' # Ruby standard library
|
|
7
|
+
require 'ostruct'
|
|
8
|
+
|
|
9
|
+
# Errors
|
|
10
|
+
require_relative "dotsync/errors"
|
|
11
|
+
|
|
12
|
+
# Utils
|
|
13
|
+
require_relative 'dotsync/logger'
|
|
14
|
+
require_relative 'dotsync/file_transfer'
|
|
15
|
+
require_relative 'dotsync/path_utils'
|
|
16
|
+
|
|
17
|
+
# Config
|
|
18
|
+
require_relative "dotsync/actions/config/xdg_base_directory_spec"
|
|
19
|
+
require_relative "dotsync/actions/config/mapping_entry"
|
|
20
|
+
require_relative "dotsync/actions/config/base_config"
|
|
21
|
+
require_relative "dotsync/actions/config/pull_action_config"
|
|
22
|
+
require_relative "dotsync/actions/config/push_action_config"
|
|
23
|
+
require_relative "dotsync/actions/config/watch_action_config"
|
|
24
|
+
|
|
25
|
+
# Actions
|
|
26
|
+
require_relative "dotsync/actions/base_action"
|
|
27
|
+
require_relative "dotsync/actions/pull_action"
|
|
28
|
+
require_relative "dotsync/actions/push_action"
|
|
29
|
+
require_relative "dotsync/actions/watch_action"
|
|
30
|
+
|
|
31
|
+
require_relative 'dotsync/runner'
|
|
32
|
+
|
|
33
|
+
require_relative "dotsync/version"
|
|
34
|
+
|
|
35
|
+
module Dotsync
|
|
36
|
+
class Error < StandardError; end
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
attr_writer :config_path
|
|
40
|
+
|
|
41
|
+
def config_path
|
|
42
|
+
@config_path ||= ENV['DOTSYNC_CONFIG'] || "~/.config/dotsync.toml"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|