linecook 0.6.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/History +139 -0
  2. data/HowTo/Control Virtual Machines +106 -0
  3. data/HowTo/Generate Scripts +263 -0
  4. data/HowTo/Run Scripts +87 -0
  5. data/HowTo/Setup Virtual Machines +76 -0
  6. data/License.txt +1 -1
  7. data/README +78 -59
  8. data/bin/linecook +12 -5
  9. data/bin/linecook_run +45 -0
  10. data/bin/linecook_scp +50 -0
  11. data/lib/linecook.rb +1 -3
  12. data/lib/linecook/attributes.rb +49 -12
  13. data/lib/linecook/commands.rb +9 -4
  14. data/lib/linecook/commands/build.rb +69 -0
  15. data/lib/linecook/commands/command.rb +13 -3
  16. data/lib/linecook/commands/command_error.rb +6 -0
  17. data/lib/linecook/commands/env.rb +74 -8
  18. data/lib/linecook/commands/helper.rb +271 -24
  19. data/lib/linecook/commands/init.rb +10 -6
  20. data/lib/linecook/commands/package.rb +36 -18
  21. data/lib/linecook/commands/run.rb +66 -0
  22. data/lib/linecook/commands/snapshot.rb +114 -0
  23. data/lib/linecook/commands/ssh.rb +39 -0
  24. data/lib/linecook/commands/start.rb +34 -0
  25. data/lib/linecook/commands/state.rb +32 -0
  26. data/lib/linecook/commands/stop.rb +22 -0
  27. data/lib/linecook/commands/vbox_command.rb +130 -0
  28. data/lib/linecook/cookbook.rb +112 -55
  29. data/lib/linecook/package.rb +293 -109
  30. data/lib/linecook/proxy.rb +19 -0
  31. data/lib/linecook/recipe.rb +321 -62
  32. data/lib/linecook/template.rb +7 -101
  33. data/lib/linecook/test.rb +196 -141
  34. data/lib/linecook/test/command_parser.rb +75 -0
  35. data/lib/linecook/test/file_test.rb +153 -35
  36. data/lib/linecook/test/shell_test.rb +176 -0
  37. data/lib/linecook/utils.rb +25 -7
  38. data/lib/linecook/version.rb +4 -4
  39. data/templates/Rakefile +44 -47
  40. data/templates/_gitignore +1 -1
  41. data/templates/attributes/project_name.rb +4 -4
  42. data/templates/config/ssh +15 -0
  43. data/templates/files/help.txt +1 -0
  44. data/templates/helpers/project_name/assert_content_equal.erb +15 -0
  45. data/templates/helpers/project_name/create_dir.erb +9 -0
  46. data/templates/helpers/project_name/create_file.erb +8 -0
  47. data/templates/helpers/project_name/install_file.erb +8 -0
  48. data/templates/packages/abox.yml +4 -0
  49. data/templates/recipes/abox.rb +22 -0
  50. data/templates/recipes/abox_test.rb +14 -0
  51. data/templates/templates/todo.txt.erb +3 -0
  52. data/templates/test/project_name_test.rb +19 -0
  53. data/templates/test/test_helper.rb +14 -0
  54. metadata +43 -41
  55. data/cookbook +0 -0
  56. data/lib/linecook/commands/helpers.rb +0 -28
  57. data/lib/linecook/commands/vbox.rb +0 -85
  58. data/lib/linecook/helper.rb +0 -117
  59. data/lib/linecook/shell.rb +0 -11
  60. data/lib/linecook/shell/posix.rb +0 -145
  61. data/lib/linecook/shell/test.rb +0 -254
  62. data/lib/linecook/shell/unix.rb +0 -117
  63. data/lib/linecook/shell/utils.rb +0 -138
  64. data/templates/README +0 -90
  65. data/templates/files/file.txt +0 -1
  66. data/templates/helpers/project_name/echo.erb +0 -5
  67. data/templates/recipes/project_name.rb +0 -20
  68. data/templates/scripts/project_name.yml +0 -7
  69. data/templates/templates/template.txt.erb +0 -3
  70. data/templates/vbox/setup/virtual_box +0 -86
  71. data/templates/vbox/ssh/id_rsa +0 -27
  72. data/templates/vbox/ssh/id_rsa.pub +0 -1
data/lib/linecook/test.rb CHANGED
@@ -1,172 +1,227 @@
1
- require 'linecook/cookbook'
2
- require 'linecook/recipe'
1
+ require 'linecook/package'
3
2
  require 'linecook/test/file_test'
4
- require 'linecook/test/regexp_escape'
5
- require 'linecook/utils'
3
+ require 'linecook/test/shell_test'
6
4
 
7
5
  module Linecook
8
6
  module Test
7
+ module ClassMethods
8
+ def host
9
+ @host ||= ENV['LINECOOK_TEST_HOST'] || name
10
+ end
11
+
12
+ def use_host(host)
13
+ @host = host
14
+ end
15
+
16
+ def only_hosts(*patterns)
17
+ patterns.collect! do |pattern|
18
+ pattern.kind_of?(Regexp) ? pattern : /\A#{pattern}\z/
19
+ end
20
+
21
+ @skip_test_suite = false
22
+ unless patterns.any? {|pattern| host =~ pattern }
23
+ skip_test "not for host (#{host})"
24
+ end
25
+ end
26
+
27
+ # Causes a test suite to be skipped. If a message is given, it will
28
+ # print and notify the user the test suite has been skipped.
29
+ def skip_test(msg=nil)
30
+ @skip_test_suite = true
31
+ skip_messages << msg
32
+ end
33
+
34
+ # Modifies the default suite method to skip the suit unless
35
+ # run_test_suite is true. If the test is skipped, the skip_messages
36
+ # will be printed along with the default 'Skipping <Test>' message.
37
+ def suite # :nodoc:
38
+ if (@skip_test_suite ||= false)
39
+ skip_message = skip_messages.compact.join(', ')
40
+ puts "Skipping #{name}#{skip_message.empty? ? '' : ': ' + skip_message}"
41
+
42
+ # return an empty test suite of the appropriate name
43
+ ::Test::Unit::TestSuite.new(name)
44
+ else
45
+ super
46
+ end
47
+ end
48
+
49
+ protected
50
+
51
+ def skip_messages # :nodoc:
52
+ @skip_messages ||= []
53
+ end
54
+ end
55
+
56
+ module ModuleMethods
57
+ module_function
58
+
59
+ def included(base)
60
+ base.extend ClassMethods
61
+ base.extend ModuleMethods unless base.kind_of?(Class)
62
+ super
63
+ end
64
+ end
65
+
66
+ extend ModuleMethods
67
+
9
68
  include FileTest
69
+ include ShellTest
10
70
 
11
- attr_writer :cookbook
12
- attr_writer :script
13
- attr_writer :recipe
71
+ LINECOOK_DIR = File.expand_path('../../..', __FILE__)
72
+ LINECOOK = File.join(LINECOOK_DIR, 'bin/linecook')
73
+
74
+ def method_dir
75
+ @host_method_dir ||= begin
76
+ if test_host = ENV['LINECOOK_TEST_HOST']
77
+ File.join(super, test_host)
78
+ else
79
+ super
80
+ end
81
+ end
82
+ end
83
+
84
+ def remote_dir
85
+ method_dir[(user_dir.length + 1)..-1]
86
+ end
87
+
88
+ def ssh_config_file
89
+ method_ssh_config_file = path('config/ssh')
90
+ File.file?(method_ssh_config_file) ? method_ssh_config_file : 'config/ssh'
91
+ end
92
+
93
+ def setup_cookbook(configs=nil, project_dir=method_dir)
94
+ configs ||= Cookbook.config_file(project_dir)
95
+ @cookbook = Cookbook.setup(configs, project_dir)
96
+ end
14
97
 
15
98
  def cookbook
16
- @cookbook ||= Cookbook.init(user_dir)
99
+ @cookbook ||= setup_cookbook
17
100
  end
18
101
 
19
- def manifest
20
- cookbook.manifest
102
+ def setup_package(env={})
103
+ @package = Package.setup(env, cookbook)
21
104
  end
22
105
 
23
- def default_env
24
- {Package::CONFIG_KEY => {Package::MANIFEST_KEY => manifest}}
106
+ def package
107
+ @package ||= setup_package
25
108
  end
26
109
 
27
- def recipe
28
- @recipe ||= Recipe.new('recipe', default_env)
29
- end
30
-
31
- def build(env={})
32
- env = Utils.deep_merge(default_env, env)
33
- Recipe.build(env).export File.join(method_dir, 'scripts')
34
- end
35
-
36
- # Asserts whether or not the a and b strings are equal, with a more
37
- # readable output than assert_equal for large strings (especially large
38
- # strings with significant whitespace).
39
- #
40
- # One gotcha is that assert_output_equal lstrips indentation off of 'a',
41
- # so that these all pass:
42
- #
43
- # assert_output_equal %q{
44
- # line one
45
- # line two
46
- # }, "line one\nline two\n"
47
- #
48
- # assert_output_equal %q{
49
- # line one
50
- # line two
51
- # }, "line one\nline two\n
52
- #
53
- # assert_output_equal %q{
54
- # line one
55
- # line two
56
- # }, "line one\nline two\n"
57
- #
58
- # Use the assert_output_equal! method to prevent indentation stripping.
59
- def assert_output_equal(a, b, msg=nil)
60
- a = strip_indent(a)
61
- assert_output_equal!(a, b, msg)
110
+ def use_helpers(*helpers)
111
+ @helpers = helpers
62
112
  end
63
-
64
- # Same as assert_output_equal but without indentation stripping.
65
- def assert_output_equal!(a, b, msg=nil)
66
- if a == b
67
- assert true
68
- else
69
- flunk %Q{
70
- #{msg}
71
- ==================== expected output ====================
72
- #{whitespace_escape(a)}
73
- ======================== but was ========================
74
- #{whitespace_escape(b)}
75
- =========================================================
76
- }
77
- end
113
+
114
+ def helpers
115
+ @helpers ||= []
78
116
  end
79
-
80
- # Asserts whether or not b is like a (which should be a Regexp), and
81
- # provides a more readable output in the case of a failure as compared
82
- # with assert_match.
83
- #
84
- # If a is a string, then indentation is stripped off and it is turned
85
- # into a RegexpEscape. Using that syntax, all these pass:
86
- #
87
- # assert_alike %q{
88
- # the time is: :...:
89
- # now!
90
- # }, "the time is: #{Time.now}\nnow!\n"
91
- #
92
- # assert_alike %q{
93
- # the time is: :...:
94
- # now!
95
- # }, "the time is: #{Time.now}\nnow!\n"
96
- #
97
- # assert_alike %q{
98
- # the time is: :...:
99
- # now!
100
- # }, "the time is: #{Time.now}\nnow!\n"
101
- #
102
- # Use assert_alike! to prevent indentation stripping (conversion to a
103
- # RegexpEscape is still in effect).
104
- def assert_alike(a, b, msg=nil)
105
- a = strip_indent(a) if a.kind_of?(String)
106
- assert_alike!(a, b, msg)
117
+
118
+ def use_host(host)
119
+ @host = host
107
120
  end
108
-
109
- # Same as assert_alike but without indentation stripping.
110
- def assert_alike!(a, b, msg=nil)
111
- a = RegexpEscape.new(a) if a.kind_of?(String)
112
-
113
- if b =~ a
114
- assert true
115
- else
116
- flunk %Q{
117
- #{msg}
118
- ================= expected output like ==================
119
- #{whitespace_escape(a)}
120
- ======================== but was ========================
121
- #{whitespace_escape(b)}
122
- =========================================================
123
- }
124
- end
121
+
122
+ def host
123
+ @host ||= self.class.host
125
124
  end
126
-
127
- def assert_recipe(expected, &block)
128
- recipe.instance_eval(&block)
125
+
126
+ def runlist
127
+ @runlist ||= []
128
+ end
129
+
130
+ def setup_recipe(target_name=package.next_target_name('recipe'), mode=0700, &block)
131
+ recipe = package.setup_recipe(target_name, mode)
132
+ helpers.each {|helper| recipe.extend helper }
133
+
134
+ recipe.instance_eval(&block) if block_given?
135
+ runlist << target_name
136
+
137
+ @recipe = recipe
138
+ end
139
+
140
+ def recipe
141
+ @recipe ||= setup_recipe
142
+ end
143
+
144
+ def assert_recipe(expected, recipe=setup_recipe, &block)
145
+ recipe.instance_eval(&block) if block_given?
146
+ recipe.close
147
+
129
148
  assert_output_equal expected, recipe.result
149
+ recipe
130
150
  end
131
151
 
132
- def assert_recipe_match(expected, &block)
133
- recipe.instance_eval(&block)
152
+ def assert_recipe_matches(expected, recipe=setup_recipe, &block)
153
+ recipe.instance_eval(&block) if block_given?
154
+ recipe.close
155
+
134
156
  assert_alike expected, recipe.result
157
+ recipe
135
158
  end
136
-
137
- def assert_content(expected, build_path)
138
- registry = recipe.close
139
-
140
- assert_equal true, registry.has_key?(build_path), "not in registry: #{build_path}"
141
- assert_output_equal expected, File.read(registry[build_path]), build_path
159
+
160
+ def build_package(host=self.host)
161
+ package_dir = path("packages/#{host}")
162
+
163
+ package.build
164
+ package.export package_dir
165
+
166
+ package_dir
142
167
  end
143
168
 
144
- private
145
-
146
- # helper for stripping indentation off a string
147
- def strip_indent(str) # :nodoc:
148
- if str =~ /\A\s*?\n( *)(.*)\z/m
149
- indent, str = $1, $2, $3
150
-
151
- if indent.length > 0
152
- str.gsub!(/^ {0,#{indent.length}}/, '')
153
- end
154
- end
155
-
156
- str
169
+ def run_package(options={}, host=self.host)
170
+ options['remote_script'] ||= runlist.join(',')
171
+
172
+ build_package host
173
+ run_project options, host
157
174
  end
158
-
159
- # helper for formatting escaping whitespace into readable text
160
- def whitespace_escape(str) # :nodoc:
161
- str.to_s.gsub(/\s/) do |match|
162
- case match
163
- when "\n" then "\\n\n"
164
- when "\t" then "\\t"
165
- when "\r" then "\\r"
166
- when "\f" then "\\f"
167
- else match
175
+
176
+ def build_project(options={})
177
+ options = {
178
+ 'project_dir' => method_dir,
179
+ 'quiet' => true
180
+ }.merge(options)
181
+
182
+ linecook('build', options)
183
+ end
184
+
185
+ # pick up user dir as a gem... bundler!
186
+ def run_project(options={}, *package_names)
187
+ options = {
188
+ 'ssh_config_file' => ssh_config_file,
189
+ 'project_dir' => method_dir,
190
+ 'remote_dir' => remote_dir,
191
+ 'quiet' => true,
192
+ }.merge(options)
193
+
194
+ linecook('run', options, *package_names)
195
+ end
196
+
197
+ def linecook(cmd, options={}, *args)
198
+ stdout = prepare("log/#{cmd}.out")
199
+ stderr = prepare("log/#{cmd}.err")
200
+
201
+ command = "#{linecook_cmd(cmd, options, *args)} 2> '#{stderr}' > '#{stdout}'"
202
+ system(command)
203
+
204
+ [File.read(stdout), "% #{command}\n#{File.read(stderr)}"]
205
+ end
206
+
207
+ def linecook_cmd(cmd, options={}, *args)
208
+ opts = []
209
+ options.each_pair do |key, value|
210
+ key = key.gsub('_', '-')
211
+
212
+ case value
213
+ when true
214
+ opts << "--#{key}"
215
+ when nil, false
216
+ else
217
+ opts << "--#{key} '#{value}'"
168
218
  end
169
219
  end
220
+
221
+ args = args.collect! {|arg| "'#{arg}'" }
222
+
223
+ cmd = [LINECOOK, cmd] + opts.sort + args
224
+ cmd.join(' ')
170
225
  end
171
226
  end
172
227
  end
@@ -0,0 +1,75 @@
1
+ module Linecook
2
+ module Test
3
+ class CommandParser
4
+ attr_reader :ps1
5
+ attr_reader :ps2
6
+ attr_reader :prefix
7
+ attr_reader :suffix
8
+
9
+ def initialize(options={})
10
+ options = {
11
+ :ps1 => '% ',
12
+ :ps2 => '> ',
13
+ :prefix => '0<&- 2>&1 ',
14
+ :suffix => ''
15
+ }.merge(options)
16
+
17
+ @ps1 = options[:ps1]
18
+ @ps2 = options[:ps2]
19
+ @prefix = options[:prefix]
20
+ @suffix = options[:suffix]
21
+ end
22
+
23
+ def parse_cmd(cmd)
24
+ cmd =~ /.*?#\s*(?:\[(\d+)\])?\s*(\.{3})?/
25
+ exit_status = $1 ? $1.to_i : 0
26
+ output = $2 ? nil : ""
27
+
28
+ [cmd, output, exit_status]
29
+ end
30
+
31
+ def parse(script)
32
+ commands = []
33
+
34
+ command, output, exit_status = nil, "", 0
35
+ script.each_line do |line|
36
+ case
37
+ when line.index(ps1) == 0
38
+ if command
39
+ commands << ["#{prefix}#{command}#{suffix}", output, exit_status]
40
+ end
41
+
42
+ command, output, exit_status = parse_cmd lchomp(ps1, line)
43
+
44
+ when command.nil?
45
+ unless line.strip.empty?
46
+ command, output, exit_status = parse_cmd(line)
47
+ end
48
+
49
+ when line.index(ps2) == 0
50
+ command << lchomp(ps2, line)
51
+
52
+ when output.nil?
53
+ output = line
54
+
55
+ else
56
+ output << line
57
+ end
58
+ end
59
+
60
+ if command
61
+ commands << ["#{prefix}#{command}#{suffix}", output, exit_status]
62
+ end
63
+
64
+ commands
65
+ end
66
+
67
+ private
68
+
69
+ def lchomp(prefix, line) # :nodoc:
70
+ length = prefix.length
71
+ line[length, line.length - length]
72
+ end
73
+ end
74
+ end
75
+ end
@@ -4,11 +4,101 @@ module Linecook
4
4
  module ClassMethods
5
5
  attr_accessor :class_dir
6
6
 
7
- def self.extended(base)
7
+ attr_reader :cleanup_method_registry
8
+
9
+ def cleanup_methods
10
+ @cleanup_methods ||= begin
11
+ cleanup_methods = {}
12
+
13
+ ancestors.reverse.each do |ancestor|
14
+ next unless ancestor.kind_of?(ClassMethods)
15
+ ancestor.cleanup_method_registry.each_pair do |key, value|
16
+ if value.nil?
17
+ cleanup_methods.delete(key)
18
+ else
19
+ cleanup_methods[key] = value
20
+ end
21
+ end
22
+ end
23
+
24
+ cleanup_methods
25
+ end
26
+ end
27
+
28
+ def reset_cleanup_methods
29
+ @cleanup_methods = nil
30
+ end
31
+
32
+ protected
33
+
34
+ def self.initialize(base)
8
35
  # Infers the test directory from the calling file.
9
36
  # 'some_class_test.rb' => 'some_class_test'
10
- calling_file = caller[2].gsub(/:\d+(:in .*)?$/, "")
37
+ calling_file = caller[1].gsub(/:\d+(:in .*)?$/, "")
11
38
  base.class_dir = calling_file.chomp(File.extname(calling_file))
39
+
40
+ base.reset_cleanup_methods
41
+ unless base.instance_variable_defined?(:@cleanup_method_registry)
42
+ base.instance_variable_set(:@cleanup_method_registry, {})
43
+ end
44
+
45
+ unless base.instance_variable_defined?(:@cleanup_paths)
46
+ base.instance_variable_set(:@cleanup_paths, ['.'])
47
+ end
48
+
49
+ unless base.instance_variable_defined?(:@cleanup)
50
+ base.instance_variable_set(:@cleanup, true)
51
+ end
52
+ end
53
+
54
+ def inherited(base) # :nodoc:
55
+ ClassMethods.initialize(base)
56
+ super
57
+ end
58
+
59
+ def define_method_cleanup(method_name, dirs)
60
+ reset_cleanup_methods
61
+ cleanup_method_registry[method_name.to_sym] = dirs
62
+ end
63
+
64
+ def remove_method_cleanup(method_name)
65
+ reset_cleanup_methods
66
+ cleanup_method_registry.delete(method_name.to_sym)
67
+ end
68
+
69
+ def undef_method_cleanup(method_name)
70
+ reset_cleanup_methods
71
+ cleanup_method_registry[method_name.to_sym] = nil
72
+ end
73
+
74
+ def cleanup_paths(*dirs)
75
+ @cleanup_paths = dirs
76
+ end
77
+
78
+ def cleanup(*method_names)
79
+ if method_names.empty?
80
+ @cleanup = true
81
+ else
82
+ method_names.each do |method_name|
83
+ define_method_cleanup method_name, @cleanup_paths
84
+ end
85
+ end
86
+ end
87
+
88
+ def no_cleanup(*method_names)
89
+ if method_names.empty?
90
+ @cleanup = false
91
+ else
92
+ method_names.each do |method_name|
93
+ undef_method_cleanup method_name
94
+ end
95
+ end
96
+ end
97
+
98
+ def method_added(sym)
99
+ if @cleanup && !cleanup_method_registry.has_key?(sym.to_sym) && sym.to_s[0, 5] == "test_"
100
+ cleanup sym
101
+ end
12
102
  end
13
103
  end
14
104
 
@@ -16,61 +106,89 @@ module Linecook
16
106
  module_function
17
107
 
18
108
  def included(base)
19
- base.extend base.kind_of?(Class) ? ClassMethods : ModuleMethods
109
+ base.extend ClassMethods
110
+ base.extend ModuleMethods unless base.kind_of?(Class)
111
+
112
+ ClassMethods.initialize(base)
20
113
  super
21
114
  end
22
115
  end
23
-
116
+
24
117
  extend ModuleMethods
25
-
26
- attr_reader :user_dir
27
- attr_reader :method_dir
28
-
118
+
29
119
  def setup
30
120
  super
31
- @user_dir = Dir.pwd
32
- @method_dir = File.expand_path(method_name, self.class.class_dir)
33
-
34
- cleanup method_dir
35
- FileUtils.mkdir_p method_dir
36
- Dir.chdir method_dir
121
+ cleanup
37
122
  end
38
-
39
- def teardown
40
- Dir.chdir user_dir
41
123
 
124
+ def teardown
125
+ Dir.chdir(user_dir)
126
+
42
127
  unless ENV["KEEP_OUTPUTS"] == "true"
43
- cleanup class_dir
128
+ cleanup
129
+
130
+ dir = method_dir
131
+ while dir != class_dir
132
+ dir = File.dirname(dir)
133
+ Dir.rmdir(dir)
134
+ end rescue(SystemCallError)
44
135
  end
45
-
136
+
46
137
  super
47
138
  end
48
-
49
- def cleanup(dir)
50
- FileUtils.rm_r(dir) if File.exists?(dir)
139
+
140
+ def user_dir
141
+ @user_dir ||= File.expand_path('.')
51
142
  end
52
-
143
+
144
+ def class_dir
145
+ @class_dir ||= File.expand_path(self.class.class_dir, user_dir)
146
+ end
147
+
148
+ def method_dir
149
+ @method_dir ||= File.expand_path(method_name.to_s, class_dir)
150
+ end
151
+
152
+ def cleanup_methods
153
+ self.class.cleanup_methods
154
+ end
155
+
156
+ def cleanup
157
+ if cleanup_paths = cleanup_methods[method_name.to_sym]
158
+ cleanup_paths.each {|relative_path| remove(relative_path) }
159
+ end
160
+ end
161
+
53
162
  def path(relative_path)
54
- File.expand_path(relative_path, method_dir)
163
+ path = File.expand_path(relative_path, method_dir)
164
+
165
+ unless path.index(method_dir) == 0
166
+ raise "does not make a path relative to method_dir: #{relative_path.inspect}"
167
+ end
168
+
169
+ path
55
170
  end
56
171
 
57
- def prepare(relative_path)
172
+ def prepare(relative_path, content=nil, &block)
58
173
  target = path(relative_path)
59
174
 
60
- target_dir = File.dirname(target)
61
- FileUtils.mkdir_p(target_dir) unless File.exists?(target_dir)
175
+ if File.exists?(target)
176
+ FileUtils.rm(target)
177
+ else
178
+ target_dir = File.dirname(target)
179
+ FileUtils.mkdir_p(target_dir) unless File.exists?(target_dir)
180
+ end
181
+
182
+ FileUtils.touch(target)
183
+ File.open(target, 'w') {|io| io << content } if content
184
+ File.open(target, 'a', &block) if block
62
185
 
63
186
  target
64
187
  end
65
188
 
66
- def file(relative_path, &block)
67
- target = prepare(relative_path)
68
- block ? File.open(target, 'w', &block) : FileUtils.touch(target)
69
- target
70
- end
71
-
72
- def class_dir
73
- self.class.class_dir
189
+ def remove(relative_path)
190
+ full_path = path(relative_path)
191
+ FileUtils.rm_r(full_path) if File.exists?(full_path)
74
192
  end
75
193
  end
76
194
  end