planter-cli 0.0.3 → 0.0.4

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.
@@ -23,7 +23,7 @@ module Planter
23
23
  ## @return Class name representation of the object.
24
24
  ##
25
25
  def to_class_name
26
- strip.no_ext.split(/[-_ ]/).map(&:capitalize).join('').gsub(/[^a-z0-9]/i, '')
26
+ strip.no_ext.title_case.gsub(/[^a-z0-9]/i, '').sub(/^\S/, &:upcase)
27
27
  end
28
28
 
29
29
  ##
@@ -66,7 +66,7 @@ module Planter
66
66
  ## @return [String] Snake-cased version of string
67
67
  ##
68
68
  def snake_case
69
- strip.gsub(/\S[A-Z]/) { |pair| pair.split('').join('_') }
69
+ strip.gsub(/\S(?=[A-Z])/, '\0_')
70
70
  .gsub(/[ -]+/, '_')
71
71
  .gsub(/[^a-z0-9_]+/i, '')
72
72
  .gsub(/_+/, '_')
@@ -82,7 +82,7 @@ module Planter
82
82
  ## @return [String] Snake-cased version of string
83
83
  ##
84
84
  def camel_case
85
- strip.gsub(/[ _]+(\S)/) { Regexp.last_match(1).upcase }
85
+ strip.gsub(/(?<=[^a-z0-9])(\S)/) { Regexp.last_match(1).upcase }
86
86
  .gsub(/[^a-z0-9]+/i, '')
87
87
  .sub(/^(\w)/) { Regexp.last_match(1).downcase }
88
88
  end
@@ -96,20 +96,11 @@ module Planter
96
96
  ## @return [String] title cased string
97
97
  ##
98
98
  def title_case
99
- gsub(/\b(\w)/) { Regexp.last_match(1).upcase }
99
+ split(/\b(\w+)/).map(&:capitalize).join('')
100
100
  end
101
101
 
102
- ##
103
- ## Apply key/value substitutions to a string. Variables are represented as
104
- ## %%key%%, and the hash passed to the function is { key: value }
105
- ##
106
- ## @param last_only [Boolean] Only replace the last instance of %%key%%
107
- ##
108
- ## @return [String] string with variables substituted
109
- ##
110
- def apply_variables(last_only: false)
111
- content = dup.clean_encode
112
- mod_rx = '(?<mod>
102
+ # @return [String] Regular expression for matching variable modifiers
103
+ MOD_RX = '(?<mod>
113
104
  (?::
114
105
  (
115
106
  l(?:ow(?:er)?)?)?|
@@ -121,11 +112,75 @@ module Planter
121
112
  )?
122
113
  )*
123
114
  )'
115
+ # @return [String] regular expression string for default values
116
+ DEFAULT_RX = '(?:%(?<default>[^%]+))?'
117
+
118
+ #
119
+ # Apply default values to a string
120
+ #
121
+ # Default values are applied to variables that are not present in the variables hash,
122
+ # or whose value matches the default value
123
+ #
124
+ # @param variables [Hash] Hash of variable values
125
+ #
126
+ # @return [String] string with default values applied
127
+ #
128
+ def apply_defaults(variables)
129
+ # Perform an in-place substitution on the content string for default values
130
+ gsub(/%%(?<varname>[^%:]+)(?<mods>(?::[^%]+)*)%(?<default>[^%]+)%%/) do
131
+ # Capture the last match object
132
+ m = Regexp.last_match
133
+
134
+ # Check if the variable is not present in the variables hash
135
+ if !variables.key?(m['varname'].to_var)
136
+ # If the variable is not present, use the default value from the match
137
+ m['default'].apply_var_names
138
+ else
139
+ # Retrieve the default value for the variable from the configuration
140
+ vars = Planter.config[:variables].filter { |v| v[:key] == m['varname'] }
141
+ default = vars.first[:default] if vars.count.positive?
142
+ if default.nil?
143
+ m[0]
144
+ elsif variables[m['varname'].to_var] == default
145
+ # If the variable's value matches the default value, use the default value from the match
146
+ m['default'].apply_var_names
147
+ else
148
+ m[0]
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ #
155
+ # Destructive version of #apply_defaults
156
+ #
157
+ # @param variables [Hash] hash of variables to apply
158
+ #
159
+ # @return [String] string with defaults applied
160
+ #
161
+ def apply_defaults!(variables)
162
+ replace apply_defaults(variables)
163
+ end
124
164
 
125
- Planter.variables.each do |k, v|
165
+ ##
166
+ ## Apply key/value substitutions to a string. Variables are represented as
167
+ ## %%key%%, and the hash passed to the function is { key: value }
168
+ ##
169
+ ## @param last_only [Boolean] Only replace the last instance of %%key%%
170
+ ##
171
+ ## @return [String] string with variables substituted
172
+ ##
173
+ def apply_variables(variables: nil, last_only: false)
174
+ variables = variables.nil? ? Planter.variables : variables
175
+
176
+ content = dup.clean_encode
177
+
178
+ content = content.apply_defaults(variables)
179
+
180
+ variables.each do |k, v|
126
181
  if last_only
127
182
  pattern = "%%#{k.to_var}"
128
- content = content.reverse.sub(/(?mix)%%(?:(?<mod>.*?):)*(?<key>#{pattern.reverse})/) do
183
+ content = content.reverse.sub(/(?mix)%%(?:(?<mod>.*?):)*(?<key>#{pattern.reverse})/i) do
129
184
  m = Regexp.last_match
130
185
  if m['mod']
131
186
  m['mod'].reverse.split(/:/).each do |mod|
@@ -136,14 +191,16 @@ module Planter
136
191
  v.reverse
137
192
  end.reverse
138
193
  else
139
- rx = /(?mix)%%(?<key>#{k.to_var})#{mod_rx}%%/
194
+ rx = /(?mix)%%(?<key>#{k.to_var})#{MOD_RX}#{DEFAULT_RX}%%/
140
195
 
141
196
  content.gsub!(rx) do
142
197
  m = Regexp.last_match
143
198
 
144
- mods = m['mod']&.split(/:/)
145
- mods&.each do |mod|
146
- v = v.apply_mod(mod.normalize_mod)
199
+ if m['mod']
200
+ mods = m['mod']&.split(/:/)
201
+ mods&.each do |mod|
202
+ v = v.apply_mod(mod.normalize_mod)
203
+ end
147
204
  end
148
205
  v
149
206
  end
@@ -153,16 +210,49 @@ module Planter
153
210
  content
154
211
  end
155
212
 
213
+ #
214
+ # Handle $varname and ${varname} variable substitutions
215
+ #
216
+ # @return [String] String with variables substituted
217
+ #
218
+ def apply_var_names
219
+ sub(/\$\{?(?<varname>\w+)(?<mods>(?::\w+)+)?\}?/) do
220
+ m = Regexp.last_match
221
+ if Planter.variables.key?(m['varname'].to_var)
222
+ Planter.variables[m['varname'].to_var].apply_mods(m['mods'])
223
+ else
224
+ m
225
+ end
226
+ end
227
+ end
228
+
229
+ #
230
+ # Apply modifiers to a string
231
+ #
232
+ # @param mods [String] Colon separated list of modifiers to apply
233
+ #
234
+ # @return [String] string with modifiers applied
235
+ #
236
+ def apply_mods(mods)
237
+ content = dup
238
+ mods.split(/:/).each do |mod|
239
+ content.apply_mod!(mod.normalize_mod)
240
+ end
241
+ content
242
+ end
243
+
156
244
  ##
157
245
  ## Apply regex replacements from @config[:replacements]
158
246
  ##
159
247
  ## @return [String] string with regexes applied
160
248
  ##
161
- def apply_regexes
249
+ def apply_regexes(regexes = nil)
162
250
  content = dup.clean_encode
163
- return self unless Planter.config.key?(:replacements)
251
+ regexes = regexes.nil? && Planter.config.key?(:replacements) ? Planter.config[:replacements] : regexes
164
252
 
165
- Planter.config[:replacements].stringify_keys.each do |pattern, replacement|
253
+ return self unless regexes
254
+
255
+ regexes.stringify_keys.each do |pattern, replacement|
166
256
  pattern = Regexp.new(pattern) unless pattern.is_a?(Regexp)
167
257
  replacement = replacement.gsub(/\$(\d)/, '\\\1').apply_variables
168
258
  content.gsub!(pattern, replacement)
@@ -177,8 +267,8 @@ module Planter
177
267
  ##
178
268
  ## @return [String] string with variables substituted
179
269
  ##
180
- def apply_variables!(last_only: false)
181
- replace apply_variables(last_only: last_only)
270
+ def apply_variables!(variables: nil, last_only: false)
271
+ replace apply_variables(variables: variables, last_only: last_only)
182
272
  end
183
273
 
184
274
  ##
@@ -186,8 +276,8 @@ module Planter
186
276
  ##
187
277
  ## @return [String] string with variables substituted
188
278
  ##
189
- def apply_regexes!
190
- replace apply_regexes
279
+ def apply_regexes!(regexes = nil)
280
+ replace apply_regexes(regexes)
191
281
  end
192
282
 
193
283
  ##
@@ -222,6 +312,8 @@ module Planter
222
312
  ##
223
313
  ## @param mod [Symbol] The modifier to apply
224
314
  ##
315
+ ## @return [String] modified string
316
+ ##
225
317
  def apply_mod(mod)
226
318
  case mod
227
319
  when :slug
@@ -241,6 +333,17 @@ module Planter
241
333
  end
242
334
  end
243
335
 
336
+ #
337
+ # Destructive version of #apply_mod
338
+ #
339
+ # @param mod [String] modified string
340
+ #
341
+ # @return [<Type>] <description>
342
+ #
343
+ def apply_mod!(mod)
344
+ replace apply_mod(mod)
345
+ end
346
+
244
347
  ##
245
348
  ## Convert mod string to symbol
246
349
  ##
@@ -334,9 +437,11 @@ module Planter
334
437
  ##
335
438
  ##
336
439
  def coerce(type)
440
+ type = type.normalize_type
441
+
337
442
  case type
338
443
  when :date
339
- Chronic.parse(self)
444
+ Chronic.parse(self).strftime('%Y-%m-%d %H:%M')
340
445
  when :integer || :number
341
446
  to_i
342
447
  when :float
@@ -3,5 +3,5 @@
3
3
  # Primary module for this gem.
4
4
  module Planter
5
5
  # Current Planter version.
6
- VERSION = '0.0.3'
6
+ VERSION = '0.0.4'
7
7
  end
data/lib/planter.rb CHANGED
@@ -24,17 +24,19 @@ require_relative 'planter/prompt'
24
24
  require_relative 'planter/string'
25
25
  require_relative 'planter/filelist'
26
26
  require_relative 'planter/fileentry'
27
+ require_relative 'planter/script'
27
28
  require_relative 'planter/plant'
28
29
 
29
30
  # Main Journal module
30
31
  module Planter
31
32
  # Base directory for templates
32
- BASE_DIR = File.expand_path('~/.config/planter/')
33
-
34
33
  class << self
35
34
  include Color
36
35
  include Prompt
37
36
 
37
+ ## Base directory for templates
38
+ attr_writer :base_dir
39
+
38
40
  ## Debug mode
39
41
  attr_accessor :debug
40
42
 
@@ -72,7 +74,9 @@ module Planter
72
74
  def notify(string, notification_type = :info, exit_code: nil)
73
75
  case notification_type
74
76
  when :debug
75
- warn "\n{dw}#{string}{x}".x if @debug
77
+ return false unless @debug
78
+
79
+ warn "\n{dw}#{string}{x}".x
76
80
  when :error
77
81
  warn "{br}#{string}{x}".x
78
82
  when :warn
@@ -82,6 +86,8 @@ module Planter
82
86
  end
83
87
 
84
88
  Process.exit exit_code unless exit_code.nil?
89
+
90
+ true
85
91
  end
86
92
 
87
93
  ##
@@ -97,6 +103,10 @@ module Planter
97
103
  error_mark: '{br}✖{x}'.x)
98
104
  end
99
105
 
106
+ def base_dir
107
+ @base_dir ||= ENV['PLANTER_BASE_DIR'] || File.join(Dir.home, '.config', 'planter')
108
+ end
109
+
100
110
  ##
101
111
  ## Build a configuration from template name
102
112
  ##
@@ -105,26 +115,30 @@ module Planter
105
115
  ## @return [Hash] Configuration object
106
116
  ##
107
117
  def config=(template)
108
- Planter.spinner.update(title: 'Initializing configuration')
109
118
  @template = template
110
119
  Planter.variables ||= {}
111
- FileUtils.mkdir_p(BASE_DIR) unless File.directory?(BASE_DIR)
112
- base_config = File.join(BASE_DIR, 'config.yml')
120
+ FileUtils.mkdir_p(Planter.base_dir) unless File.directory?(Planter.base_dir)
121
+ base_config = File.join(Planter.base_dir, 'planter.yml')
113
122
 
114
- unless File.exist?(base_config)
123
+ if File.exist?(base_config)
124
+ @config = YAML.load(IO.read(base_config)).symbolize_keys
125
+ else
115
126
  default_base_config = {
116
127
  defaults: false,
117
128
  git_init: false,
118
129
  files: { '_planter.yml' => 'ignore' },
119
130
  color: true
120
131
  }
121
- File.open(base_config, 'w') { |f| f.puts(YAML.dump(default_base_config.stringify_keys)) }
122
- Planter.notify("New configuration written to #{config}, edit as needed.", :warn)
132
+ begin
133
+ File.open(base_config, 'w') { |f| f.puts(YAML.dump(default_base_config.stringify_keys)) }
134
+ rescue Errno::ENOENT
135
+ Planter.notify("Unable to create #{base_config}", :error)
136
+ end
137
+ @config = default_base_config.symbolize_keys
138
+ Planter.notify("New configuration written to #{base_config}, edit as needed.", :warn)
123
139
  end
124
140
 
125
- @config = YAML.load(IO.read(base_config)).symbolize_keys
126
-
127
- base_dir = File.join(BASE_DIR, 'templates', @template)
141
+ base_dir = File.join(Planter.base_dir, 'templates', @template)
128
142
  unless File.directory?(base_dir)
129
143
  notify("Template #{@template} does not exist", :error)
130
144
  res = Prompt.yn('Create template directory', default_response: false)
@@ -142,13 +156,36 @@ module Planter
142
156
  raise Errors::ConfigError.new "Parse error in configuration file:\n#{e.message}"
143
157
  end
144
158
 
159
+ ##
160
+ ## Execute a shell command and return a Boolean success response
161
+ ##
162
+ ## @param cmd [String] The shell command
163
+ ##
164
+ def pass_fail(cmd)
165
+ _, status = Open3.capture2("#{cmd} &> /dev/null")
166
+ status.exitstatus.zero?
167
+ end
168
+
169
+ ##
170
+ ## Patterns reader, file handling config
171
+ ##
172
+ ## @return [Hash] hash of file patterns
173
+ ##
174
+ def patterns
175
+ @patterns ||= process_patterns
176
+ end
177
+
178
+ private
179
+
145
180
  ##
146
181
  ## Load a template-specific configuration
147
182
  ##
148
183
  ## @return [Hash] updated config object
149
184
  ##
185
+ ## @api private
186
+ ##
150
187
  def load_template_config
151
- base_dir = File.join(BASE_DIR, 'templates', @template)
188
+ base_dir = File.join(Planter.base_dir, 'templates', @template)
152
189
  config = File.join(base_dir, '_planter.yml')
153
190
 
154
191
  unless File.exist?(config)
@@ -165,8 +202,9 @@ module Planter
165
202
  git_init: false,
166
203
  files: { '*.tmp' => 'ignore' }
167
204
  }
205
+ FileUtils.mkdir_p(base_dir)
168
206
  File.open(config, 'w') { |f| f.puts(YAML.dump(default_config.stringify_keys)) }
169
- puts "New configuration written to #{config}, please edit."
207
+ notify("New configuration written to #{config}, please edit.", :warn)
170
208
  Process.exit 0
171
209
  end
172
210
  @config = @config.deep_merge(YAML.load(IO.read(config)).symbolize_keys)
@@ -177,6 +215,8 @@ module Planter
177
215
  ##
178
216
  ## @param key [Symbol] The key in @config to convert
179
217
  ##
218
+ ## @api private
219
+ ##
180
220
  def config_array_to_hash(key)
181
221
  files = {}
182
222
  @config[key].each do |k, v|
@@ -185,20 +225,13 @@ module Planter
185
225
  @config[key] = files
186
226
  end
187
227
 
188
- ##
189
- ## Patterns reader, file handling config
190
- ##
191
- ## @return [Hash] hash of file patterns
192
- ##
193
- def patterns
194
- @patterns ||= process_patterns
195
- end
196
-
197
228
  ##
198
229
  ## Process :files in config into regex pattern/operator pairs
199
230
  ##
200
231
  ## @return [Hash] { regex => operator } hash
201
232
  ##
233
+ ## @api private
234
+ ##
202
235
  def process_patterns
203
236
  patterns = {}
204
237
  @config[:files].each do |file, oper|
@@ -208,15 +241,5 @@ module Planter
208
241
  end
209
242
  patterns
210
243
  end
211
-
212
- ##
213
- ## Execute a shell command and return a Boolean success response
214
- ##
215
- ## @param cmd [String] The shell command
216
- ##
217
- def pass_fail(cmd)
218
- _, status = Open3.capture2("#{cmd} &> /dev/null")
219
- status.exitstatus.zero?
220
- end
221
244
  end
222
245
  end
data/spec/config.yml ADDED
@@ -0,0 +1,2 @@
1
+ ---
2
+ git_init: false
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ::Array do
6
+ let(:array) { [1, 'value1', 'value2', { key1: 'value1', 'key2' => 'value2' }, %w[key1 key2]] }
7
+
8
+ describe '.stringify_keys' do
9
+ it 'converts string keys to strings' do
10
+ result = array.stringify_keys
11
+ expect(result).to eq([1, 'value1', 'value2', { 'key1' => 'value1', 'key2' => 'value2' }, %w[key1 key2]])
12
+ end
13
+ end
14
+
15
+ describe '.abbr_choices' do
16
+ it 'abbreviates the choices' do
17
+ arr = ['(o)ption 1', '(s)econd option', '(t)hird option']
18
+ result = arr.abbr_choices
19
+ expect(result).to match(%r{{xdw}\[{xbw}o{dw}/{xbw}s{dw}/{xbw}t{dw}\]{x}})
20
+ end
21
+
22
+ it 'handles a default' do
23
+ arr = ['(o)ption 1', '(s)econd option', '(t)hird option']
24
+ result = arr.abbr_choices(default: 'o')
25
+ expect(result).to match(%r{{xdw}\[{xbc}o{dw}/{xbw}s{dw}/{xbw}t{dw}\]{x}})
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Planter::FileEntry do
6
+ subject do
7
+ Planter::FileEntry.new(File.expand_path('spec/templates/test/test.rb'), File.expand_path('spec/test_out/test.rb'),
8
+ :ignore)
9
+ end
10
+
11
+ describe '#initialize' do
12
+ it 'makes a new instance' do
13
+ expect(subject).to be_a described_class
14
+ end
15
+ end
16
+
17
+ describe '#to_s' do
18
+ it 'returns the name of the file' do
19
+ expect(subject.to_s).to be_a(String)
20
+ end
21
+ end
22
+
23
+ describe '#inspect' do
24
+ it 'returns a string representation of the file' do
25
+ expect(subject.inspect).to be_a(String)
26
+ end
27
+ end
28
+
29
+ describe '#ask_operation' do
30
+ it 'returns :copy' do
31
+ expect(subject.ask_operation).to eq(:copy)
32
+ end
33
+
34
+ it 'returns :ignore for existing file' do
35
+ fileentry = Planter::FileEntry.new(File.expand_path('spec/templates/test/test.rb'), File.expand_path('spec/test_out/test2.rb'),
36
+ :ignore)
37
+ expect(fileentry.ask_operation).to eq(:ignore)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ::File do
6
+ describe '.binary?' do
7
+ it 'detects a non-binary text file' do
8
+ expect(File.binary?('spec/test_out/test2.rb')).to be(false)
9
+ end
10
+
11
+ it 'detects a binary image' do
12
+ expect(File.binary?('spec/test_out/image.png')).to be(true)
13
+ end
14
+
15
+ it 'recognizes json as text' do
16
+ expect(File.binary?('spec/test_out/doing.sublime-project')).to be(false)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Planter::FileList do
6
+ describe '#initialize' do
7
+ it 'initializes with an empty list' do
8
+ Planter.base_dir = File.expand_path('spec')
9
+ Planter.variables = { project: 'Untitled', script: 'Script', title: 'Title' }
10
+ Planter.config = 'test'
11
+ filelist = Planter::FileList.new
12
+ expect(filelist.files).not_to eq([])
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ::Hash do
6
+ let(:hash) { { 'key1' => 'value1', 'key2' => 'value2' } }
7
+
8
+ describe '.symbolize_keys' do
9
+ it 'converts string keys to symbols' do
10
+ string_hash = { 'key1' => 'value1', 'key2' => 'value2', 'key3' => ['value3'] }
11
+ result = string_hash.symbolize_keys
12
+ expect(result).to eq({ key1: 'value1', key2: 'value2', key3: ['value3'] })
13
+ end
14
+
15
+ it 'handles nested hashes' do
16
+ nested_hash = { 'outer' => { 'inner' => 'value' } }
17
+ result = nested_hash.symbolize_keys
18
+ expect(result).to eq({ outer: { inner: 'value' } })
19
+ end
20
+
21
+ it 'handles empty hashes' do
22
+ result = {}.symbolize_keys
23
+ expect(result).to eq({})
24
+ end
25
+ end
26
+
27
+ describe '.stringify_keys' do
28
+ it 'converts symbol keys to strings' do
29
+ symbol_hash = { key1: 'value1', key2: 'value2', key3: ['value3'] }
30
+ result = symbol_hash.stringify_keys
31
+ expect(result).to eq({ 'key1' => 'value1', 'key2' => 'value2', 'key3' => ['value3'] })
32
+ end
33
+
34
+ it 'handles nested hashes' do
35
+ nested_hash = { outer: { inner: 'value' } }
36
+ result = nested_hash.stringify_keys
37
+ expect(result).to eq({ 'outer' => { 'inner' => 'value' } })
38
+ end
39
+
40
+ it 'handles empty hashes' do
41
+ result = {}.stringify_keys
42
+ expect(result).to eq({})
43
+ end
44
+ end
45
+
46
+ describe '.deep_merge' do
47
+ it 'merges two hashes deeply' do
48
+ hash1 = { a: 1, b: { c: 2 }, f: [1, 2] }
49
+ hash2 = { b: { d: 3 }, e: 4, f: [3, 4] }
50
+ result = hash1.deep_merge(hash2)
51
+ expect(result).to eq({ a: 1, b: { c: 2, d: 3 }, e: 4, f: [1, 2, 3, 4] })
52
+ end
53
+
54
+ it 'handles empty hashes' do
55
+ result = {}.deep_merge({})
56
+ expect(result).to eq({})
57
+ end
58
+
59
+ it 'does not modify the original hashes' do
60
+ hash1 = { a: 1, b: { c: 2 }, f: 'test' }
61
+ hash2 = { b: { d: 3 }, e: 4, f: nil }
62
+ hash1.deep_merge(hash2)
63
+ expect(hash1).to eq({ a: 1, b: { c: 2 }, f: 'test' })
64
+ expect(hash2).to eq({ b: { d: 3 }, e: 4, f: nil })
65
+ end
66
+ end
67
+
68
+ describe '.deep_freeze' do
69
+ it 'freezes all nested hashes' do
70
+ hash = { a: 1, b: { c: 2 } }.deep_freeze
71
+ expect(hash).to be_frozen
72
+ expect(hash[:b]).to be_frozen
73
+ end
74
+ end
75
+
76
+ describe '.deep_thaw' do
77
+ it 'thaws all nested hashes' do
78
+ hash = { a: 1, b: { c: 2 } }.deep_freeze.deep_thaw
79
+ expect(hash).not_to be_frozen
80
+ expect(hash[:b]).not_to be_frozen
81
+ end
82
+ end
83
+ end
@@ -4,6 +4,7 @@ require 'spec_helper'
4
4
 
5
5
  describe Planter::Plant do
6
6
  Planter.accept_defaults = true
7
+ Planter.base_dir = File.expand_path('spec')
7
8
  subject(:ruby_gem) { Planter::Plant.new('test', { project: 'Untitled', script: 'Script', title: 'Title' }) }
8
9
 
9
10
  describe '.new' do