linecook 1.2.1 → 2.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 (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