Class: Dotsync::Mapping

Inherits:
Object
  • Object
show all
Includes:
PathUtils
Defined in:
lib/dotsync/models/mapping.rb

Overview

Mapping represents a source-to-destination path pair for synchronization.

A mapping defines what should be synced (src -> dest), with optional filters:

  • only: whitelist of paths to include (everything else is excluded)
  • ignore: blacklist of paths to exclude
  • force: enable removal detection (find dest files not in src)

Path Matching Methods

The class provides several methods for path filtering, each with specific use cases:

  • #include?(path): Returns true if path is inside an inclusion. Used for file filtering.
  • #bidirectional_include?(path): Returns true if path is inside OR contains an inclusion. Used during directory traversal to allow descending into parent directories.
  • #should_prune_directory?(path): Returns true if a directory subtree can be skipped entirely. This is a PERFORMANCE OPTIMIZATION for Find.prune - see DirectoryDiffer for details.

Constant Summary

Constants included from PathUtils

PathUtils::ENV_VARS_COLOR

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from PathUtils

#colorize_env_vars, #expand_env_vars, #extract_env_vars, #path_is_parent_or_same?, #relative_to_absolute, #sanitize_path, #translate_tmp_path

Constructor Details

#initialize(attributes) ⇒ Mapping

Returns a new instance of Mapping.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/dotsync/models/mapping.rb', line 26

def initialize(attributes)
  @original_src = attributes["src"]
  @original_dest = attributes["dest"]
  @original_ignores = Array(attributes["ignore"])
  @original_only = Array(attributes["only"])
  @force = attributes["force"] || false
  @hooks = Array(attributes["hooks"])
  @sync_type = attributes["sync_type"]

  @sanitized_src, @sanitized_dest, @sanitized_ignores, @sanitized_only = process_paths(
    @original_src,
    @original_dest,
    @original_ignores,
    @original_only
  )
end

Instance Attribute Details

#hooksObject (readonly)

Returns the value of attribute hooks.



63
64
65
# File 'lib/dotsync/models/mapping.rb', line 63

def hooks
  @hooks
end

#original_destObject (readonly)

Returns the value of attribute original_dest.



24
25
26
# File 'lib/dotsync/models/mapping.rb', line 24

def original_dest
  @original_dest
end

#original_ignoresObject (readonly)

Returns the value of attribute original_ignores.



24
25
26
# File 'lib/dotsync/models/mapping.rb', line 24

def original_ignores
  @original_ignores
end

#original_onlyObject (readonly)

Returns the value of attribute original_only.



24
25
26
# File 'lib/dotsync/models/mapping.rb', line 24

def original_only
  @original_only
end

#original_srcObject (readonly)

Returns the value of attribute original_src.



24
25
26
# File 'lib/dotsync/models/mapping.rb', line 24

def original_src
  @original_src
end

#sync_typeObject (readonly)

Returns the value of attribute sync_type.



24
25
26
# File 'lib/dotsync/models/mapping.rb', line 24

def sync_type
  @sync_type
end

Instance Method Details

#apply_to(path) ⇒ Object



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/dotsync/models/mapping.rb', line 154

def apply_to(path)
  relative_path = if Pathname.new(path).absolute?
    path.delete_prefix(File.join(src, "/"))
  else
    path
  end

  Dotsync::Mapping.new(
    "src" => File.join(@original_src, relative_path),
    "dest" => File.join(@original_dest, relative_path),
    "force" => @force,
    "only" => @only,
    "ignore" => @original_ignores
  )
end

#backup_basenameObject



124
125
126
127
128
# File 'lib/dotsync/models/mapping.rb', line 124

def backup_basename
  return unless valid?
  return File.dirname(dest) unless File.exist?(dest)
  File.basename(dest)
end

#backup_possible?Boolean

Returns:

  • (Boolean)


120
121
122
# File 'lib/dotsync/models/mapping.rb', line 120

def backup_possible?
  valid? && File.exist?(dest)
end

#bidirectional_include?(path) ⇒ Boolean

Returns:

  • (Boolean)


176
177
178
179
180
# File 'lib/dotsync/models/mapping.rb', line 176

def bidirectional_include?(path)
  return true unless has_inclusions?
  return true if path == src
  inclusions.any? { |inclusion| inclusion_matches?(inclusion, path) || inclusion_is_ancestor?(path, inclusion) }
end

#decorated_destObject



150
151
152
# File 'lib/dotsync/models/mapping.rb', line 150

def decorated_dest
  colorize_env_vars(original_dest)
end

#decorated_srcObject



146
147
148
# File 'lib/dotsync/models/mapping.rb', line 146

def decorated_src
  colorize_env_vars(original_src)
end

#destObject



47
48
49
# File 'lib/dotsync/models/mapping.rb', line 47

def dest
  @sanitized_dest
end

#directories?Boolean

Returns:

  • (Boolean)


90
91
92
# File 'lib/dotsync/models/mapping.rb', line 90

def directories?
  File.directory?(src) && File.directory?(dest)
end

#file_changed?Boolean

Returns:

  • (Boolean)


112
113
114
115
116
117
118
# File 'lib/dotsync/models/mapping.rb', line 112

def file_changed?
  return false unless files_present?
  # Check size first for quick comparison
  return true if File.size(src) != File.size(dest)
  # If sizes match, compare content
  FileUtils.compare_file(src, dest) == false
end

#file_present_in_src_only?Boolean

Returns:

  • (Boolean)


102
103
104
# File 'lib/dotsync/models/mapping.rb', line 102

def file_present_in_src_only?
  File.file?(src) && !File.exist?(dest) && File.directory?(File.dirname(dest))
end

#files?Boolean

Returns:

  • (Boolean)


94
95
96
# File 'lib/dotsync/models/mapping.rb', line 94

def files?
  files_present? || file_present_in_src_only?
end

#files_present?Boolean

Returns:

  • (Boolean)


98
99
100
# File 'lib/dotsync/models/mapping.rb', line 98

def files_present?
  File.file?(src) && File.file?(dest)
end

#force?Boolean

Returns:

  • (Boolean)


59
60
61
# File 'lib/dotsync/models/mapping.rb', line 59

def force?
  @force
end

#has_hooks?Boolean

Returns:

  • (Boolean)


65
66
67
# File 'lib/dotsync/models/mapping.rb', line 65

def has_hooks?
  @hooks.any?
end

#has_inclusions?Boolean

Returns:

  • (Boolean)


69
70
71
# File 'lib/dotsync/models/mapping.rb', line 69

def has_inclusions?
  @original_only.any?
end

#iconsObject



130
131
132
133
134
135
136
137
138
# File 'lib/dotsync/models/mapping.rb', line 130

def icons
  msg = []
  msg << Icons.force if force?
  msg << Icons.only if has_inclusions?
  msg << Icons.ignore if has_ignores?
  msg << Icons.hook if has_hooks?
  msg << Icons.invalid unless valid?
  msg.join
end

#ignore?(path) ⇒ Boolean

Returns:

  • (Boolean)


182
183
184
# File 'lib/dotsync/models/mapping.rb', line 182

def ignore?(path)
  ignores.any? { |ignore| path.start_with?(ignore) }
end

#ignoresObject



51
52
53
# File 'lib/dotsync/models/mapping.rb', line 51

def ignores
  @sanitized_ignores
end

#include?(path) ⇒ Boolean

Returns:

  • (Boolean)


170
171
172
173
174
# File 'lib/dotsync/models/mapping.rb', line 170

def include?(path)
  return true unless has_inclusions?
  return true if path == src
  inclusions.any? { |inclusion| inclusion_matches?(inclusion, path) }
end

#inclusionsObject



55
56
57
# File 'lib/dotsync/models/mapping.rb', line 55

def inclusions
  @sanitized_only
end

#manifest_keyObject



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/dotsync/models/mapping.rb', line 73

def manifest_key
  return nil unless @sync_type

  shorthand_base = SyncMappings::SHORTHANDS.dig(@sync_type, :local)
  return @sync_type unless shorthand_base

  expanded_base = sanitize_path(shorthand_base)
  if dest == expanded_base
    @sync_type
  elsif dest.start_with?("#{expanded_base}/")
    subpath = dest.delete_prefix("#{expanded_base}/")
    "#{@sync_type}--#{subpath}"
  else
    @sync_type
  end
end

#should_prune_directory?(path) ⇒ Boolean

Determines if a directory subtree can be entirely skipped during traversal.

PERFORMANCE OPTIMIZATION: This method enables Find.prune in DirectoryDiffer. When walking large destination directories (e.g., ~/.config with 8,686 files), pruning irrelevant subtrees avoids visiting thousands of files that will never match the only filter. This reduced scan time from 7.2s to 0.5s in benchmarks.

A directory should be pruned if:

  1. It's ignored (in the ignore list), OR
  2. It has inclusions AND the path is neither:
    • Inside an inclusion (would be synced)
    • A parent of an inclusion (might contain synced files)

Parameters:

  • path (String)

    Absolute path to check

Returns:

  • (Boolean)

    true if the entire directory subtree can be skipped



205
206
207
208
209
# File 'lib/dotsync/models/mapping.rb', line 205

def should_prune_directory?(path)
  return true if ignore?(path)
  return false unless has_inclusions?
  !bidirectional_include?(path)
end

#skip?(path) ⇒ Boolean

Returns:

  • (Boolean)


186
187
188
# File 'lib/dotsync/models/mapping.rb', line 186

def skip?(path)
  ignore?(path) || !include?(path)
end

#srcObject



43
44
45
# File 'lib/dotsync/models/mapping.rb', line 43

def src
  @sanitized_src
end

#to_sObject



140
141
142
143
144
# File 'lib/dotsync/models/mapping.rb', line 140

def to_s
  msg = "#{decorated_src}#{decorated_dest}"
  msg += " #{icons}" if icons != ""
  msg
end

#valid?Boolean

Returns:

  • (Boolean)


106
107
108
109
110
# File 'lib/dotsync/models/mapping.rb', line 106

def valid?
  return false unless paths_are_distinct?
  return false unless paths_not_nested?
  directories? || files? || file_present_in_src_only?
end