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