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.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/.rubocop.yml +5 -7
- data/CHANGELOG.md +21 -0
- data/README.md +28 -1
- data/Rakefile +54 -18
- data/bin/plant +6 -0
- data/docker/Dockerfile-2.6 +5 -5
- data/docker/Dockerfile-2.7 +3 -3
- data/docker/Dockerfile-3.0 +3 -3
- data/lib/planter/array.rb +51 -0
- data/lib/planter/color.rb +1 -1
- data/lib/planter/errors.rb +14 -0
- data/lib/planter/file.rb +87 -4
- data/lib/planter/fileentry.rb +5 -1
- data/lib/planter/filelist.rb +43 -7
- data/lib/planter/hash.rb +81 -84
- data/lib/planter/plant.rb +4 -10
- data/lib/planter/prompt.rb +6 -3
- data/lib/planter/script.rb +24 -12
- data/lib/planter/string.rb +134 -29
- data/lib/planter/tag.rb +54 -0
- data/lib/planter/version.rb +1 -1
- data/lib/planter.rb +60 -34
- data/planter-cli.gemspec +1 -0
- data/spec/config.yml +2 -0
- data/spec/planter/array_spec.rb +28 -0
- data/spec/planter/file_entry_spec.rb +40 -0
- data/spec/planter/file_spec.rb +19 -0
- data/spec/planter/filelist_spec.rb +15 -0
- data/spec/planter/hash_spec.rb +110 -0
- data/spec/planter/plant_spec.rb +1 -0
- data/spec/planter/script_spec.rb +80 -0
- data/spec/planter/string_spec.rb +215 -2
- data/spec/planter/symbol_spec.rb +23 -0
- data/spec/planter.yml +6 -0
- data/spec/planter_spec.rb +82 -0
- data/spec/scripts/test.sh +3 -0
- data/spec/scripts/test_fail.sh +3 -0
- data/spec/spec_helper.rb +8 -2
- data/spec/templates/test/%%project:snake%%.rtf +10 -0
- data/spec/templates/test/Rakefile +6 -0
- data/spec/templates/test/_planter.yml +12 -0
- data/spec/templates/test/_scripts/test.sh +3 -0
- data/spec/templates/test/_scripts/test_fail.sh +3 -0
- data/spec/templates/test/test.rb +5 -0
- data/spec/test_out/image.png +0 -0
- data/spec/test_out/test2.rb +5 -0
- data/src/_README.md +28 -1
- metadata +57 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b4906c83392316be6ac61380bd0d2401a847f4c08f6afc91bdfbd4858980568
|
4
|
+
data.tar.gz: 7253e2ec572be45e7543d82ad2b1dde685fc5b8ba7b256ec914b0575e44ad507
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98fd1c334e3d2efdcdc10171d7a5b5f81898e9a03890bc08cd65e2f3c8a10941cfe46e5e5b23584f28c87aad3b85471ab544c3665049c5abfd911006b40bfbe8
|
7
|
+
data.tar.gz: a7a86edaf7bf49cb65cde877ab4aec8bd92a6f27a90c09f526e50f9f1ddc84c27655e67bfea75b9463c8033601ae1961e4854bce76f4e268bfe87aa3171c7677
|
data/.gitignore
CHANGED
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/
|
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
|
-
|
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/
|
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 (#{
|
97
|
+
spinner = TTY::Spinner.new("[:spinner] Running tests (#{version})...", hide_cursor: true)
|
111
98
|
|
112
99
|
spinner.auto_spin
|
113
|
-
|
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
|
data/docker/Dockerfile-2.6
CHANGED
@@ -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 /
|
4
|
-
WORKDIR /
|
5
|
-
# COPY ./ /
|
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
|
data/docker/Dockerfile-2.7
CHANGED
@@ -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 /
|
4
|
-
WORKDIR /
|
5
|
-
# COPY ./ /
|
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/docker/Dockerfile-3.0
CHANGED
@@ -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 /
|
4
|
-
WORKDIR /
|
5
|
-
# COPY ./ /
|
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
data/lib/planter/errors.rb
CHANGED
@@ -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
|
-
|
6
|
-
|
7
|
-
|
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
|
data/lib/planter/fileentry.rb
CHANGED
@@ -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
|
|
data/lib/planter/filelist.rb
CHANGED
@@ -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
|
-
|
117
|
-
|
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 "#{
|
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
|