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.
@@ -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,13 @@
1
+ module Dotfiler
2
+ module CLI
3
+ module Commands
4
+ class Version < Command
5
+ desc "Show version"
6
+
7
+ def call(*)
8
+ print("dotfiler #{Dotfiler::VERSION}")
9
+ end
10
+ end
11
+ end
12
+ end
13
+ 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"