planter-cli 0.0.3 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/.rubocop.yml +5 -7
  4. data/CHANGELOG.md +21 -0
  5. data/README.md +28 -1
  6. data/Rakefile +54 -18
  7. data/bin/plant +6 -0
  8. data/docker/Dockerfile-2.6 +5 -5
  9. data/docker/Dockerfile-2.7 +3 -3
  10. data/docker/Dockerfile-3.0 +3 -3
  11. data/lib/planter/array.rb +51 -0
  12. data/lib/planter/color.rb +1 -1
  13. data/lib/planter/errors.rb +14 -0
  14. data/lib/planter/file.rb +87 -4
  15. data/lib/planter/fileentry.rb +5 -1
  16. data/lib/planter/filelist.rb +43 -7
  17. data/lib/planter/hash.rb +81 -84
  18. data/lib/planter/plant.rb +4 -10
  19. data/lib/planter/prompt.rb +6 -3
  20. data/lib/planter/script.rb +24 -12
  21. data/lib/planter/string.rb +134 -29
  22. data/lib/planter/tag.rb +54 -0
  23. data/lib/planter/version.rb +1 -1
  24. data/lib/planter.rb +60 -34
  25. data/planter-cli.gemspec +1 -0
  26. data/spec/config.yml +2 -0
  27. data/spec/planter/array_spec.rb +28 -0
  28. data/spec/planter/file_entry_spec.rb +40 -0
  29. data/spec/planter/file_spec.rb +19 -0
  30. data/spec/planter/filelist_spec.rb +15 -0
  31. data/spec/planter/hash_spec.rb +110 -0
  32. data/spec/planter/plant_spec.rb +1 -0
  33. data/spec/planter/script_spec.rb +80 -0
  34. data/spec/planter/string_spec.rb +215 -2
  35. data/spec/planter/symbol_spec.rb +23 -0
  36. data/spec/planter.yml +6 -0
  37. data/spec/planter_spec.rb +82 -0
  38. data/spec/scripts/test.sh +3 -0
  39. data/spec/scripts/test_fail.sh +3 -0
  40. data/spec/spec_helper.rb +8 -2
  41. data/spec/templates/test/%%project:snake%%.rtf +10 -0
  42. data/spec/templates/test/Rakefile +6 -0
  43. data/spec/templates/test/_planter.yml +12 -0
  44. data/spec/templates/test/_scripts/test.sh +3 -0
  45. data/spec/templates/test/_scripts/test_fail.sh +3 -0
  46. data/spec/templates/test/test.rb +5 -0
  47. data/spec/test_out/image.png +0 -0
  48. data/spec/test_out/test2.rb +5 -0
  49. data/src/_README.md +28 -1
  50. metadata +57 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5445d6dfa3c8e0663006ce933f45f6e0717ccb4be51c67da0901c9606a5c70c
4
- data.tar.gz: 0ee188813bbc0bb274837b130231dd937f403484d1a26adccee36b8126d2ee3f
3
+ metadata.gz: 0b4906c83392316be6ac61380bd0d2401a847f4c08f6afc91bdfbd4858980568
4
+ data.tar.gz: 7253e2ec572be45e7543d82ad2b1dde685fc5b8ba7b256ec914b0575e44ad507
5
5
  SHA512:
6
- metadata.gz: 78a345ff2396c29c7beeadda989780003b5cc63174e4b9bcfcd0344350d8160fbb403231208ba1c3ca0d4bb66083711e675f5aad7dff5d14b226d59f31be6554
7
- data.tar.gz: 1dc8ab448b3674319f6a469439d7b8dd6657a9c85061358fee7c7280a1fe9bb5910e4bf5d69e3223511bfccbd77c9e09c666b1441619ec0489e25d9c934fc048
6
+ metadata.gz: 98fd1c334e3d2efdcdc10171d7a5b5f81898e9a03890bc08cd65e2f3c8a10941cfe46e5e5b23584f28c87aad3b85471ab544c3665049c5abfd911006b40bfbe8
7
+ data.tar.gz: a7a86edaf7bf49cb65cde877ab4aec8bd92a6f27a90c09f526e50f9f1ddc84c27655e67bfea75b9463c8033601ae1961e4854bce76f4e268bfe87aa3171c7677
data/.gitignore CHANGED
@@ -41,4 +41,7 @@ Gemfile.lock
41
41
 
42
42
  # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
43
43
  .rvmrc
44
- test
44
+ /test
45
+ .history
46
+ spec/test/
47
+ spec/noop
data/.rubocop.yml CHANGED
@@ -23,10 +23,6 @@ Style/MutableConstant:
23
23
  Style/SpecialGlobalVars:
24
24
  Enabled: false
25
25
 
26
- Security/YAMLLoad:
27
- Exclude:
28
- - 'lib/**/*.rb'
29
-
30
26
  Style/StringLiterals:
31
27
  Enabled: true
32
28
  EnforcedStyle: single_quotes
@@ -45,7 +41,7 @@ Metrics/BlockLength:
45
41
  Max: 45
46
42
  Exclude:
47
43
  - Rakefile
48
- - bin/howzit
44
+ - bin/untitled
49
45
  - lib/*.rb
50
46
 
51
47
  Metrics/ClassLength:
@@ -67,8 +63,7 @@ Metrics/ModuleLength:
67
63
  Max: 174
68
64
 
69
65
  Security/YAMLLoad:
70
- Exclude:
71
- - '**/*.rb'
66
+ Enabled: false
72
67
 
73
68
  Style/ModuleFunction:
74
69
  Exclude:
@@ -76,3 +71,6 @@ Style/ModuleFunction:
76
71
 
77
72
  Style/RaiseArgs:
78
73
  EnforcedStyle: compact
74
+
75
+ Style/SlicingWithRange:
76
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ### 3.0.1
2
+
3
+ 2024-08-31 14:19
4
+
5
+ ### 3.0.0-alpha
6
+
7
+ 2024-08-31 14:18
8
+
9
+ #### NEW
10
+
11
+ - Initial release
12
+ - Preserve Finder tags when planting (config option `preserve_tags: true`)
13
+
14
+ #### IMPROVED
15
+
16
+ - More tests
17
+
18
+ #### FIXED
19
+
20
+ - Code refactoring
21
+
1
22
  ### 0.0.3
2
23
 
3
24
  2024-08-28 09:46
data/README.md CHANGED
@@ -14,7 +14,18 @@ If [Gum](https://github.com/charmbracelet/gum) is available it will be used for
14
14
 
15
15
  ## Configuration
16
16
 
17
- Planter's base configuration is in `~/.config/planter/config.yml`. This file can contain any of the keys used in templates (see below) and will serve as a base configuration for all templates. Any key defined in this file will be overridden if it exists in a template.
17
+ Planter's base configuration is in `~/.config/planter/planter.yml`. This file can contain any of the keys used in templates (see below) and will serve as a base configuration for all templates. Any key defined in this file will be overridden if it exists in a template.
18
+
19
+ Example config (written on first run):
20
+
21
+ ```yaml
22
+ files:
23
+ .DS_Store: ignore
24
+ "*.tmp": ignore
25
+ "*.bak": ignore
26
+ git_init: false
27
+ preserve_tags: true
28
+ ```
18
29
 
19
30
  ### Scripts.
20
31
 
@@ -49,6 +60,10 @@ replacements: # Dictionary of pattern/replacments for regex substitution, see [R
49
60
  repo: # If a repository URL is provided, it will be pulled and duplicated instead of copying a file structure
50
61
  ```
51
62
 
63
+ #### Default values in template strings
64
+
65
+ In a template you can add a default value for a placholder by adding `%default value` to it. For example, `%%project%Default Project%%` will set the placeholder to `Default Project` if the variable value matches the default value in the configuration. This allows you to accept the default on the command line but have a different value inserted in the template. To use another variable in its place, use `$KEY` in the placeholder, e.g. `%%project%$title%%` will replace the `project` key with the value of `title` if the default is selected. Modifiers can be used on either side of the `%`, e.g. `%%project%$title:snake%%`.
66
+
52
67
  ### File-specific handling
53
68
 
54
69
  A `files` dictionary can specify how to handle specific files. Options are `copy`, `overwrite`, `merge`, or `ask`. The key for each entry is a filename or glob that matches the source filename (accounting for template variables if applicable):
@@ -67,6 +82,14 @@ Merged content
67
82
  // /merge
68
83
  ```
69
84
 
85
+ Or
86
+
87
+ ```
88
+ # merge
89
+ Merged content
90
+ # /merge
91
+ ```
92
+
70
93
  By default files that already exist in the destination directory are not overwritten, and merging allows you to add missing parts to a Rakefile or Makefile, for example.
71
94
 
72
95
  If `ask` is specified, a memu will be provided on the command line asking how to handle a file. If the file doesn't already exist, you will be asked only whether to copy the file or not. If it does exist, `overwrite` and `merge` options will be added.
@@ -83,6 +106,10 @@ replacements:
83
106
 
84
107
  Replacements are performed on both file/directory names and file contents.
85
108
 
109
+ ### Finder Tags
110
+
111
+ If `preserve_tags` is set to `true` in the config (either base or template), then existing Finder tags on the file or folder will be copied to the new file when a template is planted.
112
+
86
113
  ## Usage
87
114
 
88
115
  The executable for Planter is `plant`. You can run `plant TEMPLATE` in any directory and TEMPLATE will be planted in the current directory. You can also use `--in PATH` to plant in another directory.
data/Rakefile CHANGED
@@ -38,17 +38,6 @@ end
38
38
  desc 'Clobber files'
39
39
  task clobber: :clobber_packages
40
40
 
41
- desc 'Development version check'
42
- task :ver do
43
- gver = `git ver`
44
- cver = IO.read(File.join(File.dirname(__FILE__), 'CHANGELOG.md')).match(/^#+ (\d+\.\d+\.\d+(\w+)?)/)[1]
45
- res = `grep VERSION lib/planter/version.rb`
46
- version = res.match(/VERSION *= *['"](\d+\.\d+\.\d+(\w+)?)/)[1]
47
- puts "git tag: #{gver}"
48
- puts "version.rb: #{version}"
49
- puts "changelog: #{cver}"
50
- end
51
-
52
41
  desc 'Get Script Version'
53
42
  task :sver do
54
43
  res = `grep VERSION lib/planter/version.rb`
@@ -56,11 +45,6 @@ task :sver do
56
45
  print version
57
46
  end
58
47
 
59
- desc 'Changelog version check'
60
- task :cver do
61
- puts IO.read(File.join(File.dirname(__FILE__), 'CHANGELOG.md')).match(/^#+ (\d+\.\d+\.\d+(\w+)?)/)[1]
62
- end
63
-
64
48
  desc 'Run tests in Docker'
65
49
  task :dockertest, :version, :login, :attempt do |_, args|
66
50
  args.with_defaults(version: 'all', login: false, attempt: 1)
@@ -78,6 +62,9 @@ task :dockertest, :version, :login, :attempt do |_, args|
78
62
  Rake::Task['dockertest'].invoke(v, false)
79
63
  end
80
64
  Process.exit 0
65
+ when /^3\.?3/
66
+ img = 'plantertest33'
67
+ file = 'docker/Dockerfile-3.3'
81
68
  when /^3/
82
69
  version = '3.0'
83
70
  img = 'plantertest3'
@@ -107,10 +94,10 @@ task :dockertest, :version, :login, :attempt do |_, args|
107
94
  dir_args = dirs.map { |s, d| " -v '#{s}:#{d}'" }.join(' ')
108
95
  exec "docker run #{dir_args} -it #{img} /bin/bash -l" if args[:login]
109
96
 
110
- spinner = TTY::Spinner.new("[:spinner] Running tests (#{args[:version]})...", hide_cursor: true)
97
+ spinner = TTY::Spinner.new("[:spinner] Running tests (#{version})...", hide_cursor: true)
111
98
 
112
99
  spinner.auto_spin
113
- res = `docker run --rm #{dir_args} -it #{img}`
100
+ `docker run --rm #{dir_args} -it #{img}`
114
101
  # raise DockerError.new('Error running docker image') unless $?.success?
115
102
 
116
103
  # commit = puts `bash -c "docker commit $(docker ps -a|grep #{img}|awk '{print $1}'|head -n 1) #{img}"`.strip
@@ -130,3 +117,52 @@ end
130
117
 
131
118
  desc 'alias for build'
132
119
  task package: :build
120
+
121
+ desc 'Development version check'
122
+ task :ver do
123
+ gver = `git ver`
124
+ cver = IO.read(File.join(File.dirname(__FILE__), 'CHANGELOG.md')).match(/^#+ (\d+\.\d+\.\d+(\w+)?)/)[1]
125
+ res = `grep VERSION lib/untitled/version.rb`
126
+ version = res.match(/VERSION *= *['"](\d+\.\d+\.\d+(\w+)?)/)[1]
127
+ puts "git tag: #{gver}"
128
+ puts "version.rb: #{version}"
129
+ puts "changelog: #{cver}"
130
+ end
131
+
132
+ desc 'Changelog version check'
133
+ task :cver do
134
+ puts IO.read(File.join(File.dirname(__FILE__), 'CHANGELOG.md')).match(/^#+ (\d+\.\d+\.\d+(\w+)?)/)[1]
135
+ end
136
+
137
+ desc 'Alias for build'
138
+ task package: :build
139
+
140
+ desc 'Bump incremental version number'
141
+ task :bump, :type do |_, args|
142
+ args.with_defaults(type: 'inc')
143
+ version_file = 'lib/untitled/version.rb'
144
+ content = IO.read(version_file)
145
+ content.sub!(/VERSION = '(?<major>\d+)\.(?<minor>\d+)\.(?<inc>\d+)(?<pre>\S+)?'/) do
146
+ m = Regexp.last_match
147
+ major = m['major'].to_i
148
+ minor = m['minor'].to_i
149
+ inc = m['inc'].to_i
150
+ pre = m['pre']
151
+
152
+ case args[:type]
153
+ when /^maj/
154
+ major += 1
155
+ minor = 0
156
+ inc = 0
157
+ when /^min/
158
+ minor += 1
159
+ inc = 0
160
+ else
161
+ inc += 1
162
+ end
163
+
164
+ $stdout.puts "At version #{major}.#{minor}.#{inc}#{pre}"
165
+ "VERSION = '#{major}.#{minor}.#{inc}#{pre}'"
166
+ end
167
+ File.open(version_file, 'w+') { |f| f.puts content }
168
+ end
data/bin/plant CHANGED
@@ -22,6 +22,7 @@ options = {
22
22
  # min: 1
23
23
  # max: 5
24
24
  Planter.variables = {}
25
+ Planter.base_dir = ENV['PLANTER_DIR'] || File.expand_path('~/.config/planter')
25
26
  Planter::Color.coloring = $stdout.isatty
26
27
 
27
28
  opts = OptionParser.new
@@ -53,6 +54,10 @@ opts.on('-o', '--overwrite', 'Overwrite existing files') do
53
54
  Planter.overwrite = true
54
55
  end
55
56
 
57
+ opts.on_tail('--base-dir DIRECTORY', 'Use an alternate base directory for config and templates') do |opt|
58
+ Planter.base_dir = opt
59
+ end
60
+
56
61
  opts.on_tail('-d', '--debug', 'Display version number') do
57
62
  Planter.debug = true
58
63
  end
@@ -100,6 +105,7 @@ elsif ARGV.count.zero?
100
105
  end
101
106
 
102
107
  ARGV.each do |template|
108
+ Planter.spinner.update(title: 'Initializing configuration')
103
109
  Planter.config = template
104
110
  app = Planter::Plant.new
105
111
  app.plant
@@ -1,11 +1,11 @@
1
1
  FROM ruby:2.6
2
2
  # RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
3
- RUN mkdir /howzit
4
- WORKDIR /howzit
5
- # COPY ./ /howzit/
3
+ RUN mkdir /planter
4
+ WORKDIR /planter
5
+ # COPY ./ /planter/
6
6
  RUN gem install bundler:2.2.29
7
- RUN apt-get update -y
8
- RUN apt-get install -y less vim
7
+ # RUN apt-get update -y
8
+ # RUN apt-get install -y less vim
9
9
  COPY ./docker/inputrc /root/.inputrc
10
10
  COPY ./docker/bash_profile /root/.bash_profile
11
11
  RUN mkdir -p /root/.config/planter/templates/test
@@ -1,8 +1,8 @@
1
1
  FROM ruby:2.7
2
2
  # RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
3
- RUN mkdir /howzit
4
- WORKDIR /howzit
5
- # COPY ./ /howzit/
3
+ RUN mkdir /planter
4
+ WORKDIR /planter
5
+ # COPY ./ /planter/
6
6
  RUN gem install bundler:2.2.29
7
7
  RUN apt-get update -y
8
8
  RUN apt-get install -y less vim
@@ -1,8 +1,8 @@
1
1
  FROM ruby:3.0.0
2
2
  # RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
3
- RUN mkdir /howzit
4
- WORKDIR /howzit
5
- # COPY ./ /howzit/
3
+ RUN mkdir /planter
4
+ WORKDIR /planter
5
+ # COPY ./ /planter/
6
6
  RUN gem install bundler:2.2.29
7
7
  RUN apt-get update -y
8
8
  RUN apt-get install -y less vim
data/lib/planter/array.rb CHANGED
@@ -24,5 +24,56 @@ module Planter
24
24
  end.join('{dw}/')
25
25
  out << '{dw}]{x}'
26
26
  end
27
+
28
+ ##
29
+ ## Stringify keys in an array of hashes or arrays
30
+ ##
31
+ ## @return [Array] Array with nested hash keys stringified
32
+ ##
33
+ def stringify_keys
34
+ each_with_object([]) do |v, arr|
35
+ arr << if v.is_a?(Hash)
36
+ v.stringify_keys
37
+ elsif v.is_a?(Array)
38
+ v.map { |x| x.is_a?(Hash) || x.is_a?(Array) ? x.stringify_keys : x }
39
+ else
40
+ v
41
+ end
42
+ end
43
+ end
44
+
45
+ ##
46
+ ## Symbolize keys in an array of hashes or arrays
47
+ ##
48
+ ## @return [Array] Array with nested hash keys symbolized
49
+ ##
50
+ def symbolize_keys
51
+ each_with_object([]) do |v, arr|
52
+ arr << if v.is_a?(Hash)
53
+ v.symbolize_keys
54
+ elsif v.is_a?(Array)
55
+ v.map { |x| x.is_a?(Hash) || x.is_a?(Array) ? x.symbolize_keys : x }
56
+ else
57
+ v
58
+ end
59
+ end
60
+ end
61
+
62
+ #
63
+ # Destructive version of #symbolize_keys
64
+ #
65
+ # @return [Array] Array with symbolized keys
66
+ #
67
+ def symbolize_keys!
68
+ replace deep_dup.symbolize_keys
69
+ end
70
+
71
+ ## Deep duplicate an array of hashes or arrays
72
+ ##
73
+ ## @return [Array] Deep duplicated array
74
+ ##
75
+ def deep_dup
76
+ map { |v| v.is_a?(Hash) || v.is_a?(Array) ? v.deep_dup : v.dup }
77
+ end
27
78
  end
28
79
  end
data/lib/planter/color.rb CHANGED
@@ -81,7 +81,7 @@ module Planter
81
81
  [:default, '0;39']
82
82
  ].map(&:freeze).freeze
83
83
 
84
- # Array of attribute keys only
84
+ # @return [Array<String>] all available color names
85
85
  ATTRIBUTE_NAMES = ATTRIBUTES.transpose.first
86
86
 
87
87
  # Returns true if Color supports the +feature+.
@@ -7,6 +7,7 @@ module Planter
7
7
  EXIT_CODES = {
8
8
  argument: 12,
9
9
  canceled: 1,
10
+ script: 10,
10
11
  config: 127,
11
12
  git: 129
12
13
  }.deep_freeze
@@ -55,5 +56,18 @@ module Planter
55
56
  super(msg)
56
57
  end
57
58
  end
59
+
60
+ #
61
+ # Script error
62
+ #
63
+ class ScriptError < StandardError
64
+ def initialize(msg = nil)
65
+ msg = msg ? "Script: #{msg}" : 'Script error'
66
+ Planter.spinner.error('(Error)')
67
+ Planter.notify(msg, :error, exit_code: EXIT_CODES[:script])
68
+
69
+ super(msg)
70
+ end
71
+ end
58
72
  end
59
73
  end
data/lib/planter/file.rb CHANGED
@@ -2,10 +2,93 @@
2
2
 
3
3
  # Main module
4
4
  module Planter
5
- def File.binary?(name)
6
- IO.read(name) do |f|
7
- f.each_byte { |x| x.nonzero? or return true }
5
+ # File helpers
6
+ class ::File
7
+ #
8
+ # Test if file is text
9
+ #
10
+ # @param name [String] file path
11
+ #
12
+ # @return [Boolean] File is text
13
+ #
14
+ def self.text?(name)
15
+ !binary?(name)
16
+ end
17
+
18
+ #
19
+ # Test if file is binary
20
+ #
21
+ # @param name [String] File path
22
+ #
23
+ # @return [Boolean] file is binary
24
+ #
25
+ def self.binary?(name)
26
+ return true if name.nil? || name.empty? || !File.exist?(name)
27
+
28
+ ascii = control = binary = 0
29
+
30
+ bytes = File.open(name, 'rb') { |io| io.read(1024) }
31
+ return true if bytes.nil? || bytes.empty?
32
+
33
+ bytes.each_byte do |bt|
34
+ case bt
35
+ when 0...32
36
+ control += 1
37
+ when 32...128
38
+ ascii += 1
39
+ else
40
+ binary += 1
41
+ end
42
+ end
43
+
44
+ first_test = binary.to_f / ascii > 0.05
45
+
46
+ first_test || second_test(name)
47
+ end
48
+
49
+ # Allowable text file types
50
+ TEXT_TYPES = %w[text ansi xml json yaml csv empty].freeze
51
+
52
+ #
53
+ # Secondary test with file command
54
+ #
55
+ # @param name [String] file path
56
+ #
57
+ # @return [Boolean] file is binary according to file command
58
+ #
59
+ def self.second_test(name)
60
+ if TTY::Which.exist?('file')
61
+ file_type, status = Open3.capture2e('file', name)
62
+ file_type = file_type.split(':')[1..-1].join(':').strip
63
+ if file_type =~ /Apple binary property list/ && TTY::Which.exist?('plutil')
64
+ `plutil -convert xml1 "#{name}"`
65
+ File.binary?(name)
66
+ else
67
+ status.success? && !text_type?(file_type)
68
+ end
69
+ else
70
+ false
71
+ end
72
+ end
73
+
74
+ #
75
+ # Tertiary test for binary file
76
+ #
77
+ # @param name [String] file path
78
+ #
79
+ # @return [Boolean] file is binary according to mdls
80
+ #
81
+ def self.third_test(name)
82
+ if TTY::Which.exist?('mdls')
83
+ file_type, status = Open3.capture2e('mdls', '-name', 'kMDItemContentTypeTree', name)
84
+ status.success? && !text_type?(file_type)
85
+ else
86
+ false
87
+ end
88
+ end
89
+
90
+ def self.text_type?(name)
91
+ TEXT_TYPES.any? { |type| name.downcase.include?(type) } || name.empty?
8
92
  end
9
- false
10
93
  end
11
94
  end
@@ -7,7 +7,7 @@ module Planter
7
7
  attr_accessor :operation
8
8
 
9
9
  # File path and target path
10
- attr_reader :file, :target
10
+ attr_reader :file, :target, :tags
11
11
 
12
12
  ##
13
13
  ## Initialize a FileEntry object
@@ -19,10 +19,14 @@ module Planter
19
19
  ## @return [FileEntry] a Hash of parameters
20
20
  ##
21
21
  def initialize(file, target, operation)
22
+ return nil unless File.exist?(file)
23
+
22
24
  @file = file
23
25
  @target = target
24
26
  @operation = operation
25
27
 
28
+ @tags = Tag.get(file)
29
+
26
30
  super()
27
31
  end
28
32
 
@@ -10,7 +10,7 @@ module Planter
10
10
  ##
11
11
  ## @param path [String] The base path for template
12
12
  ##
13
- def initialize(path)
13
+ def initialize(path = Planter.base_dir)
14
14
  @basedir = File.realdirpath(path)
15
15
 
16
16
  search_path = File.join(@basedir, '**/*')
@@ -30,9 +30,9 @@ module Planter
30
30
  end
31
31
 
32
32
  ##
33
- ## Public method for copying files based on their operator
33
+ ## Public method for copying @files to target based on their operator
34
34
  ##
35
- ## @return [Boolean] success
35
+ ## @return [Boolean] success or failure
36
36
  ##
37
37
  def copy
38
38
  @files.each do |file|
@@ -61,6 +61,14 @@ module Planter
61
61
  else
62
62
  copy_file(entry)
63
63
  end
64
+
65
+ apply_tags(entry)
66
+ end
67
+
68
+ def apply_tags(entry)
69
+ return unless Planter.config[:preserve_tags]
70
+
71
+ Tag.copy(entry.file, entry.target) if File.exist?(entry.target)
64
72
  end
65
73
 
66
74
  ##
@@ -89,36 +97,53 @@ module Planter
89
97
  end
90
98
 
91
99
  ##
92
- ## Copy tagged merge sections from source to target
100
+ ## Copy tagged merge sections from source to target. If merge tags do not exist in the file,
101
+ ## append the entire file contents to the target.
93
102
  ##
94
103
  ## @param entry [FileEntry] The file entry
95
104
  ##
105
+ ## @return [Boolean] success
106
+ ##
96
107
  def merge(entry)
97
108
  return copy_file(entry) if File.directory?(entry.file)
98
109
 
110
+ # Get the file type
99
111
  type = `file #{entry.file}`
100
112
  case type.sub(/^#{Regexp.escape(entry.file)}: /, '').split(/:/).first
101
113
  when /Apple binary property list/
114
+ # Convert to XML1 format
102
115
  `plutil -convert xml1 #{entry.file}`
103
116
  `plutil -convert xml1 #{entry.target}`
104
117
  content = IO.read(entry.file)
105
118
  when /data/
119
+ # Simply copy the file
106
120
  return copy_file(entry)
107
121
  else
122
+ # Copy the file if it is binary
108
123
  return copy_file(entry) if File.binary?(entry.file)
109
124
 
125
+ # Read the file content
110
126
  content = IO.read(entry.file)
111
127
  end
112
128
 
129
+ # Get the merge sections from the file, delimited by merge and /merge
113
130
  merges = content.scan(%r{(?<=\A|\n).{,4}merge *\n(.*?)\n.{,4}/merge}m)
114
131
  &.map { |m| m[0].strip.apply_variables.apply_regexes }
132
+ # If no merge sections are found, use the entire file
115
133
  merges = [content] if !merges || merges.empty?
116
- new_content = IO.read(entry.target)
117
- merges.delete_if { |m| new_content =~ /#{Regexp.escape(m)}/ }
134
+
135
+ # Get the existing content of the target file
136
+ target_content = IO.read(entry.target)
137
+
138
+ # Remove any merges that already exist in the target file
139
+ merges.delete_if { |m| target_content =~ /#{Regexp.escape(m)}/ }
140
+
141
+ # If there are any merge sections left, merge them with the target file
118
142
  if merges.count.positive?
119
- File.open(entry.target, 'w') { |f| f.puts "#{new_content.chomp}\n\n#{merges.join("\n\n")}" }
143
+ File.open(entry.target, 'w') { |f| f.puts "#{target_content.chomp}\n\n#{merges.join("\n\n")}" }
120
144
  Planter.notify("Merged #{entry.file} => #{entry.target} (#{merges.count} merges)", :debug)
121
145
  else
146
+ # If there are no merge sections left, copy the file instead
122
147
  copy_file(entry)
123
148
  end
124
149
  end
@@ -129,14 +154,25 @@ module Planter
129
154
  ## @param file [FileEntry] The file entry
130
155
  ## @param overwrite [Boolean] Force overwrite
131
156
  ##
157
+ ## @return [Boolean] success
158
+ ##
132
159
  def copy_file(file, overwrite: false)
160
+ # Check if the target file already exists
161
+ # If it does and overwrite is true, or Planter.overwrite is true,
162
+ # or if the file doesn't exist, then copy the file
133
163
  if !File.exist?(file.target) || overwrite || Planter.overwrite
164
+ # Make sure the target directory exists
134
165
  FileUtils.mkdir_p(File.dirname(file.target))
166
+ # Copy the file if it isn't a directory
135
167
  FileUtils.cp(file.file, file.target) unless File.directory?(file.file)
168
+ # Log a message to the console
136
169
  Planter.notify("Copied #{file.file} => #{file.target}", :debug)
170
+ # Return true to indicate success
137
171
  true
138
172
  else
173
+ # Log a message to the console
139
174
  Planter.notify("Skipped #{file.file} => #{file.target}", :debug)
175
+ # Return false to indicate that the copy was skipped
140
176
  false
141
177
  end
142
178
  end