dotfiler 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/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"
|