linecook 0.6.2 → 1.0.0

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