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,36 @@
|
|
1
|
+
module Dotfiler
|
2
|
+
module CLI
|
3
|
+
module Commands
|
4
|
+
class Backup < Command
|
5
|
+
BACKUP_DIR = ".dotfiler_backup".freeze
|
6
|
+
TIMESTAMP_FORMAT = "%Y_%m_%d_%H_%M_%S".freeze
|
7
|
+
|
8
|
+
include Dotfiler::Import["copier", "remover"]
|
9
|
+
|
10
|
+
desc "Backup existing dotfiles directory"
|
11
|
+
|
12
|
+
def call(*)
|
13
|
+
handle_errors do
|
14
|
+
info("Backing up dotfiles directory (#{dotfiles_path}) to #{backup_dir_path}...")
|
15
|
+
copier.call(dotfiles_path, backup_dir_path)
|
16
|
+
remover.call(backup_dir_path.join(".git"), only_symlinks: false)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def backup_dir_path
|
23
|
+
config.home_path.join("#{BACKUP_DIR}_#{current_timestamp}")
|
24
|
+
end
|
25
|
+
|
26
|
+
def current_timestamp
|
27
|
+
Time.now.strftime(TIMESTAMP_FORMAT)
|
28
|
+
end
|
29
|
+
|
30
|
+
def dotfiles_path
|
31
|
+
to_path.(config[:dotfiles])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Dotfiler
|
2
|
+
module CLI
|
3
|
+
module Commands
|
4
|
+
class Command < Hanami::CLI::Command
|
5
|
+
include Dotfiler::Import["config", "to_path", "shell"]
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def handle_errors(&block)
|
10
|
+
begin
|
11
|
+
yield
|
12
|
+
rescue Dotfiler::Error => e
|
13
|
+
error!(e.message)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def print(*args)
|
18
|
+
shell.print(*args)
|
19
|
+
end
|
20
|
+
|
21
|
+
def info(message)
|
22
|
+
print(message, :info)
|
23
|
+
end
|
24
|
+
|
25
|
+
def error!(message)
|
26
|
+
terminate!(:error, message: message)
|
27
|
+
end
|
28
|
+
|
29
|
+
def terminate!(*args)
|
30
|
+
shell.terminate(*args)
|
31
|
+
end
|
32
|
+
|
33
|
+
def prompt(*args)
|
34
|
+
shell.prompt(*args)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Dotfiler
|
2
|
+
module CLI
|
3
|
+
module Commands
|
4
|
+
class Edit < Command
|
5
|
+
include Dotfiler::Import["fs", "dotfiles"]
|
6
|
+
|
7
|
+
desc "Edit specified dotfile. By default, dotfile will be opened in $EDITOR"
|
8
|
+
|
9
|
+
argument :name, required: true, desc: "Name of the dotfile you want to edit"
|
10
|
+
option :with, aliases: ["w"], required: false, desc: "Editor in which to open the specified dotfile"
|
11
|
+
|
12
|
+
def call(name:, **options)
|
13
|
+
handle_errors do
|
14
|
+
validate_name!(name)
|
15
|
+
|
16
|
+
dotfile_path = dotfiles.find(name).path.to_s
|
17
|
+
editor = options.fetch(:with, default_editor)
|
18
|
+
|
19
|
+
error!("Editor is not specified. Either set the '$EDITOR' environment variable or provide '--with' option") unless editor
|
20
|
+
|
21
|
+
fs.execute(editor, dotfile_path)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def validate_name!(name)
|
28
|
+
error!("Dotfile '#{name}' not found") unless dotfiles.exists?(name)
|
29
|
+
end
|
30
|
+
|
31
|
+
def default_editor
|
32
|
+
ENV["EDITOR"]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Dotfiler
|
2
|
+
module CLI
|
3
|
+
module Commands
|
4
|
+
class Init < Command
|
5
|
+
include Dotfiler::Import["fs"]
|
6
|
+
|
7
|
+
desc "Create config file. Create dotfiles directory at specified path. Initialize git repo."
|
8
|
+
|
9
|
+
argument :path, required: true, desc: "Path where the Dotfiles directory will be created"
|
10
|
+
option :git, type: :boolean, default: true, desc: "Initialize git repo for dotfiles directory"
|
11
|
+
|
12
|
+
def call(path:, **options)
|
13
|
+
handle_errors do
|
14
|
+
dotfiles_path = to_path.(path)
|
15
|
+
|
16
|
+
create_config_file(config_file_contents(dotfiles_path))
|
17
|
+
create_dotfiles_dir(dotfiles_path)
|
18
|
+
create_dotfiles_file(dotfiles_path)
|
19
|
+
initialize_vcs_repo(dotfiles_path) unless options[:git] == false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def config_file_contents(dotfiles_path)
|
26
|
+
"dotfiles: #{dotfiles_path.to_s}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_config_file(contents)
|
30
|
+
if config.file_path.exists?
|
31
|
+
answer = prompt("Config file (#{config.file_path}) already exists. Would you like to overwrite it?")
|
32
|
+
|
33
|
+
return unless answer == :yes
|
34
|
+
end
|
35
|
+
|
36
|
+
info("Creating config file (#{config.file_path})...")
|
37
|
+
fs.create_file(config.file_path.to_s, contents)
|
38
|
+
end
|
39
|
+
|
40
|
+
def create_dotfiles_dir(dotfiles_path)
|
41
|
+
if dotfiles_path.exists?
|
42
|
+
answer = prompt("Dotfiles directory (#{dotfiles_path}) already exists. Would you like to overwrite it?")
|
43
|
+
|
44
|
+
return unless answer == :yes
|
45
|
+
|
46
|
+
info("Removing existing dotfiles directory (#{dotfiles_path})...")
|
47
|
+
fs.remove(dotfiles_path.to_s)
|
48
|
+
end
|
49
|
+
|
50
|
+
info("Creating dotfiles directory (#{dotfiles_path})...")
|
51
|
+
fs.create_dir(dotfiles_path.to_s)
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_dotfiles_file(dotfiles_path)
|
55
|
+
dotfiles_file = dotfiles_path.join(config.dotfiles_file_name)
|
56
|
+
|
57
|
+
if dotfiles_file.exists?
|
58
|
+
answer = prompt("Dotfiles file (#{dotfiles_file}) already exists. Would you like to overwrite it?")
|
59
|
+
|
60
|
+
return unless answer == :yes
|
61
|
+
end
|
62
|
+
|
63
|
+
info("Creating dotfiles file (#{dotfiles_file})...")
|
64
|
+
fs.create_file(dotfiles_file.to_s)
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize_vcs_repo(dotfiles_path)
|
68
|
+
if dotfiles_path.join(".git").exists?
|
69
|
+
answer = prompt("Dotfiles dir (#{dotfiles_path}) is already a git repository. Would you like to reinitialize it?")
|
70
|
+
|
71
|
+
return unless answer == :yes
|
72
|
+
end
|
73
|
+
|
74
|
+
info(fs.execute("git", "init", dotfiles_path.to_s, capture: true))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Dotfiler
|
2
|
+
module CLI
|
3
|
+
module Commands
|
4
|
+
class Install < Command
|
5
|
+
BACKUP_DIR = "dotfiler_installation_backup".freeze
|
6
|
+
TIMESTAMP_FORMAT = "%Y_%m_%d_%H_%M_%S".freeze
|
7
|
+
|
8
|
+
include Dotfiler::Import[ "fs", "dotfiles", "mover", "symlinker"]
|
9
|
+
|
10
|
+
desc "Install dotfiles from existing dotfiles directory"
|
11
|
+
|
12
|
+
argument :path, required: true, desc: "Path to existing dotfiles directory"
|
13
|
+
|
14
|
+
def call(path: , **options)
|
15
|
+
handle_errors do
|
16
|
+
dotfiles_path = to_path.(path)
|
17
|
+
|
18
|
+
validate_dotfiles_file!(dotfiles_path)
|
19
|
+
|
20
|
+
configure_dotfiles_dir(dotfiles_path)
|
21
|
+
|
22
|
+
config.reload!
|
23
|
+
dotfiles.config.reload!
|
24
|
+
dotfiles.reload!
|
25
|
+
|
26
|
+
create_symlinks
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def validate_dotfiles_file!(dotfiles_path)
|
33
|
+
dotfiles_path = dotfiles_path.join(config.dotfiles_file_name)
|
34
|
+
|
35
|
+
return if dotfiles_path.file?
|
36
|
+
|
37
|
+
error!("Dotfiles file (#{dotfiles_path}) not found...")
|
38
|
+
end
|
39
|
+
|
40
|
+
def configure_dotfiles_dir(dotfiles_path)
|
41
|
+
config.update!(dotfiles: dotfiles_path.to_s)
|
42
|
+
end
|
43
|
+
|
44
|
+
def create_symlinks
|
45
|
+
dotfiles.each do |dotfile|
|
46
|
+
backup(dotfile.link) if dotfile.link.exists? && !dotfile.link.symlink?
|
47
|
+
|
48
|
+
info("Symlinking dotfile (#{dotfile.path}) to #{dotfile.link}...")
|
49
|
+
symlinker.call(dotfile.path, dotfile.link, force: true)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def backup(path)
|
54
|
+
fs.create_dir(backup_dir_path.to_s) unless backup_dir_path.exists?
|
55
|
+
info("Backing up #{path} to #{backup_dir_path}...")
|
56
|
+
mover.call(path, backup_dir_path)
|
57
|
+
end
|
58
|
+
|
59
|
+
def backup_dir_path
|
60
|
+
@_backup_dir_path ||= config.home_path.join("#{BACKUP_DIR}_#{current_timestamp}")
|
61
|
+
end
|
62
|
+
|
63
|
+
def current_timestamp
|
64
|
+
Time.now.strftime(TIMESTAMP_FORMAT)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Dotfiler
|
2
|
+
module CLI
|
3
|
+
module Commands
|
4
|
+
class List < Command
|
5
|
+
include Dotfiler::Import["dotfiles"]
|
6
|
+
|
7
|
+
desc "List all managed dotfiles"
|
8
|
+
|
9
|
+
argument :names, desc: "List only names of managed dotfiles"
|
10
|
+
|
11
|
+
def call(names: nil)
|
12
|
+
handle_errors do
|
13
|
+
if dotfiles.list.empty?
|
14
|
+
terminate!(:info, message: "No dotfiles are managed at the moment")
|
15
|
+
else
|
16
|
+
content = generate_list(names_only: !names.nil?)
|
17
|
+
shell.print(content)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def generate_list(names_only: false)
|
25
|
+
content = ""
|
26
|
+
|
27
|
+
dotfiles.each do |dotfile|
|
28
|
+
name, link, path = dotfile.to_h.values_at(:name, :link, :path)
|
29
|
+
|
30
|
+
content += " #{name}\n"
|
31
|
+
|
32
|
+
if !names_only
|
33
|
+
content += " - LINK: #{link}\n"
|
34
|
+
content += " - PATH: #{path}\n\n"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
content
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Dotfiler
|
2
|
+
module CLI
|
3
|
+
module Commands
|
4
|
+
class Remove < Command
|
5
|
+
include Dotfiler::Import["dotfiles", "mover", "remover"]
|
6
|
+
|
7
|
+
desc "Remove specified dotfile from dotfiles and restore it to it's original location"
|
8
|
+
|
9
|
+
argument :name, required: true, desc: "Name of the dotfile that should be unlinked"
|
10
|
+
|
11
|
+
def call(name:)
|
12
|
+
handle_errors do
|
13
|
+
validate_name!(name)
|
14
|
+
|
15
|
+
dotfile = dotfiles.find(name)
|
16
|
+
|
17
|
+
info("Removing symlink (#{dotfile.link})...")
|
18
|
+
remover.call(dotfile.link)
|
19
|
+
|
20
|
+
info("Restoring dotfile (#{dotfile.path}) to its original location (#{dotfile.link})...")
|
21
|
+
mover.call(dotfile.path, dotfile.link)
|
22
|
+
|
23
|
+
|
24
|
+
info("Removing '#{name}' from dotfiles...")
|
25
|
+
dotfiles.remove!(name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def validate_name!(name)
|
32
|
+
return if dotfiles.exists?(name)
|
33
|
+
|
34
|
+
shell.terminate(:error, message: "Dotfile with the name '#{name}' does not exist")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module Dotfiler
|
4
|
+
class Config
|
5
|
+
CONFIG_FILE = ".dotfiler".freeze
|
6
|
+
DOTFILES_FILE = ".dotfiles".freeze
|
7
|
+
RELATIVE_HOME_PATH = "~".freeze
|
8
|
+
|
9
|
+
include Dotfiler::Import["fs", "to_path"]
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
super
|
13
|
+
@data = load_data
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](setting)
|
17
|
+
@data[setting]
|
18
|
+
end
|
19
|
+
|
20
|
+
def file_path
|
21
|
+
@_file_path ||= home_path.join(CONFIG_FILE)
|
22
|
+
end
|
23
|
+
|
24
|
+
def home_path
|
25
|
+
@_home_path ||= path(ENV["DOTFILER_HOME"] || Dir.home)
|
26
|
+
end
|
27
|
+
|
28
|
+
def relative_home_path
|
29
|
+
@_relative_home_path ||= path(RELATIVE_HOME_PATH, expand: false)
|
30
|
+
end
|
31
|
+
|
32
|
+
def set?
|
33
|
+
file_path.exists?
|
34
|
+
end
|
35
|
+
|
36
|
+
def dotfiles_file_path
|
37
|
+
path(self[:dotfiles]).join(dotfiles_file_name) if set?
|
38
|
+
end
|
39
|
+
|
40
|
+
def dotfiles_file_name
|
41
|
+
DOTFILES_FILE
|
42
|
+
end
|
43
|
+
|
44
|
+
def reload!
|
45
|
+
@data = load_data
|
46
|
+
end
|
47
|
+
|
48
|
+
def update!(args = {})
|
49
|
+
new_data = @data.merge(args)
|
50
|
+
file_content = new_data.each_with_object([]) { |(k, v), result| result << "#{k}: #{v}" }.join("\n")
|
51
|
+
|
52
|
+
fs.create_file(file_path.to_s, file_content)
|
53
|
+
|
54
|
+
reload!
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def path(input, *opts)
|
60
|
+
to_path.(input, *opts)
|
61
|
+
end
|
62
|
+
|
63
|
+
def load_data
|
64
|
+
return {} unless set?
|
65
|
+
|
66
|
+
(YAML.load_file(file_path.to_s) || {}).each_with_object({}) do |(setting, value), result|
|
67
|
+
result[setting.to_sym] = value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "dry-container"
|
2
|
+
require "dry-auto_inject"
|
3
|
+
|
4
|
+
module Dotfiler
|
5
|
+
class Container
|
6
|
+
extend Dry::Container::Mixin
|
7
|
+
|
8
|
+
register "to_path", memoize: true do
|
9
|
+
Dotfiler::Path.method(:new)
|
10
|
+
end
|
11
|
+
|
12
|
+
register "fs", memoize: true do
|
13
|
+
Dotfiler::FileSystem.new
|
14
|
+
end
|
15
|
+
|
16
|
+
register "config", memoize: false do
|
17
|
+
Dotfiler::Config.new
|
18
|
+
end
|
19
|
+
|
20
|
+
register "dotfiles", memoize: false do
|
21
|
+
Dotfiler::Dotfiles.new
|
22
|
+
end
|
23
|
+
|
24
|
+
register "copier", memoize: true do
|
25
|
+
Dotfiler::Copier.new
|
26
|
+
end
|
27
|
+
|
28
|
+
register "mover", memoize: true do
|
29
|
+
Dotfiler::Mover.new
|
30
|
+
end
|
31
|
+
|
32
|
+
register "symlinker", memoize: true do
|
33
|
+
Dotfiler::Symlinker.new
|
34
|
+
end
|
35
|
+
|
36
|
+
register "remover", memoize: true do
|
37
|
+
Dotfiler::Remover.new
|
38
|
+
end
|
39
|
+
|
40
|
+
register "shell", memoize: true do
|
41
|
+
Dotfiler::Shell.new
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
Import = Dry::AutoInject(Container)
|
46
|
+
end
|
47
|
+
|
48
|
+
require "dotfiler/path"
|
49
|
+
require "dotfiler/file_system"
|
50
|
+
require "dotfiler/config"
|
51
|
+
require "dotfiler/dotfiles"
|
52
|
+
|
53
|
+
require "dotfiler/copier"
|
54
|
+
require "dotfiler/mover"
|
55
|
+
require "dotfiler/symlinker"
|
56
|
+
require "dotfiler/remover"
|
57
|
+
|
58
|
+
require "dotfiler/shell"
|