linecook 1.2.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/{History → History.rdoc} +3 -2
  2. data/README.rdoc +93 -0
  3. data/bin/linecook +32 -56
  4. data/bin/linecook_run +19 -6
  5. data/bin/linecook_scp +12 -4
  6. data/doc/vm_setup.rdoc +75 -0
  7. data/lib/linecook.rb +3 -2
  8. data/lib/linecook/attributes.rb +33 -8
  9. data/lib/linecook/command.rb +61 -0
  10. data/lib/linecook/command_set.rb +85 -0
  11. data/lib/linecook/command_utils.rb +20 -0
  12. data/lib/linecook/commands/build.rb +108 -57
  13. data/lib/linecook/commands/compile.rb +181 -0
  14. data/lib/linecook/commands/{helper.rb → compile_helper.rb} +123 -94
  15. data/lib/linecook/commands/run.rb +43 -39
  16. data/lib/linecook/commands/snapshot.rb +24 -24
  17. data/lib/linecook/commands/ssh.rb +7 -7
  18. data/lib/linecook/commands/start.rb +10 -10
  19. data/lib/linecook/commands/state.rb +7 -7
  20. data/lib/linecook/commands/stop.rb +3 -3
  21. data/lib/linecook/commands/{vbox_command.rb → virtual_box_command.rb} +31 -29
  22. data/lib/linecook/cookbook.rb +149 -131
  23. data/lib/linecook/executable.rb +28 -0
  24. data/lib/linecook/package.rb +177 -361
  25. data/lib/linecook/proxy.rb +4 -10
  26. data/lib/linecook/recipe.rb +289 -369
  27. data/lib/linecook/test.rb +114 -98
  28. data/lib/linecook/utils.rb +31 -41
  29. data/lib/linecook/version.rb +2 -6
  30. metadata +120 -68
  31. data/HowTo/Control Virtual Machines +0 -106
  32. data/HowTo/Generate Scripts +0 -268
  33. data/HowTo/Run Scripts +0 -87
  34. data/HowTo/Setup Virtual Machines +0 -76
  35. data/README +0 -117
  36. data/lib/linecook/commands.rb +0 -11
  37. data/lib/linecook/commands/command.rb +0 -58
  38. data/lib/linecook/commands/command_error.rb +0 -12
  39. data/lib/linecook/commands/env.rb +0 -89
  40. data/lib/linecook/commands/init.rb +0 -86
  41. data/lib/linecook/commands/package.rb +0 -57
  42. data/lib/linecook/template.rb +0 -17
  43. data/lib/linecook/test/command_parser.rb +0 -75
  44. data/lib/linecook/test/file_test.rb +0 -197
  45. data/lib/linecook/test/regexp_escape.rb +0 -86
  46. data/lib/linecook/test/shell_test.rb +0 -177
  47. data/lib/linecook/test/shim.rb +0 -71
  48. data/templates/Gemfile +0 -3
  49. data/templates/Rakefile +0 -146
  50. data/templates/_gitignore +0 -4
  51. data/templates/attributes/project_name.rb +0 -3
  52. data/templates/config/ssh +0 -14
  53. data/templates/cookbook +0 -10
  54. data/templates/files/example.txt +0 -1
  55. data/templates/helpers/project_name/echo.erb +0 -4
  56. data/templates/packages/abox.yml +0 -2
  57. data/templates/project_name.gemspec +0 -30
  58. data/templates/recipes/abox.rb +0 -16
  59. data/templates/templates/example.erb +0 -1
  60. data/templates/test/project_name_test.rb +0 -24
  61. data/templates/test/test_helper.rb +0 -14
@@ -1,19 +1,13 @@
1
1
  module Linecook
2
2
  # A proxy used to chain method calls back to a recipe.
3
- class Proxy
3
+ class Proxy < BasicObject
4
4
  def initialize(recipe)
5
5
  @recipe = recipe
6
6
  end
7
-
8
- # Proxies to recipe.chain.
7
+
8
+ # Invokes method via recipe.chain.
9
9
  def method_missing(*args, &block)
10
- @recipe.chain(*args, &block)
11
- end
12
-
13
- # Returns an empty string, such that the proxy makes no text when it is
14
- # accidentally put into a target by a helper.
15
- def to_s
16
- ''
10
+ @recipe.chain.__send__(*args, &block)
17
11
  end
18
12
  end
19
13
  end
@@ -1,369 +1,289 @@
1
- require 'linecook/attributes'
2
- require 'linecook/proxy'
3
- require 'linecook/utils'
4
- require 'erb'
5
- require 'stringio'
6
-
7
- module Linecook
8
- # Recipe is the context in which recipes are evaluated (literally). Recipe
9
- # uses compiled ERB snippets to build text using method calls. For example:
10
- #
11
- # module Helper
12
- # # This is an ERB template compiled to write to a Recipe.
13
- # #
14
- # # compiler = ERB::Compiler.new('<>')
15
- # # compiler.put_cmd = "write"
16
- # # compiler.insert_cmd = "write"
17
- # # compiler.compile("echo '<%= args.join(' ') %>'\n")
18
- # #
19
- # def echo(*args)
20
- # write "echo '"; write(( args.join(' ') ).to_s); write "'\n"
21
- # end
22
- # end
23
- #
24
- # package = Package.new
25
- # recipe = package.setup_recipe
26
- #
27
- # recipe.extend Helper
28
- # recipe.instance_eval do
29
- # echo 'a', 'b c'
30
- # echo 'X Y'.downcase, :z
31
- # end
32
- #
33
- # "\n" + recipe.result
34
- # # => %{
35
- # # echo 'a b c'
36
- # # echo 'x y z'
37
- # # }
38
- #
39
- class Recipe
40
-
41
- # The recipe package
42
- attr_reader :_package_
43
-
44
- # The recipe target
45
- attr_reader :_target_
46
-
47
- # The target name of self in package
48
- attr_reader :_target_name_
49
-
50
- # The recipe proxy
51
- attr_reader :_proxy_
52
-
53
- # The current target for self set as needed during captures; equal to
54
- # _target_ otherwise.
55
- attr_reader :target
56
-
57
- # The current target_name for self set as needed during captures; equal to
58
- # _target_name_ otherwise.
59
- attr_reader :target_name
60
-
61
- def initialize(package, target_name, mode)
62
- @_package_ = package
63
- @_target_name_ = target_name
64
- @_target_ = package.setup_tempfile(target_name, mode)
65
- @_proxy_ = Proxy.new(self)
66
-
67
- @target_name = @_target_name_
68
- @target = @_target_
69
-
70
- @chain = false
71
- @attributes = {}
72
- @indents = []
73
- @outdents = []
74
- end
75
-
76
- # Closes _target_ and returns self.
77
- def close
78
- unless closed?
79
- _target_.close
80
- end
81
- self
82
- end
83
-
84
- # Closes _target_ and returns self, and unregisters _target_ from the
85
- # package. Useful for recipes that only generate other files.
86
- def close!
87
- _package_.registry.delete _target_name_
88
- close
89
- end
90
-
91
- # Returns true if _target_ is closed.
92
- def closed?
93
- _target_.closed?
94
- end
95
-
96
- # Returns the current contents of target, or the contents of _target_ if
97
- # closed? is true.
98
- def result
99
- if closed?
100
- _package_.content(_target_name_)
101
- else
102
- target.flush
103
- target.rewind
104
- target.read
105
- end
106
- end
107
-
108
- # Loads the specified attributes file and merges the results into attrs. A
109
- # block may be given to specify attrs as well; it will be evaluated in the
110
- # context of an Attributes instance.
111
- def attributes(attributes_name=nil, &block)
112
- attributes = _package_.load_attributes(attributes_name)
113
-
114
- if block_given?
115
- attributes.instance_eval(&block)
116
- end
117
-
118
- @attributes = Utils.deep_merge(@attributes, attributes.to_hash)
119
- @attrs = nil
120
- self
121
- end
122
-
123
- # Returns the package env merged over any attrs specified by attributes.
124
- # The attrs hash should be treated as if it were read-only because changes
125
- # could alter the package env and thereby spill over into other recipes.
126
- def attrs
127
- @attrs ||= Utils.deep_merge(@attributes, _package_.env)
128
- end
129
-
130
- # Looks up and extends self with the specified helper.
131
- def helpers(helper_name)
132
- extend _package_.load_helper(helper_name)
133
- end
134
-
135
- # Returns an expression that evaluates to the package dir, assuming that
136
- # $0 evaluates to the full path to the current recipe.
137
- def package_dir
138
- "${0%/#{target_name}}"
139
- end
140
-
141
- # The path to the named target as it should be referenced in the final
142
- # script, specifically target_name joined to package_dir.
143
- def target_path(target_name)
144
- File.join(package_dir, target_name)
145
- end
146
-
147
- # Registers the specified file into package and returns the target_path to
148
- # the file.
149
- def file_path(file_name, target_name=file_name, mode=0600)
150
- _package_.build_file(target_name, file_name, mode)
151
- target_path target_name
152
- end
153
-
154
- # Looks up, builds, and registers the specified template and returns the
155
- # target_path to the resulting file.
156
- def template_path(template_name, target_name=template_name.chomp('.erb'), mode=0600, locals={'attrs' => attrs})
157
- _package_.build_template(target_name, template_name, mode, locals)
158
- target_path target_name
159
- end
160
-
161
- # Looks up, builds, and registers the specified recipe and returns the
162
- # target_path to the resulting file.
163
- def recipe_path(recipe_name, target_name=recipe_name, mode=0700)
164
- _package_.build_recipe(target_name, recipe_name, mode)
165
- target_path target_name
166
- end
167
-
168
- # Captures the output for a block, registers it, and returns the
169
- # target_path to the resulting file. The current target and target_name
170
- # are updated for the duration of the block.
171
- def capture_path(target_name, mode=0600)
172
- tempfile = _package_.setup_tempfile(target_name, mode)
173
- capture_block(tempfile) do
174
- current = @target_name
175
-
176
- begin
177
- @target_name = target_name
178
- yield
179
- ensure
180
- @target_name = current
181
- end
182
-
183
- end
184
- tempfile.close
185
-
186
- target_path target_name
187
- end
188
-
189
- # Captures output to the target for the duration of a block. Returns the
190
- # capture target.
191
- def capture_block(target=StringIO.new)
192
- current = @target
193
-
194
- begin
195
- @target = target
196
- yield
197
- ensure
198
- @target = current
199
- end
200
-
201
- target
202
- end
203
-
204
- # Captures and returns output for the duration of a block by redirecting
205
- # target to a temporary buffer.
206
- def capture_str
207
- capture_block { yield }.string
208
- end
209
-
210
- # Writes input to target using 'write'. Returns self.
211
- def write(input)
212
- target.write input
213
- self
214
- end
215
-
216
- # Writes input to target using 'puts'. Returns self.
217
- def writeln(input)
218
- target.puts input
219
- self
220
- end
221
-
222
- # Truncates the contents of target starting at the first match of pattern
223
- # and returns the resulting match data. If a block is given then rewrite
224
- # yields the match data to the block and returns the block result.
225
- #
226
- # ==== Notes
227
- #
228
- # Rewrites can be computationally expensive because they require the
229
- # current target to be flushed, rewound, and read in it's entirety. In
230
- # practice the performance of rewrite is almost never an issue because
231
- # recipe output is usually small in size.
232
- #
233
- # If performance becomes an issue, then wrap the rewritten bits in a
234
- # capture block to reassign the current target to a StringIO (which is
235
- # much faster to rewrite), and to limit the scope of the rewritten text.
236
- def rewrite(pattern)
237
- if match = pattern.match(result)
238
- start = match.begin(0)
239
- target.pos = start
240
- target.truncate start
241
- end
242
-
243
- block_given? ? yield(match) : match
244
- end
245
-
246
- # Strips whitespace from the end of target and returns the stripped
247
- # whitespace, or an empty string if no whitespace is available.
248
- def rstrip
249
- match = rewrite(/\s+\z/)
250
- match ? match[0] : ''
251
- end
252
-
253
- # Indents the output of the block. Indents may be nested. To prevent a
254
- # section from being indented, enclose it within outdent which resets
255
- # indentation to nothing for the duration of a block.
256
- #
257
- # Example:
258
- #
259
- # writeln 'a'
260
- # indent do
261
- # writeln 'b'
262
- # outdent do
263
- # writeln 'c'
264
- # indent do
265
- # writeln 'd'
266
- # end
267
- # writeln 'c'
268
- # end
269
- # writeln 'b'
270
- # end
271
- # writeln 'a'
272
- #
273
- # "\n" + result
274
- # # => %q{
275
- # # a
276
- # # b
277
- # # c
278
- # # d
279
- # # c
280
- # # b
281
- # # a
282
- # # }
283
- #
284
- def indent(indent=' ')
285
- @indents << @indents.last.to_s + indent
286
- str = capture_str { yield }
287
- @indents.pop
288
-
289
- unless str.empty?
290
- str.gsub!(/^/, indent)
291
-
292
- if @indents.empty?
293
- @outdents.each do |flag|
294
- str.gsub!(/#{flag}(\d+):(.*?)#{flag}/m) do
295
- $2.gsub!(/^.{#{$1.to_i}}/, '')
296
- end
297
- end
298
- @outdents.clear
299
- end
300
-
301
- writeln str
302
- end
303
-
304
- self
305
- end
306
-
307
- # Resets indentation to nothing for a section of text indented by indent.
308
- #
309
- # === Notes
310
- #
311
- # Outdent works by setting a text flag around the outdented section; the
312
- # flag and indentation is later stripped out using regexps. For that
313
- # reason, be sure flag is not something that will appear anywhere else in
314
- # the section.
315
- #
316
- # The default flag is like ':outdent_N:' where N is a big random number.
317
- def outdent(flag=nil)
318
- current_indent = @indents.last
319
-
320
- if current_indent.nil?
321
- yield
322
- else
323
- flag ||= ":outdent_#{rand(10000000)}:"
324
- @outdents << flag
325
-
326
- write "#{flag}#{current_indent.length}:#{rstrip}"
327
- @indents << ''
328
-
329
- yield
330
-
331
- @indents.pop
332
-
333
- write "#{flag}#{rstrip}"
334
- end
335
-
336
- self
337
- end
338
-
339
- # Sets chain? to true and calls the method (thereby allowing the method to
340
- # invoke chain-specific behavior). Chain is invoked via the chain_proxy
341
- # which is returned by helper methods.
342
- def chain(method_name, *args, &block)
343
- @chain = true
344
- send(method_name, *args, &block)
345
- end
346
-
347
- # Returns true if the current context was invoked through chain.
348
- def chain?
349
- @chain
350
- end
351
-
352
- # Sets chain to false and returns the proxy.
353
- def chain_proxy
354
- @chain = false
355
- _proxy_
356
- end
357
-
358
- # Captures a block of output and concats to the named callback.
359
- def callback(name)
360
- target = _package_.callbacks[name]
361
- capture_block(target) { yield }
362
- end
363
-
364
- # Writes the specified callback to the current target.
365
- def write_callback(name)
366
- write _package_.callbacks[name].string
367
- end
368
- end
369
- end
1
+ require 'erb'
2
+ require 'tilt'
3
+ require 'linecook/attributes'
4
+ require 'linecook/cookbook'
5
+ require 'linecook/package'
6
+ require 'linecook/utils'
7
+ require 'linecook/proxy'
8
+
9
+ module Linecook
10
+ # Recipe is the context in which recipes are evaluated (literally). Recipe
11
+ # uses compiled ERB snippets to build text using method calls. For example:
12
+ #
13
+ # module Helper
14
+ # # This is an ERB template compiled to write to a Recipe.
15
+ # #
16
+ # # compiler = ERB::Compiler.new('<>')
17
+ # # compiler.put_cmd = "write"
18
+ # # compiler.insert_cmd = "write"
19
+ # # compiler.compile("echo '<%= args.join(' ') %>'\n")
20
+ # #
21
+ # def echo(*args)
22
+ # write "echo '"; write(( args.join(' ') ).to_s); write "'\n"
23
+ # end
24
+ # end
25
+ #
26
+ # recipe = Recipe.new do
27
+ # _extend_ Helper
28
+ # echo 'a', 'b c'
29
+ # echo 'X Y'.downcase, :z
30
+ # end
31
+ #
32
+ # "\n" + recipe.to_s
33
+ # # => %{
34
+ # # echo 'a b c'
35
+ # # echo 'x y z'
36
+ # # }
37
+ #
38
+ class Recipe < BasicObject
39
+ # The recipe Proxy
40
+ attr_reader :_proxy_
41
+
42
+ # The recipe Package
43
+ attr_reader :_package_
44
+
45
+ attr_reader :_package_path_
46
+
47
+ attr_reader :_package_opts_
48
+
49
+ # The recipe Cookbook
50
+ attr_reader :_cookbook_
51
+
52
+ # The target recieving writes
53
+ attr_reader :target
54
+
55
+ def initialize(package = Package.new, cookbook = Cookbook.new, target = "")
56
+ @_proxy_ = Proxy.new(self)
57
+ @_package_ = package
58
+ @_cookbook_ = cookbook
59
+ @_package_path_ = nil
60
+ @_package_opts_ = nil
61
+ @target = target
62
+ @chain = false
63
+ @attributes = {}
64
+ @attrs = nil
65
+
66
+ if Kernel.block_given?
67
+ instance_eval(&Proc.new)
68
+ end
69
+ end
70
+
71
+ # Overridden to look up constants as normal.
72
+ def self.const_missing(name)
73
+ ::Object.const_get(name)
74
+ end
75
+
76
+ # Returns the singleton class for self. Used by clone to access modules
77
+ # included in self (ex via _extend_).
78
+ def _singleton_class_
79
+ class << self
80
+ SINGLETON_CLASS = self
81
+ def _singleton_class_
82
+ SINGLETON_CLASS
83
+ end
84
+ end
85
+
86
+ # this and future calls go to the _singleton_class_ as defined above.
87
+ _singleton_class_
88
+ end
89
+
90
+ # Returns the class for self.
91
+ def _class_
92
+ _singleton_class_.superclass
93
+ end
94
+
95
+ # Extends self with the module.
96
+ def _extend_(mod)
97
+ mod.__send__(:extend_object, self)
98
+ end
99
+
100
+ # Callback to initialize a clone of self. Passes forward all state,
101
+ # including local data and attributes.
102
+ def _initialize_clone_(orig)
103
+ @_proxy_ = Proxy.new(self)
104
+ @_package_ = orig._package_
105
+ @_cookbook_ = orig._cookbook_
106
+ @_package_path_ = orig._package_path_
107
+ @_package_opts_ = orig._package_path_
108
+ @target = orig.target
109
+ @chain = orig.chain?
110
+ @attributes = orig.attributes
111
+ @attrs = nil
112
+ end
113
+
114
+ # Returns a clone of self, kind of like Object#clone.
115
+ #
116
+ # Note that unlike Object.clone this currently does not carry forward
117
+ # tainted/frozen state, nor can it carry forward singleton methods.
118
+ # Modules and internal state only.
119
+ def _clone_
120
+ clone = _class_.allocate
121
+ clone._initialize_clone_(self)
122
+ _singleton_class_.included_modules.each {|mod| clone._extend_ mod }
123
+ clone
124
+ end
125
+
126
+ # Callback to initialize children created by _beget_. Sets a new target
127
+ # by calling dup.clear on the original target, and unchains. Note that
128
+ # the child shares the same attributes as the parent, and so can
129
+ # (un)intentionally cause changes in the parent.
130
+ def _initialize_child_(orig)
131
+ @target = orig.target.dup.clear
132
+ @_package_path_ = nil
133
+ @_package_opts_ = nil
134
+ unchain
135
+ end
136
+
137
+ # Returns a clone of self created by _clone_, but also calls
138
+ # _initialize_child_ on the clone.
139
+ def _beget_
140
+ clone = _clone_
141
+ clone._initialize_child_(self)
142
+ clone
143
+ end
144
+
145
+ # Returns a child of self with a new target. Writes str to the child, and
146
+ # evaluates the block in the context of the child, if given.
147
+ def _(str=nil, &block)
148
+ child = _beget_
149
+ child.write str if str
150
+ child.instance_eval(&block) if block
151
+ child
152
+ end
153
+
154
+ def register_as(path, options={})
155
+ @_package_path_ = path
156
+ @_package_opts_ = options
157
+ self
158
+ end
159
+
160
+ def register_to(package, path = _package_path_, options = _package_opts_)
161
+ package.add(path, options) {|io| io << to_s }
162
+ end
163
+
164
+ # Loads the specified attributes file and merges the results into attrs. A
165
+ # block may be given to specify attrs as well; it will be evaluated in the
166
+ # context of an Attributes instance.
167
+ #
168
+ # Returns a hash representing all attributes loaded thusfar (specifically
169
+ # attrs prior to merging in the package env). The attributes hash should
170
+ # be treated as if it were read-only.
171
+ def attributes(source_name=nil, &block)
172
+ if source_name || block
173
+ attributes = Attributes.new
174
+
175
+ if source_name
176
+ if source_path = _cookbook_.find(:attributes, source_name, attributes.load_attrs_extnames)
177
+ attributes.load_attrs(source_path)
178
+ end
179
+ end
180
+
181
+ if block
182
+ attributes.instance_eval(&block)
183
+ end
184
+
185
+ @attributes = Utils.deep_merge(@attributes, attributes.to_hash)
186
+ @attrs = nil
187
+ end
188
+
189
+ @attributes
190
+ end
191
+
192
+ # Returns the package env merged over any attrs specified by attributes.
193
+ #
194
+ # The attrs hash should be treated as if it were read-only because changes
195
+ # could alter the package env and thereby spill over into other recipes.
196
+ def attrs
197
+ @attrs ||= Utils.deep_merge(@attributes, _package_.env)
198
+ end
199
+
200
+ # Looks up and extends self with the specified helper(s).
201
+ def helpers(*helper_names)
202
+ helper_names.each do |helper|
203
+ unless helper.respond_to?(:extend_object)
204
+ helper_name = helper
205
+ module_name = Utils.camelize(helper_name)
206
+
207
+ helper = Utils.constantize(module_name) do
208
+ # Don't use Kernel because that may evade RubyGems
209
+ Utils.__send__(:require, Utils.underscore(helper_name))
210
+ Utils.constantize(module_name)
211
+ end
212
+ end
213
+ helper.__send__(:extend_object, self)
214
+ end
215
+ self
216
+ end
217
+
218
+ def register(package_path, options={})
219
+ source_path = _cookbook_.find(:files, options[:source] || package_path)
220
+ _package_.register(package_path, source_path, options)
221
+ end
222
+
223
+ # Captures writes during the block to a new target. Returns the target.
224
+ def capture(target = "")
225
+ current = @target
226
+ begin
227
+ @target = target
228
+ yield
229
+ ensure
230
+ @target = current
231
+ end
232
+ target
233
+ end
234
+
235
+ def capture_to(package_path, options={})
236
+ str = capture { yield }
237
+ _package_.add(package_path, options) {|io| io << str }
238
+ end
239
+
240
+ # Writes input to target using `<<`. Stringifies input using to_s. Returns
241
+ # self.
242
+ def write(input)
243
+ target << input.to_s
244
+ self
245
+ end
246
+
247
+ # Writes input to self, writes a newline, and returns last.
248
+ def writeln(input=nil)
249
+ write input
250
+ write "\n"
251
+ self
252
+ end
253
+
254
+ # Looks up a template in _cookbook_ and renders it.
255
+ def render(template_name, locals=attrs)
256
+ file = _cookbook_.find(:templates, template_name, ['.erb'])
257
+ Tilt.new(file).render(Object.new, locals)
258
+ end
259
+
260
+ # Causes chain? to return true. Returns self.
261
+ def chain
262
+ @chain = true
263
+ self
264
+ end
265
+
266
+ # Causes chain? to return false. Returns self.
267
+ def unchain
268
+ @chain = false
269
+ self
270
+ end
271
+
272
+ # Returns the proxy. Unchains first to ensure that if the proxy is not
273
+ # called, then the previous chain is stopped.
274
+ def chain_proxy
275
+ unchain
276
+ _proxy_
277
+ end
278
+
279
+ # Returns true as per chain/unchain.
280
+ def chain?
281
+ @chain
282
+ end
283
+
284
+ # Returns target.to_s.
285
+ def to_s
286
+ target.to_s
287
+ end
288
+ end
289
+ end