label_weaver 0.0.5 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6bbe89444ed69fe936e7e64cb7c0e2b36c013df2afef71f1b33ae65a17e67fa3
4
- data.tar.gz: db4917c239cf6502e3da219f7fe215af11d5fa5dd3668e16b01fad2389c93243
3
+ metadata.gz: eca50ec7fe76eb58a454a92a3815af9037e80617e71fdf6cd53b81b27cce9f56
4
+ data.tar.gz: 5e6d191c664203f71feaba035e774b4fa492ae804b0d31cc5a0f645342ae9343
5
5
  SHA512:
6
- metadata.gz: edb992f4593c2de938debe63bc4ee1f60bf4f308b80e859a82c1fb6e3318f45aca96096c8b0859f33c72e916f61f2bffb5f627af4effae44b4135313490b92f1
7
- data.tar.gz: 2d51c191608b5070f20f31ef818b26d4d21d67839427655974e8ff55b3911ce41b3a501418e9151bd1d61f8fc1d49393fdaa68f01e225d673687913f8a59f553
6
+ metadata.gz: 711898d28b3baea45f8659e68a6c63d7537f5acb61e879f61c01268fcdad59b3f65c1067ef67a4a160575314d3577be856902d766131e146d3dce7a1a34fe675
7
+ data.tar.gz: d07d1c04a8936e7ada2e3bcf9c2a9f58381f4169faaea67ff9f8b245dd3646d8ca87031f9212f4b10503ca080838d8858826c4bb14ea28639f57c9aa963ce307
data/label_weaver.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "label_weaver"
5
- spec.version = "0.0.5"
5
+ spec.version = "0.0.7"
6
6
  spec.authors = ["Stefan Exner"]
7
7
  spec.email = ["stex@stex.codes"]
8
8
  spec.homepage = "https://gitlab.com/lopo-tech/label_weaver"
@@ -17,10 +17,12 @@ Gem::Specification.new do |spec|
17
17
  spec.required_ruby_version = "~> 3.3"
18
18
  spec.add_dependency "cogger", "~> 0.21"
19
19
  spec.add_dependency "containable", "~> 0.0"
20
+ spec.add_dependency "dot-properties", "~> 0.1"
20
21
  spec.add_dependency "dry-schema", "~> 1.6"
21
22
  spec.add_dependency "diffy", "~> 3.4"
22
23
  spec.add_dependency "git", "~> 2.1.1"
23
24
  spec.add_dependency "infusible", "~> 3.5"
25
+ spec.add_dependency "plist", "~> 3.7"
24
26
  spec.add_dependency "ruby-next-core", "~> 1.0"
25
27
  spec.add_dependency "runcom", "~> 11.0"
26
28
  spec.add_dependency "sod", "~> 0.8"
@@ -38,7 +38,10 @@ module LabelWeaver
38
38
  relative_filename = repo.relative_path(repository_file)
39
39
  project_file = context.project_root_dir + relative_filename
40
40
 
41
- if project_file.exist?
41
+ if repo.forced_file?(repository_file)
42
+ # If the file is forced, we need to delete the existing file if it exists.
43
+ FileUtils.rm_f(project_file) if project_file.exist?
44
+ elsif Util.file_exists?(project_file)
42
45
  if repo.timestamp_for(repository_file) > project_file.mtime
43
46
  logger.warn "File '#{relative_filename}' has a newer version in the remote repository (#{repo.timestamp_for(repository_file)} vs #{project_file.mtime})."
44
47
  end
@@ -48,7 +51,8 @@ module LabelWeaver
48
51
  end
49
52
 
50
53
  FileUtils.mkdir_p(project_file.dirname)
51
- FileUtils.copy(repository_file, project_file, preserve: true)
54
+ # We're not using #copy here as it does not preserve symlinks.
55
+ FileUtils.copy_entry(repository_file, project_file, preserve: true)
52
56
  end
53
57
  end
54
58
 
@@ -66,7 +70,8 @@ module LabelWeaver
66
70
  context.repository_dir,
67
71
  repository_url: configuration.git[:repository],
68
72
  branch: configuration.git[:branch],
69
- excludes: configuration.excludes,
73
+ excluded_upstream_files: configuration.excluded_upstream_files,
74
+ forced_upstream_files: configuration.forced_upstream_files,
70
75
  logger: logger
71
76
  )
72
77
  end
@@ -37,13 +37,16 @@ module LabelWeaver
37
37
  relative_filename = repo.relative_path(repository_file)
38
38
  project_file = context.project_root_dir + relative_filename
39
39
 
40
- if project_file.exist?
41
- if digest(project_file) != digest(repository_file)
40
+ if repo.forced_file?(repository_file) && Util.file_exists?(project_file)
41
+ logger.debug "Force-Deleting file '#{relative_filename}'..."
42
+ project_file.unlink
43
+ remove_empty_tree(project_file.dirname)
44
+ elsif Util.file_exists?(project_file)
45
+ if Util.digest(project_file) != Util.digest(repository_file)
42
46
  logger.debug "File '#{relative_filename}' is kept as it was changed in the whitelabel project (r: #{repo.timestamp_for(repository_file)} vs w: #{project_file.mtime})."
43
47
  else
44
48
  logger.debug "Deleting file '#{relative_filename}'..."
45
49
  project_file.unlink
46
-
47
50
  remove_empty_tree(project_file.dirname)
48
51
  end
49
52
  end
@@ -68,16 +71,13 @@ module LabelWeaver
68
71
  end
69
72
  end
70
73
 
71
- def digest(file)
72
- Digest::SHA1.file(file).hexdigest
73
- end
74
-
75
74
  def repo
76
75
  @repo ||= LabelWeaver::TempRepo.new(
77
76
  context.repository_dir,
78
77
  repository_url: configuration.git[:repository],
79
78
  branch: configuration.git[:branch],
80
- excludes: configuration.excludes,
79
+ excluded_upstream_files: configuration.excluded_upstream_files,
80
+ forced_upstream_files: configuration.forced_upstream_files,
81
81
  logger: logger
82
82
  )
83
83
  end
@@ -9,7 +9,8 @@ module LabelWeaver
9
9
  required(:repository).filled(:string)
10
10
  required(:branch).filled(:string)
11
11
  end
12
- optional(:excludes).array :string
12
+ optional(:excluded_upstream_files).array(:string)
13
+ optional(:forced_upstream_files).array(:string)
13
14
  optional(:hooks).hash do
14
15
  optional(:develop).hash do
15
16
  optional(:before_start).array(:string)
@@ -1,9 +1,9 @@
1
1
  git:
2
2
  repository: "git@gitlab.com:lopo-tech/label_weaver.git"
3
3
  branch: "main"
4
- # Files to exclude from the base repository, they will not be copied over to the whitelabel project.
4
+ # Files to exclude from the upstream repository, they will not be copied over to the whitelabel project.
5
5
  # Shell filename globbing rules apply, not regular expressions
6
- excludes:
6
+ excluded_upstream_files:
7
7
  - .git/**
8
8
  hooks:
9
9
  develop:
@@ -18,6 +18,13 @@ hooks:
18
18
  # target_file: package.json
19
19
  # overrides_file: package.whitelabel.json
20
20
 
21
+ # Files to be **always** taken from the upstream repository.
22
+ # They will always be copied over on development start and will be removed from the
23
+ # whitelabel project on stop.
24
+ # Shell filename globbing rules apply, not regular expressions
25
+ #forced_upstream_files:
26
+ # - test/**
27
+
21
28
  # Files here will be appended on whitelabel development instead of being skipped if the
22
29
  # file already exists in a project
23
30
  #append:
@@ -4,12 +4,13 @@ module LabelWeaver
4
4
  module Configuration
5
5
  # Defines the configuration model for use throughout the gem.
6
6
  Model = Data.define(
7
- :excludes,
7
+ :excluded_upstream_files,
8
8
  :git,
9
9
  :hooks,
10
- :merges
10
+ :merges,
11
+ :forced_upstream_files
11
12
  ) do
12
- def initialize(git:, hooks: {}, excludes: [], merges: [])
13
+ def initialize(git:, hooks: {}, excluded_upstream_files: [], forced_upstream_files: [], merges: [])
13
14
  super
14
15
  end
15
16
  end
@@ -1,3 +1,5 @@
1
+ require "dot_properties"
2
+
1
3
  module LabelWeaver
2
4
  class Merger
3
5
  include Import[:configuration, :logger]
@@ -38,7 +40,17 @@ module LabelWeaver
38
40
  source = load_file(source_file)
39
41
  overrides = load_file(overrides_file)
40
42
 
41
- write_file(target_file, source.deep_merge(overrides))
43
+ validate_compatibility!(source, overrides)
44
+
45
+ case target_file.extname
46
+ when ".properties"
47
+ overrides.to_h.each do |k, v|
48
+ source[k] = v
49
+ end
50
+ write_file(target_file, source)
51
+ else
52
+ write_file(target_file, source.deep_merge(overrides))
53
+ end
42
54
  end
43
55
 
44
56
  def restore_file(relative_file_path)
@@ -52,8 +64,23 @@ module LabelWeaver
52
64
  configuration.merges.map { _1.transform_keys(&:to_sym) }
53
65
  end
54
66
 
67
+ def validate_compatibility!(source, overrides)
68
+ # Check for property files
69
+ if source.is_a?(DotProperties) || overrides.is_a?(DotProperties)
70
+ if nested_hash?(source) || nested_hash?(overrides)
71
+ raise ArgumentError, "Property files can only be merged with other property files or one-dimensional hashes"
72
+ end
73
+ end
74
+ end
75
+
76
+ def nested_hash?(value)
77
+ value.is_a?(Hash) && value.values.any? { |v| v.is_a?(Hash) }
78
+ end
79
+
55
80
  def load_file(file)
56
81
  case file.extname
82
+ when ".properties"
83
+ DotProperties.load(file)
57
84
  when ".yaml", ".yml"
58
85
  YAML.load_file(file)
59
86
  when ".json"
@@ -65,10 +92,13 @@ module LabelWeaver
65
92
 
66
93
  def write_file(file, content)
67
94
  case file.extname
95
+ when ".properties"
96
+ # TODO: Handle content that is not already a DotProperties instance
97
+ File.write(file, content.to_s)
68
98
  when ".yaml", ".yml"
69
- File.write(file, content.to_yaml)
99
+ File.write(file, content.to_h.to_yaml)
70
100
  when ".json"
71
- File.write(file, JSON.pretty_generate(content))
101
+ File.write(file, JSON.pretty_generate(content.to_h))
72
102
  else
73
103
  raise ArgumentError, "Unsupported file type: #{file.extname}"
74
104
  end
@@ -7,13 +7,21 @@ module LabelWeaver
7
7
  class TempRepo
8
8
  using Refinements::Pathname
9
9
 
10
- attr_reader :repository_path, :repository_url, :branch, :excludes, :logger
11
-
12
- def initialize(repository_path, repository_url:, branch: "main", excludes: [], logger: Logger.new($stdout))
10
+ attr_reader :repository_path, :repository_url, :branch, :logger, :excluded_upstream_files, :forced_upstream_files
11
+
12
+ def initialize(
13
+ repository_path,
14
+ repository_url:,
15
+ branch: "main",
16
+ excluded_upstream_files: [],
17
+ forced_upstream_files: [],
18
+ logger: Logger.new($stdout)
19
+ )
13
20
  @repository_path = repository_path
14
21
  @repository_url = repository_url
15
22
  @branch = branch
16
- @excludes = excludes + %w[. ..]
23
+ @excluded_upstream_files = excluded_upstream_files + %w[. ..]
24
+ @forced_upstream_files = forced_upstream_files
17
25
  @logger = logger
18
26
  end
19
27
 
@@ -27,7 +35,7 @@ module LabelWeaver
27
35
  end
28
36
 
29
37
  def digest_for(file)
30
- Digest::SHA1.file(file).hexdigest
38
+ Util.digest(file)
31
39
  end
32
40
 
33
41
  def changed_file?(repository_file, project_root_dir:)
@@ -60,12 +68,26 @@ module LabelWeaver
60
68
  def relevant_files
61
69
  files.filter do |file|
62
70
  # Reject any files that are set up to be excluded. Shell filename globbing rules apply
63
- next false if excludes.any? { relative_path(file).fnmatch?(_1, File::FNM_DOTMATCH) }
71
+ next false if excluded_file?(file)
64
72
 
65
73
  true
66
74
  end
67
75
  end
68
76
 
77
+ #
78
+ # @return [Boolean] +true+ if the file should never be copied over from the repository
79
+ #
80
+ def excluded_file?(repository_file)
81
+ excluded_upstream_files.any? { relative_path(repository_file).fnmatch?(_1, File::FNM_DOTMATCH) }
82
+ end
83
+
84
+ #
85
+ # @return [Boolean] +true+ if the file should **always** be copied over from the repository
86
+ #
87
+ def forced_file?(repository_file)
88
+ forced_upstream_files.any? { relative_path(repository_file).fnmatch?(_1, File::FNM_DOTMATCH) }
89
+ end
90
+
69
91
  #
70
92
  # Updates the atime and mtime of each file in the repository to their latest commit time
71
93
  # TODO: Check if still needed
@@ -103,7 +125,9 @@ module LabelWeaver
103
125
  # @return [Array<Pathname>] All files inside the repository, including dotfiles. No folders.
104
126
  #
105
127
  def files
106
- repository_path.glob("**/*", File::FNM_DOTMATCH).select(&:file?)
128
+ # The symlink check is necessary to include directory symlinks, but leave out
129
+ # regular directories
130
+ repository_path.glob("**/*", File::FNM_DOTMATCH).select { _1.file? || _1.symlink? }
107
131
  end
108
132
 
109
133
  def repo
@@ -0,0 +1,18 @@
1
+ module LabelWeaver
2
+ module Util
3
+ def digest(file)
4
+ if file.symlink?
5
+ # for symlinks we just use the target path
6
+ file.readlink
7
+ else
8
+ Digest::SHA1.file(file).hexdigest
9
+ end
10
+ end
11
+ module_function :digest
12
+
13
+ def file_exists?(file)
14
+ file.symlink? || file.exist?
15
+ end
16
+ module_function :file_exists?
17
+ end
18
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: label_weaver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Exner
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-09-29 00:00:00.000000000 Z
11
+ date: 2024-10-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cogger
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dot-properties
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.1'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: dry-schema
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +108,20 @@ dependencies:
94
108
  - - "~>"
95
109
  - !ruby/object:Gem::Version
96
110
  version: '3.5'
111
+ - !ruby/object:Gem::Dependency
112
+ name: plist
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.7'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.7'
97
125
  - !ruby/object:Gem::Dependency
98
126
  name: ruby-next-core
99
127
  requirement: !ruby/object:Gem::Requirement
@@ -222,6 +250,7 @@ files:
222
250
  - lib/label_weaver/import.rb
223
251
  - lib/label_weaver/merger.rb
224
252
  - lib/label_weaver/temp_repo.rb
253
+ - lib/label_weaver/util.rb
225
254
  homepage: https://gitlab.com/lopo-tech/label_weaver
226
255
  licenses:
227
256
  - MIT