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,26 +1,25 @@
1
- require 'linecook/commands/command'
1
+ require 'linecook/command'
2
2
  require 'linecook/utils'
3
3
  require 'fileutils'
4
4
  require 'erb'
5
5
 
6
6
  module Linecook
7
7
  module Commands
8
-
9
- # :startdoc::desc generates a helper module
8
+
9
+ # :startdoc::desc compile helper modules
10
10
  #
11
- # Generates the specified helper module from a set of source files. Each
12
- # source file becomes a method in the module, named after the source file
13
- # itself.
11
+ # Compiles a helper module from a set of source files. Each source file
12
+ # becomes a method in the module, named after the source file itself.
14
13
  #
15
- # The helper module will be generated under the lib directory in a file
16
- # corresponding to const_name (which can also be a constant path). By
17
- # default, all files under the corresponding helpers directory will be
18
- # used as sources. For example these are equivalent and produce the
14
+ # The helper module will be generated under the output directory in a file
15
+ # corresponding to const_name (which can also be a constant path). Input
16
+ # directories may be specified to automatically search for source files
17
+ # based on constant path. These are all are equivalent and produce the
19
18
  # Const::Name module in 'lib/const/name.rb':
20
19
  #
21
- # % linecook helper Const::Name
22
- # % linecook helper const/name
23
- # % linecook helper const/name helpers/const/name/*
20
+ # $ linecook compile-helper Const::Name helpers/const/name/*
21
+ # $ linecook compile-helper const/name helpers/const/name/*
22
+ # $ linecook compile-helper const/name -i helpers
24
23
  #
25
24
  # == Source Files
26
25
  #
@@ -32,8 +31,7 @@ module Linecook
32
31
  # .erb file defines an ERB template (compiled to ruby code)
33
32
  #
34
33
  # Source files can specify documenation and a method signature using a
35
- # standard header separated from the body by a double-dash. For example
36
- # this:
34
+ # header separated from the body by a double-dash, like this:
37
35
  #
38
36
  # [echo.erb]
39
37
  # Echo arguments out to the target.
@@ -41,34 +39,37 @@ module Linecook
41
39
  # --
42
40
  # echo <%= args.join(' ') %>
43
41
  #
44
- # Is translated into something like:
42
+ # Which produces something like:
45
43
  #
46
44
  # # Echo arguments out to the target.
47
45
  # def echo(*args)
48
46
  # eval ERB.new("echo <%= args.join(' ') %>").src
49
47
  # end
50
48
  #
51
- # A second method is also generated to return the result without writing
52
- # it to the target. The latter method is prefixed by and underscore
53
- # like:
49
+ # A second 'capture' method is also generated to return the result without
50
+ # writing it to the target. The latter method is prefixed by an
51
+ # underscore like this:
54
52
  #
55
53
  # # Return the output of echo, without writing to the target
56
54
  # def _echo(*args)
57
55
  # ...
58
56
  # end
59
57
  #
60
- # Check and bang methods can be specified by adding -check and -bang to
61
- # the end of the file name. These extensions are stripped off like:
58
+ # Special characters can be added to a method name by using a -extension
59
+ # to the file name. For example 'file-check.erb' defines the 'file?'
60
+ # method. These extensions are supported:
62
61
  #
63
- # [file-check.erb] # => def file? ...
64
- # [make-bang.rb] # => def make! ...
62
+ # extension character
63
+ # -check ?
64
+ # -bang !
65
+ # -eq =
65
66
  #
66
- # Otherwise the basename of the source file must be a word; non-word
67
- # basenames raise an error.
67
+ # Otherwise the basename of the source file must be a valid method name;
68
+ # invalid names raise an error.
68
69
  #
69
70
  # == Section Files
70
71
  #
71
- # Special section files can be used to define non-standard code in the
72
+ # Section files are source files that can be used to insert code in the
72
73
  # following places:
73
74
  #
74
75
  # [:header]
@@ -82,112 +83,144 @@ module Linecook
82
83
  # end
83
84
  # [:footer]
84
85
  #
85
- # Section files are defined by prepending '-' to the file basename (like
86
- # path/to/-header.rb) and are not processed like other source files;
87
- # instead the contents are directly transcribed into the target file.
88
- class Helper < Command
89
- config :project_dir, '.', :short => :d # the project directory
90
- config :force, false, :short => :f, &c.flag # force creation
91
- config :quiet, false, &c.flag
92
-
86
+ # Section files are defined by prepending '_' to the file basename (like
87
+ # path/to/_header.rb) and are, like other source files, processed
88
+ # according to their extname:
89
+ #
90
+ # extname translation
91
+ # .rb file defines ruby (directly inserted)
92
+ # .rdoc file defines RDoc (lines commented out, then inserted)
93
+ #
94
+ # Note that section files prevent methods beginning with an underscore;
95
+ # this is intentional and prevents collisions with capture methods.
96
+ class CompileHelper < Command
97
+ config :output_dir, 'lib' # -o DIRECTORY : the output directory
98
+ config :search_dirs, [] # -i DIRECTORY : the input directory
99
+ config :force, false # -f, --force : force creation
100
+ config :quiet, false # -q, --quiet : print nothing
101
+
93
102
  include Utils
94
-
103
+
95
104
  def process(const_name, *sources)
96
105
  const_path = underscore(const_name)
97
- const_name = camelize(const_path)
98
-
106
+ const_name = camelize(const_name)
107
+
99
108
  unless const_name?(const_name)
100
- raise "invalid constant name: #{const_name.inspect}"
109
+ raise CommandError, "invalid constant name: #{const_name.inspect}"
101
110
  end
102
-
103
- sources = default_sources(const_path) if sources.empty?
104
- target = File.expand_path(File.join('lib', "#{const_path}.rb"), project_dir)
105
-
111
+
112
+ sources = sources | search_sources(const_path)
113
+ target = File.expand_path(File.join(output_dir, "#{const_path}.rb"))
114
+
106
115
  if sources.empty?
107
- raise CommandError, "no sources specified (and none found under 'helpers/#{const_path}')"
116
+ raise CommandError, "no sources specified"
108
117
  end
109
-
118
+
110
119
  if force || !FileUtils.uptodate?(target, sources)
111
120
  content = build(const_name, sources)
112
-
121
+
113
122
  target_dir = File.dirname(target)
114
123
  unless File.exists?(target_dir)
115
124
  FileUtils.mkdir_p(target_dir)
116
125
  end
117
-
126
+
118
127
  File.open(target, 'w') {|io| io << content }
119
128
  $stdout.puts target unless quiet
120
129
  end
121
-
130
+
122
131
  target
123
132
  end
124
-
125
- # Returns the default source files for a given constant path, which are
126
- # all files under the 'project_dir/helpers/const_path' folder.
127
- def default_sources(const_path)
128
- pattern = File.join(project_dir, 'helpers', const_path, '*')
129
- sources = Dir.glob(pattern)
133
+
134
+ # Returns source files for a given constant path, which are all files
135
+ # under the 'search_dir/const_path' folder.
136
+ def search_sources(const_path)
137
+ sources = []
138
+
139
+ search_dirs.each do |search_dir|
140
+ pattern = File.join(search_dir, const_path, '*')
141
+ sources.concat Dir.glob(pattern)
142
+ end
143
+
130
144
  sources.select {|path| File.file?(path) }
131
145
  end
132
-
146
+
133
147
  # returns true if const_name is a valid constant name.
134
148
  def const_name?(const_name) # :nodoc:
135
149
  const_name =~ /\A(?:::)?[A-Z]\w*(?:::[A-Z]\w*)*\z/
136
150
  end
137
-
151
+
138
152
  # helper to partition an array of source files into section and
139
153
  # defintion files
140
154
  def partition(sources) # :nodoc:
141
155
  sources.partition do |path|
142
156
  basename = File.basename(path)
143
157
  extname = File.extname(path)
144
- basename[0] == ?- && basename.chomp(extname) != '-'
158
+ basename[0] == ?_ && basename.chomp(extname) != '_'
145
159
  end
146
160
  end
147
-
161
+
148
162
  # helper to load each section path into a sections hash; removes the
149
163
  # leading - from the path basename to determine the section key.
150
164
  def load_sections(paths) # :nodoc:
151
165
  sections = {}
152
-
166
+
153
167
  paths.each do |path|
154
- basename = File.basename(path)
155
- extname = File.extname(path)
156
- key = basename[1, basename.length - extname.length - 1]
157
- sections[key] = File.read(path)
168
+ begin
169
+ basename = File.basename(path)
170
+ extname = File.extname(path)
171
+ key = basename[1, basename.length - extname.length - 1]
172
+ sections[key] = section_content(File.read(path), extname)
173
+ rescue CommandError
174
+ err = CommandError.new("invalid source file: #{path.inspect} (#{$!.message})")
175
+ err.set_backtrace($!.backtrace)
176
+ raise err
177
+ end
158
178
  end
159
-
179
+
160
180
  sections
161
181
  end
162
-
182
+
183
+ def section_content(content, extname) # :nodoc:
184
+ case extname
185
+ when '.rdoc'
186
+ content.gsub(/^/, '# ')
187
+
188
+ when '.rb'
189
+ content
190
+
191
+ else
192
+ raise CommandError.new("unsupported section format #{extname.inspect}")
193
+ end
194
+ end
195
+
163
196
  # helper to load and parse a definition file
164
197
  def load_definition(path) # :nodoc:
165
198
  extname = File.extname(path)
166
199
  name = File.basename(path).chomp(extname)
167
200
  desc, signature, body = parse_definition(File.read(path))
168
-
201
+
169
202
  [desc, parse_method_name(name), signature, method_body(body, extname)]
170
203
  rescue CommandError
171
- err = CommandError.new("#{$!.message} (#{path.inspect})")
204
+ err = CommandError.new("invalid source file: #{path.inspect} (#{$!.message})")
172
205
  err.set_backtrace($!.backtrace)
173
206
  raise err
174
207
  end
175
-
208
+
176
209
  # helper to reformat special basenames (in particular -check and -bang)
177
210
  # to their corresponding method_name
178
211
  def parse_definition(str) # :nodoc:
179
212
  head, body = str.split(/^--.*\n/, 2)
180
213
  head, body = '', head if body.nil?
181
-
214
+
182
215
  found_signature = false
183
216
  signature, desc = head.split("\n").partition do |line|
184
217
  found_signature = true if line =~ /^\s*\(.*?\)/
185
218
  found_signature
186
219
  end
187
-
220
+
188
221
  [desc.join("\n"), found_signature ? signature.join("\n") : '()', body.to_s]
189
222
  end
190
-
223
+
191
224
  # helper to reformat special basenames (in particular -check and -bang)
192
225
  # to their corresponding method_name
193
226
  def parse_method_name(basename) # :nodoc:
@@ -195,11 +228,11 @@ module Linecook
195
228
  when /-check\z/ then basename.sub(/-check$/, '?')
196
229
  when /-bang\z/ then basename.sub(/-bang$/, '!')
197
230
  when /-eq\z/ then basename.sub(/-eq$/, '=')
198
- when /\A\w+\z/ then basename
199
- else raise CommandError.new("invalid method name: #{basename.inspect}")
231
+ when /\A[A-Za-z]\w*\z/ then basename
232
+ else raise CommandError.new("invalid method name #{basename.inspect}")
200
233
  end
201
234
  end
202
-
235
+
203
236
  # helper to reformat a definition body according to a given extname. rb
204
237
  # content is rstripped to improve formatting. erb content is compiled
205
238
  # and the source is placed as a comment before it (to improve
@@ -212,54 +245,57 @@ module Linecook
212
245
  compiler.put_cmd = "write"
213
246
  compiler.insert_cmd = "write"
214
247
  code = [compiler.compile(body)].flatten.first
215
-
248
+
249
+ # remove encoding comment in 1.9 because it is not needed
250
+ code = code.sub(/\A#coding:.*?\n/, '')
251
+
216
252
  "#{source}\n#{code}".gsub(/^(\s*)/) do |m|
217
253
  indent = 2 + $1.length - ($1.length % 2)
218
254
  ' ' * indent
219
255
  end
220
-
256
+
221
257
  when '.rb'
222
258
  body.rstrip
223
-
259
+
224
260
  else
225
- raise CommandError.new("invalid definition format: #{extname.inspect}")
261
+ raise CommandError.new("unsupported format #{extname.inspect}")
226
262
  end
227
263
  end
228
-
264
+
229
265
  # helper to nest a module body within a const_name. documentation
230
266
  # can be provided for the innermost constant.
231
267
  def module_nest(const_name, body, inner_doc=nil) # :nodoc:
232
268
  body = body.strip.split("\n")
233
-
269
+
234
270
  const_name.split(/::/).reverse_each do |name|
235
- body.collect! {|line| " #{line}" }
236
-
271
+ body.collect! {|line| line.empty? ? line : " #{line}" }
272
+
237
273
  body.unshift "module #{name}"
238
274
  body.push "end"
239
-
275
+
240
276
  # prepend the inner doc to the innermost const
241
277
  if inner_doc
242
278
  body = inner_doc.strip.split("\n") + body
243
279
  inner_doc = nil
244
280
  end
245
281
  end
246
-
282
+
247
283
  body.join("\n")
248
284
  end
249
-
285
+
250
286
  # Returns the code for a const_name module as defined by the source
251
287
  # files.
252
288
  def build(const_name, sources)
253
289
  section_paths, definition_paths = partition(sources)
254
290
  sections = load_sections(section_paths)
255
291
  definitions = definition_paths.collect {|path| load_definition(path) }
256
-
292
+
257
293
  body = eval DEFINITION_TEMPLATE, binding, __FILE__, DEFINITION_TEMPLATE_LINE
258
294
  code = eval MODULE_TEMPLATE, binding, __FILE__, MODULE_TEMPLATE_LINE
259
-
295
+
260
296
  code
261
297
  end
262
-
298
+
263
299
  # :stopdoc:
264
300
  MODULE_TEMPLATE_LINE = __LINE__ + 2
265
301
  MODULE_TEMPLATE = ERB.new(<<-DOC, nil, '<>').src
@@ -282,14 +318,7 @@ def <%= method_name %><%= signature %>
282
318
 
283
319
  chain_proxy
284
320
  end
285
-
286
- def _<%= method_name %>(*args, &block) # :nodoc:
287
- str = capture_str { <%= method_name %>(*args, &block) }
288
- str.strip!
289
- str
290
- end
291
321
  <% end %>
292
-
293
322
  <%= sections['foot'] %>
294
323
  DOC
295
324
  # :startdoc:
@@ -1,48 +1,44 @@
1
- require 'linecook/commands/command'
2
- require 'linecook/commands/build'
1
+ require 'linecook/command'
2
+ require 'linecook/command_utils'
3
3
 
4
4
  module Linecook
5
5
  module Commands
6
-
6
+
7
7
  # :startdoc::desc run packages
8
+ #
9
+ # Runs packages on remote hosts by transfering a package directory using
10
+ # scp, and then executing one or more scripts via ssh. Run is configured
11
+ # using an ssh config file, which should contain all necessary ssh
12
+ # options.
13
+ #
14
+ # Packages are transfered to hosts based on the basename of the package,
15
+ # hence 'path/to/abox' will run on the 'abox' host as configured in the
16
+ # ssh config file.
17
+ #
8
18
  class Run < Command
19
+ include CommandUtils
20
+
9
21
  RUN_SCRIPT = File.expand_path('../../../../bin/linecook_run', __FILE__)
10
22
  SCP_SCRIPT = File.expand_path('../../../../bin/linecook_scp', __FILE__)
11
-
12
- config :project_dir, '.', :short => :d # the project directory
13
- config :remote_dir, 'linecook', :short => :D # the remote package dir
14
- config :ssh_config_file, 'config/ssh', :short => :F # the ssh config file
15
- config :quiet, false, :short => :q, &c.flag # silence output
16
- config :transfer, true, &c.switch # transfer package (or not)
17
- config :remote_scripts, ['run'],
18
- :short => :S,
19
- :long => :remote_script, &c.list # the remote script(s)
20
-
21
- def glob_package_dirs(package_names)
22
- if package_names.empty?
23
- pattern = File.expand_path('packages/*', project_dir)
24
- Dir.glob(pattern).select {|path| File.directory?(path) }
25
- else
26
- package_names.collect do |package_name|
27
- File.expand_path("packages/#{package_name}", project_dir)
28
- end
29
- end
23
+
24
+ config :remote_dir, 'linecook' # -D : the remote package dir
25
+ config :remote_scripts, ['run'] # -S : the remote script(s)
26
+ config :ssh_config_file, 'config/ssh' # -F : the ssh config file
27
+ config :xtrace, false # -x : xtrace local scripts
28
+ config :scp, true # transfer package or not
29
+
30
+ def full_path_to_remote_dir
31
+ (remote_dir[0] == ?/ ? remote_dir : "$(pwd)/#{remote_dir}").chomp('/')
30
32
  end
31
-
32
- def process(*package_names)
33
- package_dirs = glob_package_dirs(package_names)
34
-
35
- unless remote_dir[0] == ?/
36
- self.remote_dir = "$(pwd)/#{remote_dir}"
37
- self.remote_dir.chomp!('/')
38
- end
39
-
33
+
34
+ def process(*package_dirs)
40
35
  opts = {
41
- 'D' => remote_dir,
42
- 'F' => ssh_config_file
36
+ 'D' => full_path_to_remote_dir,
37
+ 'F' => ssh_config_file,
38
+ 'x' => xtrace
43
39
  }
44
-
45
- if transfer
40
+
41
+ if scp
46
42
  sh! "sh #{SCP_SCRIPT} #{format(opts)} #{package_dirs.join(' ')}"
47
43
  end
48
44
 
@@ -51,14 +47,22 @@ module Linecook
51
47
  sh! "sh #{RUN_SCRIPT} #{format(script_opts)} #{package_dirs.join(' ')}"
52
48
  end
53
49
  end
54
-
50
+
55
51
  def format(opts)
56
52
  options = []
57
-
53
+
58
54
  opts.each_pair do |key, value|
59
- options << "-#{key} '#{value}'"
55
+ next if value.to_s.strip.empty?
56
+
57
+ case value
58
+ when true
59
+ options << "-#{key}"
60
+ when false
61
+ else
62
+ options << "-#{key} '#{value}'"
63
+ end
60
64
  end
61
-
65
+
62
66
  options.sort.join(' ')
63
67
  end
64
68
  end