dotfiler 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/LICENSE.txt +21 -0
- data/README.md +302 -0
- data/dotfiler.gemspec +28 -0
- data/exe/dotfiler +7 -0
- data/lib/dotfiler.rb +11 -0
- data/lib/dotfiler/cli/commands.rb +39 -0
- data/lib/dotfiler/cli/commands/add.rb +105 -0
- data/lib/dotfiler/cli/commands/backup.rb +36 -0
- data/lib/dotfiler/cli/commands/command.rb +39 -0
- data/lib/dotfiler/cli/commands/edit.rb +37 -0
- data/lib/dotfiler/cli/commands/init.rb +79 -0
- data/lib/dotfiler/cli/commands/install.rb +69 -0
- data/lib/dotfiler/cli/commands/list.rb +43 -0
- data/lib/dotfiler/cli/commands/remove.rb +39 -0
- data/lib/dotfiler/cli/commands/version.rb +13 -0
- data/lib/dotfiler/config.rb +71 -0
- data/lib/dotfiler/container.rb +58 -0
- data/lib/dotfiler/copier.rb +21 -0
- data/lib/dotfiler/dotfile.rb +18 -0
- data/lib/dotfiler/dotfiles.rb +101 -0
- data/lib/dotfiler/file_system.rb +43 -0
- data/lib/dotfiler/mover.rb +21 -0
- data/lib/dotfiler/path.rb +77 -0
- data/lib/dotfiler/remover.rb +15 -0
- data/lib/dotfiler/shell.rb +60 -0
- data/lib/dotfiler/symlinker.rb +23 -0
- data/lib/dotfiler/version.rb +3 -0
- metadata +156 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
module Dotfiler
|
2
|
+
class Copier
|
3
|
+
include Dotfiler::Import["fs"]
|
4
|
+
|
5
|
+
def call(source, target, options = {})
|
6
|
+
check_paths!(source, target.parent_dir)
|
7
|
+
|
8
|
+
fs.copy(source.to_s, target.to_s, options)
|
9
|
+
|
10
|
+
target.join(source.name)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def check_paths!(*paths)
|
16
|
+
paths.each do |path|
|
17
|
+
raise Error, "Path '#{path}' does not exist" unless path.exists?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Dotfiler
|
2
|
+
class Dotfile
|
3
|
+
attr_reader :name, :link, :path
|
4
|
+
|
5
|
+
def initialize(name:, link:, path:)
|
6
|
+
@name = name
|
7
|
+
|
8
|
+
to_path = Dotfiler.resolve["to_path"]
|
9
|
+
|
10
|
+
@link = to_path.(link)
|
11
|
+
@path = to_path.(path)
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_h
|
15
|
+
{ name: name, link: link.to_s, path: path.to_s }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
require "dotfiler/dotfile"
|
4
|
+
|
5
|
+
module Dotfiler
|
6
|
+
class Dotfiles
|
7
|
+
extend Forwardable
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
include Dotfiler::Import["fs", "config", "to_path"]
|
11
|
+
|
12
|
+
HOME_PLACEHOLDER = "%home%".freeze
|
13
|
+
DOTFILES_PLACEHOLDER = "%dotfiles%".freeze
|
14
|
+
|
15
|
+
def_delegators :config, :home_path, :relative_home_path
|
16
|
+
|
17
|
+
def initialize(args)
|
18
|
+
super(args)
|
19
|
+
load_data!
|
20
|
+
end
|
21
|
+
|
22
|
+
def find(name)
|
23
|
+
@dotfiles.find { |dotfile| dotfile.name == name }
|
24
|
+
end
|
25
|
+
|
26
|
+
def each(&block)
|
27
|
+
@dotfiles.each(&block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def names
|
31
|
+
@dotfiles.map(&:name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def name_taken?(name)
|
35
|
+
names.include?(name)
|
36
|
+
end
|
37
|
+
alias_method :exists?, :name_taken?
|
38
|
+
|
39
|
+
def add!(*args)
|
40
|
+
dotfile = Dotfile.new(*args)
|
41
|
+
|
42
|
+
File.open(config.dotfiles_file_path.to_s, "a") do |file|
|
43
|
+
file << dotfile_to_line(dotfile)
|
44
|
+
end
|
45
|
+
|
46
|
+
reload!
|
47
|
+
end
|
48
|
+
|
49
|
+
def remove!(name)
|
50
|
+
content = @dotfiles.each_with_object("") do |dotfile, result|
|
51
|
+
next if dotfile.name == name
|
52
|
+
|
53
|
+
result << dotfile_to_line(dotfile)
|
54
|
+
end
|
55
|
+
|
56
|
+
File.open(config.dotfiles_file_path.to_s, "w+") { |file| file << content }
|
57
|
+
|
58
|
+
reload!
|
59
|
+
end
|
60
|
+
|
61
|
+
def list
|
62
|
+
@dotfiles.map(&:to_h)
|
63
|
+
end
|
64
|
+
|
65
|
+
def load_data!
|
66
|
+
@dotfiles = if config.set?
|
67
|
+
parse(File.readlines(config.dotfiles_file_path.to_s))
|
68
|
+
else
|
69
|
+
{}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
alias_method :reload!, :load_data!
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def parse(lines)
|
78
|
+
lines.sort.each_with_object([]) do |line, result|
|
79
|
+
sanitized_line = line.gsub("\n", "")
|
80
|
+
|
81
|
+
next if sanitized_line.empty?
|
82
|
+
|
83
|
+
name, link, path = sanitized_line.split(" :: ")
|
84
|
+
|
85
|
+
link = link.sub(HOME_PLACEHOLDER, home_path.to_s)
|
86
|
+
path = path.sub(DOTFILES_PLACEHOLDER, config[:dotfiles])
|
87
|
+
|
88
|
+
result << Dotfile.new(name: name, link: link, path: path)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def dotfile_to_line(dotfile)
|
93
|
+
name, link, path = dotfile.to_h.values_at(:name, :link, :path)
|
94
|
+
|
95
|
+
link = link.sub(home_path.to_s, HOME_PLACEHOLDER)
|
96
|
+
path = path.sub(config[:dotfiles], DOTFILES_PLACEHOLDER)
|
97
|
+
|
98
|
+
[name, link, path].join(" :: ") + "\n"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
|
3
|
+
module Dotfiler
|
4
|
+
class FileSystem
|
5
|
+
attr_reader :utils
|
6
|
+
|
7
|
+
def initialize(utils: FileUtils)
|
8
|
+
@utils = utils
|
9
|
+
end
|
10
|
+
|
11
|
+
def move(source_path, destination_path, *options)
|
12
|
+
utils.move(source_path, destination_path, *options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def copy(source_path, destination_path, *options)
|
16
|
+
utils.cp_r(source_path, destination_path, *options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def symlink(source, destination, *options)
|
20
|
+
utils.symlink(source, destination, *options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def remove(path)
|
24
|
+
utils.remove_entry_secure(path)
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_file(name, content = "")
|
28
|
+
file = File.new(name, "w+")
|
29
|
+
file.write(content)
|
30
|
+
file.close
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_dir(path, *options)
|
34
|
+
utils.mkdir_p(path, *options)
|
35
|
+
end
|
36
|
+
|
37
|
+
def execute(command, *command_options, **options)
|
38
|
+
full_command = ([command] + command_options).join(" ")
|
39
|
+
|
40
|
+
options[:capture] == true ? `#{full_command}` : system(full_command)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Dotfiler
|
2
|
+
class Mover
|
3
|
+
include Dotfiler::Import["fs"]
|
4
|
+
|
5
|
+
def call(source, target, options = { secure: true })
|
6
|
+
check_paths!(source, target.parent_dir)
|
7
|
+
|
8
|
+
fs.move(source.to_s, target.to_s, options)
|
9
|
+
|
10
|
+
target.join(source.name)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def check_paths!(*paths)
|
16
|
+
paths.each do |path|
|
17
|
+
raise Error, "Path '#{path}' does not exist" unless path.exists?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
3
|
+
module Dotfiler
|
4
|
+
class Path
|
5
|
+
include Comparable
|
6
|
+
|
7
|
+
def initialize(raw_path, expand: true)
|
8
|
+
@path = raw_path.nil? ? "" : raw_path
|
9
|
+
@full_path = Pathname.new(raw_path)
|
10
|
+
@full_path = @full_path.expand_path if expand == true
|
11
|
+
end
|
12
|
+
|
13
|
+
def <=>(other_path)
|
14
|
+
self.full <=> other_path.full
|
15
|
+
end
|
16
|
+
|
17
|
+
def full
|
18
|
+
@full_path
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
@full_path.to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
def join(*paths)
|
26
|
+
new_path = @full_path
|
27
|
+
|
28
|
+
paths.each { |path| new_path = new_path.join(path) }
|
29
|
+
|
30
|
+
self.class.new(new_path.to_s)
|
31
|
+
end
|
32
|
+
|
33
|
+
def contains?(other_path)
|
34
|
+
other_path = self.class.new(other_path) if other_path.is_a?(String)
|
35
|
+
|
36
|
+
children.any? { |child| child == other_path }
|
37
|
+
end
|
38
|
+
|
39
|
+
def within?(other_path)
|
40
|
+
other_path = self.class.new(other_path) if other_path.is_a?(String)
|
41
|
+
|
42
|
+
other_path.children.any? { |child| child == self }
|
43
|
+
end
|
44
|
+
|
45
|
+
def children
|
46
|
+
@full_path.children.map { |child| self.class.new(child.to_s) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def parent_dir
|
50
|
+
self.class.new(@full_path.parent.to_s)
|
51
|
+
end
|
52
|
+
|
53
|
+
def real
|
54
|
+
@full_path.realpath
|
55
|
+
end
|
56
|
+
|
57
|
+
def name
|
58
|
+
@full_path.basename
|
59
|
+
end
|
60
|
+
|
61
|
+
def exists?
|
62
|
+
File.exists?(self.to_s)
|
63
|
+
end
|
64
|
+
|
65
|
+
def file?
|
66
|
+
!dir? && File.file?(@full_path)
|
67
|
+
end
|
68
|
+
|
69
|
+
def dir?
|
70
|
+
File.directory?(@full_path)
|
71
|
+
end
|
72
|
+
|
73
|
+
def symlink?
|
74
|
+
File.symlink?(@full_path)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Dotfiler
|
2
|
+
class Remover
|
3
|
+
include Dotfiler::Import["fs"]
|
4
|
+
|
5
|
+
def call(path, only_symlinks: true)
|
6
|
+
if only_symlinks && !path.symlink?
|
7
|
+
raise(Error, "Cannot remove '#{path}' since it is not a symbolic link")
|
8
|
+
end
|
9
|
+
|
10
|
+
fs.remove(path.to_s)
|
11
|
+
|
12
|
+
path
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Dotfiler
|
2
|
+
class Shell
|
3
|
+
TERMINATION_CODES = {
|
4
|
+
clean: 0,
|
5
|
+
info: 0,
|
6
|
+
error: 1
|
7
|
+
}.freeze
|
8
|
+
|
9
|
+
def initialize(output: $stdout, input: $stdin, error: $stderr)
|
10
|
+
@output = output
|
11
|
+
@error = error
|
12
|
+
@input = input
|
13
|
+
end
|
14
|
+
|
15
|
+
def print(content, type = nil)
|
16
|
+
case type
|
17
|
+
when :error
|
18
|
+
error.puts("ERROR: #{content}")
|
19
|
+
when :info
|
20
|
+
output.puts("# #{content}")
|
21
|
+
else
|
22
|
+
output.puts("#{content}")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def prompt(text, available_answers = {})
|
27
|
+
question = text
|
28
|
+
|
29
|
+
if available_answers.any?
|
30
|
+
question += "\n\n"
|
31
|
+
available_answers.each { |key, details| question += " #{key}) #{details[:desc]}\n" }
|
32
|
+
else
|
33
|
+
available_answers = default_prompt_answer
|
34
|
+
question += " [y/N] "
|
35
|
+
end
|
36
|
+
|
37
|
+
print(question)
|
38
|
+
|
39
|
+
answer_key = input.gets.strip.downcase
|
40
|
+
|
41
|
+
return :other unless available_answers.key?(answer_key)
|
42
|
+
|
43
|
+
available_answers[answer_key].fetch(:value)
|
44
|
+
end
|
45
|
+
|
46
|
+
def terminate(type, message: nil)
|
47
|
+
print(message, type) unless message.nil?
|
48
|
+
exit(TERMINATION_CODES[type])
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
attr_reader :output, :error, :input
|
54
|
+
|
55
|
+
def default_prompt_answer
|
56
|
+
{ "y" => { value: :yes }, "n" => { value: :no } }
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Dotfiler
|
2
|
+
class Symlinker
|
3
|
+
include Dotfiler::Import["fs"]
|
4
|
+
|
5
|
+
def call(source, link_path, options = {})
|
6
|
+
check_paths!(source, link_path.parent_dir)
|
7
|
+
|
8
|
+
options[:force] = true if link_path.symlink?
|
9
|
+
|
10
|
+
fs.symlink(source.to_s, link_path.to_s, options)
|
11
|
+
|
12
|
+
link_path
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def check_paths!(*paths)
|
18
|
+
paths.each do |path|
|
19
|
+
raise Error, "Path '#{path}' does not exist" unless path.exists?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dotfiler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aleksandar Radunovic
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-06-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dry-container
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.6.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.6.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dry-auto_inject
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.4.6
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.4.6
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: hanami-cli
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.2.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.2.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.16'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.16'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.0'
|
97
|
+
description: CLI gem for managing dotfiles
|
98
|
+
email:
|
99
|
+
- aleksandar@radunovic.io
|
100
|
+
executables:
|
101
|
+
- dotfiler
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- LICENSE.txt
|
106
|
+
- README.md
|
107
|
+
- dotfiler.gemspec
|
108
|
+
- exe/dotfiler
|
109
|
+
- lib/dotfiler.rb
|
110
|
+
- lib/dotfiler/cli/commands.rb
|
111
|
+
- lib/dotfiler/cli/commands/add.rb
|
112
|
+
- lib/dotfiler/cli/commands/backup.rb
|
113
|
+
- lib/dotfiler/cli/commands/command.rb
|
114
|
+
- lib/dotfiler/cli/commands/edit.rb
|
115
|
+
- lib/dotfiler/cli/commands/init.rb
|
116
|
+
- lib/dotfiler/cli/commands/install.rb
|
117
|
+
- lib/dotfiler/cli/commands/list.rb
|
118
|
+
- lib/dotfiler/cli/commands/remove.rb
|
119
|
+
- lib/dotfiler/cli/commands/version.rb
|
120
|
+
- lib/dotfiler/config.rb
|
121
|
+
- lib/dotfiler/container.rb
|
122
|
+
- lib/dotfiler/copier.rb
|
123
|
+
- lib/dotfiler/dotfile.rb
|
124
|
+
- lib/dotfiler/dotfiles.rb
|
125
|
+
- lib/dotfiler/file_system.rb
|
126
|
+
- lib/dotfiler/mover.rb
|
127
|
+
- lib/dotfiler/path.rb
|
128
|
+
- lib/dotfiler/remover.rb
|
129
|
+
- lib/dotfiler/shell.rb
|
130
|
+
- lib/dotfiler/symlinker.rb
|
131
|
+
- lib/dotfiler/version.rb
|
132
|
+
homepage: https://github.com/aradunovic/dotfiler
|
133
|
+
licenses:
|
134
|
+
- MIT
|
135
|
+
metadata: {}
|
136
|
+
post_install_message:
|
137
|
+
rdoc_options: []
|
138
|
+
require_paths:
|
139
|
+
- lib
|
140
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '2.3'
|
145
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
requirements: []
|
151
|
+
rubyforge_project:
|
152
|
+
rubygems_version: 2.6.8
|
153
|
+
signing_key:
|
154
|
+
specification_version: 4
|
155
|
+
summary: CLI gem for managing dotfiles
|
156
|
+
test_files: []
|