dotsync 0.1.13 → 0.1.15

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6902bf77ffae5ae4fa88c9a35e452207c7ee5fd1cc274690023a5e2622079681
4
- data.tar.gz: 024ac7795cae1eee0a342f96b7957a024297d64bac16e1375b7541b0e963e8b5
3
+ metadata.gz: c1bdf9f9ccaf7c8fb89e69d1e2f8cd95b72bc7b089d2e582fb358a84ebefb6d2
4
+ data.tar.gz: e05942884ce70c0921c8bfa18f7a9e8be1d8f92ff06912b1f9d09098088ff8fc
5
5
  SHA512:
6
- metadata.gz: aa928d590ce5a9fc6e7494e9c31c542ed336e0ce7a46afaf3050847acefb9033c9b7812027661e49c963f1cb17204e4b0ab81820ccb2544964aaa1d8159f97ba
7
- data.tar.gz: 17d532898d18d4b11a11f649fdfd097f37af75dbd091096b52da9eb6ba420cce28657f37872c912636a71e7206ae391f30c69523a4b59ae214e5fdab9ac54027
6
+ metadata.gz: f33b7f4c6293a0a50c263560f433f7712f6b7d67f997222db3070c7c72b35837b7ed7d92c1b7fcc3b05574d047db6d4daa8a97bd20438e1237ed9f1ffbd8fba8
7
+ data.tar.gz: 15e81bab5c0eaba1ab206c7a8c720f1baaacceb1e8ee5429e7ec1df20515329466c1a43949a637a8485219d92578d2e6acefc3768a529aef9136b90ab6d6b34a
@@ -34,7 +34,7 @@ jobs:
34
34
  bundle exec rubocop
35
35
 
36
36
  - name: Publish to RubyGems
37
- if: matrix.ruby == '3.2'
37
+ if: matrix.ruby == '3.2' && github.ref_name == 'master'
38
38
  run: |
39
39
  mkdir -p $HOME/.gem
40
40
  touch $HOME/.gem/credentials
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ # 0.1.15
2
+
3
+ - Readme: update screenshots in docs
4
+ - Consistent order of icons on Legend and Mappings
5
+ - DirectoryDiffer: implements only option
6
+ - Show Flags icons closer
7
+
8
+ # 0.1.14
9
+
10
+ - Render environment variables and mappings with a table
11
+ - Added mappings legend
12
+ - Added transfer files with "only" option
13
+
1
14
  # 0.1.13
2
15
 
3
16
  - PullAction: backup message simplified
data/Gemfile.lock CHANGED
@@ -1,12 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dotsync (0.1.13)
4
+ dotsync (0.1.15)
5
5
  fileutils (~> 1.7.3)
6
6
  find (~> 0.2.0)
7
7
  listen (~> 3.9.0)
8
8
  logger (~> 1.7.0)
9
9
  ostruct (~> 0.6.3)
10
+ terminal-table (~> 4.0.0)
10
11
  toml-rb (~> 4.0.0)
11
12
 
12
13
  GEM
@@ -118,6 +119,8 @@ GEM
118
119
  rbs (>= 3, < 5)
119
120
  ruby-progressbar (1.13.0)
120
121
  stringio (3.1.7)
122
+ terminal-table (4.0.0)
123
+ unicode-display_width (>= 1.1.1, < 4)
121
124
  timecop (0.9.10)
122
125
  toml-rb (4.0.0)
123
126
  citrus (~> 3.0, > 3.0)
data/README.md CHANGED
@@ -35,14 +35,14 @@ Dotsync provides the following commands to manage your dotfiles:
35
35
 
36
36
  - **Push**: Transfer dotfiles from your local machine to the destination repository.
37
37
  ```shell
38
- dotsync push
38
+ dotsync push --apply
39
39
  ```
40
40
 
41
41
  ![dotsync push](docs/images/dotsync_push.png)
42
42
 
43
43
  - **Pull**: Synchronize dotfiles from the repository to your local machine.
44
44
  ```shell
45
- dotsync pull
45
+ dotsync pull --apply
46
46
  ```
47
47
 
48
48
  During the `pull` operation, `Dotsync::PullAction` creates a backup of the existing files on the destination. These backups are stored in a directory under the XDG path, with each backup organized by a timestamp. To prevent excessive storage usage, only the 10 most recent backups are retained. Older backups are automatically purged, ensuring efficient storage management.
@@ -72,8 +72,10 @@ ignore = ["nvim"]
72
72
  [[pull.mappings]]
73
73
  src = "$XDG_CONFIG_HOME_MIRROR/nvim"
74
74
  dest = "$XDG_CONFIG_HOME/nvim"
75
- force = true # it forces the deletion of destination folder
76
- ignore = ["lazy-lock.json"] # use relative paths to "dest" to ignore files and folders
75
+ # FEATURE: forces the deletion of destination folder
76
+ force = true
77
+ # FEATURE: use relative paths to "dest" to ignore files and folders
78
+ ignore = ["lazy-lock.json"]
77
79
 
78
80
  [[pull.mappings]]
79
81
  src = "$HOME_MIRROR/.zshenv"
@@ -87,6 +89,8 @@ src = "$HOME_MIRROR/.zshenv"
87
89
  [[push.mappings]]
88
90
  src = "$XDG_CONFIG_HOME/alacritty"
89
91
  dest = "$DOTFILES_DIR/config/alacritty"
92
+ # FEATURE: transfer only relative paths of files and folders passed here
93
+ only = ["alacritty.toml", "rose-pine.toml"]
90
94
 
91
95
 
92
96
  [[watch.mappings]]
@@ -110,6 +114,14 @@ dest = "$DOTFILES_DIR/config/alacritty"
110
114
  Each mapping entry supports the following options:
111
115
 
112
116
  - **`force`**: A boolean (true/false) value. When set to `true`, it forces deletion of the destination folder before transferring files from the source. This is particularly useful when you need to ensure that the destination is clean before a transfer.
117
+ - **`only`**: An array of files or folders. This option ensures that only the specified files or folders from the `src` directory are transferred to the `dest` directory. Example:
118
+ ```toml
119
+ [[push.mappings]]
120
+ src = "$XDG_CONFIG_HOME"
121
+ dest = "$DOTFILES_DIR/config"
122
+ only = ["config.yml", "themes"]
123
+
124
+ ```
113
125
  - **`ignore`**: An array of patterns or file names to exclude during the transfer. This allows you to specify files or folders that should not be copied from the source to the destination.
114
126
 
115
127
  These options apply when the source is a directory and are relevant for both `push` and `pull` operations.
Binary file
Binary file
data/dotsync.gemspec CHANGED
@@ -33,6 +33,7 @@ Gem::Specification.new do |spec|
33
33
  spec.add_dependency "logger", "~> 1.7.0" # No longer part of default gems from Ruby 3.5
34
34
  spec.add_dependency "ostruct", "~> 0.6.3" # No longer part of default gems from Ruby 3.5
35
35
  spec.add_dependency "find", "~> 0.2.0"
36
+ spec.add_dependency "terminal-table", "~> 4.0.0"
36
37
 
37
38
  # Debug with:
38
39
  # require 'debug'; binding.break
@@ -23,6 +23,7 @@ module Dotsync
23
23
  def show_options(options)
24
24
  info("Options:", icon: :options)
25
25
  logger.log(" Apply: #{options[:apply] ? "TRUE" : "FALSE"}")
26
+ logger.log("")
26
27
  end
27
28
  end
28
29
  end
@@ -2,6 +2,15 @@
2
2
 
3
3
  module Dotsync
4
4
  module MappingsTransfer
5
+ include Dotsync::PathUtils
6
+
7
+ LEGEND = [
8
+ [Dotsync::Icons.force, "The source will overwrite the destination"],
9
+ [Dotsync::Icons.only, "Paths designated explicitly as source only"],
10
+ [Dotsync::Icons.ignore, "Paths configured to be ignored in the destination"],
11
+ [Dotsync::Icons.invalid, "Invalid paths detected in the source or destination"]
12
+ ]
13
+
5
14
  extend Forwardable # def_delegator
6
15
 
7
16
  def_delegator :@config, :mappings
@@ -11,17 +20,33 @@ module Dotsync
11
20
  return unless env_vars.any?
12
21
 
13
22
  info("Environment variables:", icon: :env_vars)
14
- env_vars.each do |env_var|
15
- logger.log(" #{env_var}: #{ENV[env_var]}")
16
- end
23
+
24
+ rows = env_vars.map { |env_var| [env_var, ENV[env_var]] }.sort_by(&:first)
25
+ table = Terminal::Table.new(rows: rows)
26
+ logger.log(table)
27
+ logger.log("")
28
+ end
29
+
30
+ def show_mappings_legend
31
+ info("Legend:", icon: :legend)
32
+ table = Terminal::Table.new(rows: LEGEND)
33
+ logger.log(table)
34
+ logger.log("")
17
35
  end
18
36
 
19
37
  def show_mappings
20
38
  info("Mappings:", icon: :config)
21
39
 
22
- mappings.each do |mapping|
23
- logger.log(" #{mapping}")
40
+ rows = mappings.map do |mapping|
41
+ [
42
+ mapping.icons,
43
+ colorize_env_vars(mapping.original_src),
44
+ colorize_env_vars(mapping.original_dest)
45
+ ]
24
46
  end
47
+ table = Terminal::Table.new(headings: ["Flags", "Source", "Destination"], rows: rows)
48
+ logger.log(table)
49
+ logger.log("")
25
50
  end
26
51
 
27
52
  def show_changes
@@ -9,6 +9,7 @@ module Dotsync
9
9
  def execute(options = {})
10
10
  show_options(options)
11
11
  show_env_vars
12
+ show_mappings_legend
12
13
  show_mappings
13
14
  show_changes
14
15
  return unless options[:apply]
@@ -19,7 +20,7 @@ module Dotsync
19
20
  end
20
21
 
21
22
  transfer_mappings
22
- action("Dotfiles pulled")
23
+ action("Mappings pulled", icon: :done)
23
24
  end
24
25
 
25
26
  private
@@ -7,12 +7,13 @@ module Dotsync
7
7
  def execute(options = {})
8
8
  show_options(options)
9
9
  show_env_vars
10
+ show_mappings_legend
10
11
  show_mappings
11
12
  show_changes
12
13
  return unless options[:apply]
13
14
 
14
15
  transfer_mappings
15
- action("Dotfiles pushed", icon: :copy)
16
+ action("Mappings pushed", icon: :done)
16
17
  end
17
18
  end
18
19
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Dotsync
4
4
  class WatchAction < BaseAction
5
+ include Dotsync::MappingsTransfer
6
+
5
7
  def_delegator :@config, :mappings
6
8
 
7
9
  def initialize(config, logger)
@@ -11,7 +13,8 @@ module Dotsync
11
13
  end
12
14
 
13
15
  def execute
14
- show_config
16
+ show_mappings_legend
17
+ show_mappings
15
18
 
16
19
  @listeners.each(&:start)
17
20
 
@@ -21,11 +24,6 @@ module Dotsync
21
24
  end
22
25
 
23
26
  private
24
- def show_config
25
- logger.info("Mappings:", icon: :config)
26
- mappings.each { |mapping| logger.log(" #{mapping}") }
27
- end
28
-
29
27
  def setup_listeners
30
28
  @listeners = mappings.map do |mapping|
31
29
  src = mapping.src
data/lib/dotsync/icons.rb CHANGED
@@ -9,11 +9,13 @@ module Dotsync
9
9
  # Configuration icon
10
10
  OPTIONS = " "
11
11
  ENV_VARS = " "
12
+ LEGEND = " "
12
13
  CONFIG = " "
13
14
  DIFF = " "
14
15
 
15
- # Default Mapping icons
16
+ # Default Mapping Flags icons
16
17
  DEFAULT_FORCE = "󰁪 "
18
+ DEFAULT_ONLY = " "
17
19
  DEFAULT_IGNORE = "󰈉 "
18
20
  DEFAULT_INVALID = "󱏏 "
19
21
 
@@ -38,6 +40,7 @@ module Dotsync
38
40
  def self.load_custom_icons(config)
39
41
  @custom_icons = {
40
42
  force: config.dig("icons", "force") || DEFAULT_FORCE,
43
+ only: config.dig("icons", "only") || DEFAULT_ONLY,
41
44
  ignore: config.dig("icons", "ignore") || DEFAULT_IGNORE,
42
45
  invalid: config.dig("icons", "invalid") || DEFAULT_INVALID
43
46
  }
@@ -47,6 +50,10 @@ module Dotsync
47
50
  @custom_icons[:force] || DEFAULT_FORCE
48
51
  end
49
52
 
53
+ def self.only
54
+ @custom_icons[:only] || DEFAULT_ONLY
55
+ end
56
+
50
57
  def self.ignore
51
58
  @custom_icons[:ignore] || DEFAULT_IGNORE
52
59
  end
@@ -61,6 +68,7 @@ module Dotsync
61
68
  error: ERROR,
62
69
  env_vars: ENV_VARS,
63
70
  options: OPTIONS,
71
+ legend: LEGEND,
64
72
  config: CONFIG,
65
73
  diff: DIFF,
66
74
  force: -> { force },
@@ -4,18 +4,20 @@ module Dotsync
4
4
  class Mapping
5
5
  include Dotsync::PathUtils
6
6
 
7
- attr_reader :original_src, :original_dest, :original_ignores
7
+ attr_reader :original_src, :original_dest, :original_ignores, :original_only
8
8
 
9
9
  def initialize(attributes)
10
10
  @original_src = attributes["src"]
11
11
  @original_dest = attributes["dest"]
12
12
  @original_ignores = Array(attributes["ignore"])
13
+ @original_only = Array(attributes["only"])
13
14
  @force = attributes["force"] || false
14
15
 
15
- @sanitized_src, @sanitized_dest, @sanitized_ignore = process_paths(
16
+ @sanitized_src, @sanitized_dest, @sanitized_ignores, @sanitized_only = process_paths(
16
17
  @original_src,
17
18
  @original_dest,
18
- @original_ignores
19
+ @original_ignores,
20
+ @original_only
19
21
  )
20
22
  end
21
23
 
@@ -28,7 +30,11 @@ module Dotsync
28
30
  end
29
31
 
30
32
  def ignores
31
- @sanitized_ignore
33
+ @sanitized_ignores
34
+ end
35
+
36
+ def inclusions
37
+ @sanitized_only
32
38
  end
33
39
 
34
40
  def force?
@@ -69,14 +75,27 @@ module Dotsync
69
75
  File.basename(dest)
70
76
  end
71
77
 
72
- def to_s
73
- colorized_src = colorize_env_vars(original_src)
74
- colorized_dest = colorize_env_vars(original_dest)
75
- msg = ["#{colorized_src} → #{colorized_dest}"]
78
+ def icons
79
+ msg = []
76
80
  msg << Icons.force if force?
77
- msg << Icons.ignore if ignores?
81
+ msg << Icons.only if has_inclusions?
82
+ msg << Icons.ignore if has_ignores?
78
83
  msg << Icons.invalid unless valid?
79
- msg.join(" ")
84
+ msg.join
85
+ end
86
+
87
+ def to_s
88
+ msg = "#{decorated_src} → #{decorated_dest}"
89
+ msg += " #{icons}" if icons != ""
90
+ msg
91
+ end
92
+
93
+ def decorated_src
94
+ colorize_env_vars(original_src)
95
+ end
96
+
97
+ def decorated_dest
98
+ colorize_env_vars(original_dest)
80
99
  end
81
100
 
82
101
  def apply_to(path)
@@ -90,22 +109,46 @@ module Dotsync
90
109
  "src" => File.join(@original_src, relative_path),
91
110
  "dest" => File.join(@original_dest, relative_path),
92
111
  "force" => @force,
112
+ "only" => @only,
93
113
  "ignore" => @original_ignores
94
114
  )
95
115
  end
96
116
 
117
+ def include?(path)
118
+ return true unless has_inclusions?
119
+ return true if path == src
120
+ inclusions.any? { |inclusion| path_is_parent_or_same?(inclusion, path) }
121
+ end
122
+
123
+ def bidirectional_include?(path)
124
+ return true unless has_inclusions?
125
+ return true if path == src
126
+ inclusions.any? { |inclusion| path_is_parent_or_same?(inclusion, path) || path_is_parent_or_same?(path, inclusion) }
127
+ end
128
+
129
+ def ignore?(path)
130
+ ignores.any? { |ignore| path.start_with?(ignore) }
131
+ end
132
+
97
133
  private
98
- def ignores?
134
+ def has_ignores?
99
135
  @original_ignores.any?
100
136
  end
101
137
 
102
- def process_paths(src, dest, ignores)
103
- sanitized_src = sanitize_path(src)
104
- sanitized_dest = sanitize_path(dest)
105
- sanitized_ignore = ignores.flat_map do |path|
138
+ def has_inclusions?
139
+ @original_only.any?
140
+ end
141
+
142
+ def process_paths(raw_src, raw_dest, raw_ignores, raw_only)
143
+ sanitized_src = sanitize_path(raw_src)
144
+ sanitized_dest = sanitize_path(raw_dest)
145
+ sanitized_ignores = raw_ignores.flat_map do |path|
106
146
  [File.join(sanitized_src, path), File.join(sanitized_dest, path)]
107
147
  end
108
- [sanitized_src, sanitized_dest, sanitized_ignore]
148
+ sanitized_only = raw_only.map do |path|
149
+ File.join(sanitized_src, path)
150
+ end
151
+ [sanitized_src, sanitized_dest, sanitized_ignores, sanitized_only]
109
152
  end
110
153
  end
111
154
  end
@@ -36,6 +36,11 @@ module Dotsync
36
36
  Find.find(mapping_src) do |src_path|
37
37
  rel_path = src_path.sub(/^#{Regexp.escape(mapping_src)}\/?/, "")
38
38
 
39
+ unless @mapping.include?(src_path)
40
+ Find.prune
41
+ next
42
+ end
43
+
39
44
  dest_path = File.join(mapping_dest, rel_path)
40
45
 
41
46
  if !File.exist?(dest_path)
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Dotsync
4
4
  class FileTransfer
5
- attr_reader :ignores
6
-
7
5
  # Initializes a new FileTransfer instance
8
6
  #
9
7
  # @param mapping [Dotsync::Mapping] the mapping object containing source, destination, force, and ignore details
@@ -12,9 +10,11 @@ module Dotsync
12
10
  # @option mapping [Boolean] :force? optional flag to force actions
13
11
  # @option mapping [Array<String>] :ignores optional list of files/directories to ignore
14
12
  def initialize(mapping)
13
+ @mapping = mapping
15
14
  @src = mapping.src
16
15
  @dest = mapping.dest
17
16
  @force = mapping.force?
17
+ @inclusions = mapping.inclusions || []
18
18
  @ignores = mapping.ignores || []
19
19
  end
20
20
 
@@ -28,6 +28,8 @@ module Dotsync
28
28
  end
29
29
 
30
30
  private
31
+ attr_reader :mapping, :ignores
32
+
31
33
  def transfer_file(file_src, file_dest)
32
34
  FileUtils.mkdir_p(File.dirname(file_dest))
33
35
  FileUtils.cp(file_src, file_dest)
@@ -35,11 +37,17 @@ module Dotsync
35
37
 
36
38
  def transfer_folder(folder_src, folder_dest)
37
39
  FileUtils.mkdir_p(folder_dest)
40
+
41
+ # `Dir.glob("#{folder_src}/*")` retrieves only the immediate contents
42
+ # (files and directories) within the specified directory (`folder_src`),
43
+ # without descending into subdirectories.
44
+
38
45
  Dir.glob("#{folder_src}/*", File::FNM_DOTMATCH).each do |path|
39
46
  next if [".", ".."].include?(File.basename(path))
40
47
 
41
48
  full_path = File.expand_path(path)
42
- next if ignore?(full_path)
49
+ next unless mapping.bidirectional_include?(full_path)
50
+ next if mapping.ignore?(full_path)
43
51
 
44
52
  target = File.join(folder_dest, File.basename(path))
45
53
  if File.file?(full_path)
@@ -75,9 +83,5 @@ module Dotsync
75
83
  end
76
84
  end
77
85
  end
78
-
79
- def ignore?(path)
80
- @ignores.any? { |ignore| path.start_with?(ignore) }
81
- end
82
86
  end
83
87
  end
@@ -24,6 +24,12 @@ module Dotsync
24
24
  paths.map { |path| File.join(base_path, path) }
25
25
  end
26
26
 
27
+ def path_is_parent_or_same?(parent, child)
28
+ parent = Pathname.new(parent).expand_path
29
+ child = Pathname.new(child).expand_path
30
+ child.ascend.any? { |ancestor| ancestor == parent }
31
+ end
32
+
27
33
  # Translates /tmp paths to /private/tmp paths on macOS
28
34
  # Retains other paths as-is
29
35
  # @param [String] path The input path to translate
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dotsync
4
- VERSION = "0.1.13"
4
+ VERSION = "0.1.15"
5
5
  end
data/lib/dotsync.rb CHANGED
@@ -8,6 +8,14 @@ require "logger"
8
8
  require "forwardable" # Ruby standard library
9
9
  require "ostruct"
10
10
  require "find"
11
+ require "terminal-table"
12
+
13
+ # Base classes
14
+ require_relative "dotsync/errors"
15
+ require_relative "dotsync/icons"
16
+ require_relative "dotsync/colors"
17
+ require_relative "dotsync/runner"
18
+ require_relative "dotsync/version"
11
19
 
12
20
  # Utils
13
21
  require_relative "dotsync/utils/path_utils"
@@ -37,13 +45,6 @@ require_relative "dotsync/actions/pull_action"
37
45
  require_relative "dotsync/actions/push_action"
38
46
  require_relative "dotsync/actions/watch_action"
39
47
 
40
- # Base classes
41
- require_relative "dotsync/errors"
42
- require_relative "dotsync/icons"
43
- require_relative "dotsync/colors"
44
- require_relative "dotsync/runner"
45
- require_relative "dotsync/version"
46
-
47
48
  module Dotsync
48
49
  class << self
49
50
  attr_writer :config_path
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dotsync
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.13
4
+ version: 0.1.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Sáenz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-11-04 00:00:00.000000000 Z
11
+ date: 2025-11-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: toml-rb
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: 0.2.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: terminal-table
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 4.0.0
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 4.0.0
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: debug
99
113
  requirement: !ruby/object:Gem::Requirement