planter-cli 0.0.3 → 3.0.1

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.
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