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.rb CHANGED
@@ -1,6 +1,4 @@
1
- require 'linecook/helper'
2
- require 'linecook/script'
3
- require 'linecook/cookbook'
1
+ require 'linecook/package'
4
2
 
5
3
  module Linecook
6
4
  end
@@ -1,22 +1,59 @@
1
- require 'linecook/utils'
2
-
3
1
  module Linecook
2
+
3
+ # Attributes provides a context for specifying default attributes. For
4
+ # example:
5
+ #
6
+ # attributes = Attributes.new
7
+ # attributes.instance_eval %{
8
+ # attrs['a'] = 'A'
9
+ # attrs['b']['c'] = 'C'
10
+ # }
11
+ #
12
+ # attributes.to_hash
13
+ # # => {'a' => 'A', 'b' => {'c' => 'C'}}
14
+ #
15
+ # Note that attrs is an auto-filling nested hash, making it easy to set
16
+ # nested attributes, but it is not indifferent, meaning you do need to
17
+ # differentiate between symbols and strings. Normally strings are
18
+ # preferred.
4
19
  class Attributes
5
- attr_reader :attrs
6
- attr_reader :context
20
+ # A proc used to create nest_hash hashes
21
+ NEST_HASH_PROC = Proc.new do |hash, key|
22
+ hash[key] = Hash.new(&NEST_HASH_PROC)
23
+ end
7
24
 
8
- def initialize(context={})
9
- @context = context
10
- reset(true)
25
+ class << self
26
+ # Returns an auto-filling nested hash.
27
+ def nest_hash
28
+ Hash.new(&NEST_HASH_PROC)
29
+ end
30
+
31
+ # Recursively disables automatic nesting of nest_hash hashes.
32
+ def disable_nest_hash(hash)
33
+ if hash.default_proc == NEST_HASH_PROC
34
+ hash.default = nil
35
+ end
36
+
37
+ hash.each_pair do |key, value|
38
+ if value.kind_of?(Hash)
39
+ disable_nest_hash(value)
40
+ end
41
+ end
42
+
43
+ hash
44
+ end
11
45
  end
12
46
 
13
- def current
14
- @current ||= Utils.serial_merge(attrs, context)
47
+ # An auto-filling nested hash
48
+ attr_reader :attrs
49
+
50
+ def initialize
51
+ @attrs = Attributes.nest_hash
15
52
  end
16
53
 
17
- def reset(full=true)
18
- @attrs = Utils.nest_hash if full
19
- @current = nil
54
+ # Disables automatic nesting and returns attrs.
55
+ def to_hash
56
+ Attributes.disable_nest_hash(attrs)
20
57
  end
21
58
  end
22
59
  end
@@ -1,6 +1,11 @@
1
- require 'linecook/commands/init'
1
+ require 'linecook/commands/build'
2
+ require 'linecook/commands/env'
2
3
  require 'linecook/commands/helper'
3
- require 'linecook/commands/helpers'
4
+ require 'linecook/commands/init'
4
5
  require 'linecook/commands/package'
5
- require 'linecook/commands/env'
6
- require 'linecook/commands/vbox'
6
+ require 'linecook/commands/snapshot'
7
+ require 'linecook/commands/ssh'
8
+ require 'linecook/commands/start'
9
+ require 'linecook/commands/state'
10
+ require 'linecook/commands/stop'
11
+ require 'linecook/commands/run'
@@ -0,0 +1,69 @@
1
+ require 'linecook/commands/helper'
2
+ require 'linecook/commands/package'
3
+
4
+ module Linecook
5
+ module Commands
6
+
7
+ # :startdoc::desc build a project
8
+ #
9
+ # Builds some or all packages and helpers in a project, as needed.
10
+ #
11
+ class Build < Command
12
+ config :project_dir, '.', :short => :d # the project directory
13
+ config :force, false, :short => :f, &c.flag # force creation
14
+ config :quiet, false, &c.flag # silence output
15
+
16
+ def glob_helpers(project_dir)
17
+ helpers_dir = File.expand_path('helpers', project_dir)
18
+ sources = {}
19
+ helpers = []
20
+
21
+ Dir.glob("#{helpers_dir}/*/**/*").each do |source|
22
+ next if File.directory?(source)
23
+ (sources[File.dirname(source)] ||= []) << source
24
+ end
25
+
26
+ sources.each_pair do |dir, sources|
27
+ name = dir[(helpers_dir.length + 1)..-1]
28
+ helpers << [name, sources]
29
+ end
30
+
31
+ helpers.sort_by {|name, sources| name }
32
+ end
33
+
34
+ def glob_package_files(package_names)
35
+ if package_names.empty?
36
+ pattern = File.expand_path('packages/*.yml', project_dir)
37
+ Dir.glob(pattern).select {|path| File.file?(path) }
38
+ else
39
+ package_names.collect do |package_name|
40
+ File.expand_path("packages/#{package_name}.yml", project_dir)
41
+ end
42
+ end
43
+ end
44
+
45
+ def process(*package_names)
46
+ helper = Helper.new(
47
+ :project_dir => project_dir,
48
+ :force => force,
49
+ :quiet => true
50
+ )
51
+
52
+ helpers = glob_helpers(project_dir)
53
+ helpers.each do |(name, sources)|
54
+ helper.process(name, *sources)
55
+ end
56
+
57
+ package = Package.new(
58
+ :project_dir => project_dir,
59
+ :force => force,
60
+ :quiet => quiet
61
+ )
62
+
63
+ glob_package_files(package_names).collect do |package_file|
64
+ package.process(package_file)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -24,16 +24,26 @@ module Linecook
24
24
  lazy_attr :args, :process
25
25
  lazy_register :process, Lazydoc::Arguments
26
26
 
27
- def initialize(config)
27
+ attr_accessor :quiet
28
+
29
+ def initialize(config={})
30
+ @quiet = true
28
31
  initialize_config(config)
29
32
  end
30
33
 
31
34
  def log(action, msg)
32
- puts(" %s %s" % [action, msg])
35
+ $stderr.puts(" %s %s" % [action, msg])
33
36
  end
34
37
 
35
38
  def sh(cmd)
36
- system cmd
39
+ puts "% #{cmd}" unless quiet
40
+ system(cmd)
41
+ end
42
+
43
+ def sh!(cmd)
44
+ unless sh(cmd)
45
+ raise CommandError.new("", $?.exitstatus)
46
+ end
37
47
  end
38
48
 
39
49
  def call(argv)
@@ -1,6 +1,12 @@
1
1
  module Linecook
2
2
  module Commands
3
3
  class CommandError < RuntimeError
4
+ attr_reader :exitstatus
5
+
6
+ def initialize(msg, exitstatus=1)
7
+ @exitstatus = exitstatus
8
+ super(msg)
9
+ end
4
10
  end
5
11
  end
6
12
  end
@@ -1,22 +1,88 @@
1
1
  require 'linecook/commands/command'
2
2
  require 'linecook/cookbook'
3
+ require 'yaml'
3
4
 
4
5
  module Linecook
5
6
  module Commands
6
7
 
7
- # ::desc prints the cookbook env
8
+ # :startdoc::desc prints a package env
8
9
  #
9
- # Print the cookbook env.
10
+ # Prints the env for the current project directory. Specifically the
11
+ # cookbook file is loaded and used to determine all resources that are
12
+ # current available. The full build env for a package can be viewed by
13
+ # specifying the package file as an option.
10
14
  #
15
+ # A specific env value can be printed by specifying the key path to it.
11
16
  class Env < Command
12
- config :cookbook_dir, '.', :short => :d # the cookbook directory
13
- config :path, nil # package path
17
+ config :project_dir, '.', :short => :d # the project directory
18
+ config :package_file, nil, :short => :p # the package file
14
19
 
15
- def process(*keys)
16
- current = Linecook::Cookbook.init(cookbook_dir).env(path)
17
- keys.each {|key| current = current[key] if current }
20
+ # :stopdoc:
21
+ # Evaluate to replace the to_yaml function on Hash so that it will
22
+ # serialize keys in order. Evaluate the OFF code to turn this hack off
23
+ # (and thereby ease up on the code pollution)
24
+ #
25
+ # Modified from: http://snippets.dzone.com/posts/show/5811 Original
26
+ # func: /usr/lib/ruby/1.8/yaml/rubytypes.rb
27
+ ORIGINAL_TO_YAML = 'linecook_original_to_yaml'
28
+ SORTED_HASH_ON_LINE = __LINE__ + 1
29
+ SORTED_HASH_ON = %{
30
+ class Hash
31
+ unless instance_methods.include?('#{ORIGINAL_TO_YAML}')
32
+ alias #{ORIGINAL_TO_YAML} to_yaml
33
+ undef_method :to_yaml
34
+ def to_yaml( opts = {} )
35
+ YAML::quick_emit( object_id, opts ) do |out|
36
+ out.map( taguri, to_yaml_style ) do |map|
37
+ keys.sort_by do |k|
38
+ k.to_s
39
+ end.each do |k|
40
+ map.add( k, fetch(k) )
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end}
47
+
48
+ SORTED_HASH_OFF_LINE = __LINE__ + 1
49
+ SORTED_HASH_OFF = %{
50
+ class Hash
51
+ if instance_methods.include?('#{ORIGINAL_TO_YAML}')
52
+ undef_method :to_yaml
53
+ alias to_yaml #{ORIGINAL_TO_YAML}
54
+ undef_method :#{ORIGINAL_TO_YAML}
55
+ end
56
+ end}
57
+ # :startdoc:
58
+
59
+ def select(current, *keys)
60
+ keys.each do |key|
61
+ unless current.kind_of?(Hash)
62
+ return nil
63
+ end
64
+
65
+ current = current[key]
66
+ end
18
67
 
19
- YAML.dump(current, $stdout)
68
+ current
69
+ end
70
+
71
+ # Serializes the env to the target as YAML. Ensures hashes are
72
+ # serialized with their keys sorted by their to_s value.
73
+ def serialize(env, target="")
74
+ begin
75
+ eval SORTED_HASH_ON, TOPLEVEL_BINDING, __FILE__, SORTED_HASH_ON_LINE
76
+ YAML.dump(env, target)
77
+ ensure
78
+ eval SORTED_HASH_OFF, TOPLEVEL_BINDING, __FILE__, SORTED_HASH_OFF_LINE
79
+ end
80
+ end
81
+
82
+ def process(*keys)
83
+ package = Linecook::Package.init(package_file, project_dir)
84
+ env = select(package.env, *keys)
85
+ serialize(env, $stdout)
20
86
  end
21
87
  end
22
88
  end
@@ -1,51 +1,298 @@
1
1
  require 'linecook/commands/command'
2
- require 'linecook/helper'
3
2
  require 'linecook/utils'
3
+ require 'fileutils'
4
+ require 'erb'
4
5
 
5
6
  module Linecook
6
7
  module Commands
7
8
 
8
- # ::desc generates a helper
9
+ # :startdoc::desc generates a helper module
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.
14
+ #
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
19
+ # Const::Name module in 'lib/const/name.rb':
20
+ #
21
+ # % linecook helper Const::Name
22
+ # % linecook helper const/name
23
+ # % linecook helper const/name helpers/const/name/*
24
+ #
25
+ # == Source Files
26
+ #
27
+ # The contents of the source file are translated into code according to
28
+ # the source file extname.
29
+ #
30
+ # extname translation
31
+ # .rb file defines method body
32
+ # .erb file defines an ERB template (compiled to ruby code)
33
+ #
34
+ # 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:
37
+ #
38
+ # [echo.erb]
39
+ # Echo arguments out to the target.
40
+ # (*args)
41
+ # --
42
+ # echo <%= args.join(' ') %>
43
+ #
44
+ # Is translated into something like:
45
+ #
46
+ # # Echo arguments out to the target.
47
+ # def echo(*args)
48
+ # eval ERB.new("echo <%= args.join(' ') %>").src
49
+ # end
50
+ #
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:
54
+ #
55
+ # # Return the output of echo, without writing to the target
56
+ # def _echo(*args)
57
+ # ...
58
+ # end
59
+ #
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:
62
+ #
63
+ # [file-check.erb] # => def file? ...
64
+ # [make-bang.rb] # => def make! ...
65
+ #
66
+ # Otherwise the basename of the source file must be a word; non-word
67
+ # basenames raise an error.
68
+ #
69
+ # == Section Files
70
+ #
71
+ # Special section files can be used to define non-standard code in the
72
+ # following places:
73
+ #
74
+ # [:header]
75
+ # module Const
76
+ # [:doc]
77
+ # module Name
78
+ # [:head]
79
+ # ...
80
+ # [:foot]
81
+ # end
82
+ # end
83
+ # [:footer]
84
+ #
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.
9
88
  class Helper < Command
10
- config :cookbook_dir, '.', :short => :d # the cookbook directory
11
- config :namespace, 'linebook', :short => :n # the helper namespace
89
+ config :project_dir, '.', :short => :d # the project directory
12
90
  config :force, false, :short => :f, &c.flag # force creation
91
+ config :quiet, false, &c.flag
13
92
 
14
93
  include Utils
15
94
 
16
- def process(name, *sources)
17
- name = underscore(name)
18
-
19
- const_path = namespace ? File.join(namespace, name) : name
95
+ def process(const_name, *sources)
96
+ const_path = underscore(const_name)
20
97
  const_name = camelize(const_path)
21
98
 
22
- sources = default_sources(name) if sources.empty?
23
- target = File.expand_path(File.join('lib', "#{const_path}.rb"), cookbook_dir)
99
+ unless const_name?(const_name)
100
+ raise "invalid constant name: #{const_name.inspect}"
101
+ end
102
+
103
+ sources = default_sources(const_path) if sources.empty?
104
+ target = File.expand_path(File.join('lib', "#{const_path}.rb"), project_dir)
24
105
 
25
106
  if sources.empty?
26
- raise CommandError, "no sources specified (and none could be found)"
107
+ raise CommandError, "no sources specified (and none found under 'helpers/#{const_path}')"
27
108
  end
28
109
 
29
- if File.exists?(target) && !force
30
- raise CommandError, "already exists: #{target}"
110
+ if force || !FileUtils.uptodate?(target, sources)
111
+ content = build(const_name, sources)
112
+
113
+ target_dir = File.dirname(target)
114
+ unless File.exists?(target_dir)
115
+ FileUtils.mkdir_p(target_dir)
116
+ end
117
+
118
+ File.open(target, 'w') {|io| io << content }
119
+ $stdout.puts target unless quiet
31
120
  end
32
121
 
33
- log :create, const_name
122
+ target
123
+ 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)
130
+ sources.select {|path| File.file?(path) }
131
+ end
132
+
133
+ # returns true if const_name is a valid constant name.
134
+ def const_name?(const_name) # :nodoc:
135
+ const_name =~ /\A(?:::)?[A-Z]\w*(?:::[A-Z]\w*)*\z/
136
+ end
137
+
138
+ # helper to partition an array of source files into section and
139
+ # defintion files
140
+ def partition(sources) # :nodoc:
141
+ sources.partition do |path|
142
+ basename = File.basename(path)
143
+ extname = File.extname(path)
144
+ basename[0] == ?- && basename.chomp(extname) != '-'
145
+ end
146
+ end
147
+
148
+ # helper to load each section path into a sections hash; removes the
149
+ # leading - from the path basename to determine the section key.
150
+ def load_sections(paths) # :nodoc:
151
+ sections = {}
34
152
 
35
- helper = Linecook::Helper.new(const_name, sources)
36
- content = helper.build
37
-
38
- target_dir = File.dirname(target)
39
- unless File.exists?(target_dir)
40
- FileUtils.mkdir_p(target_dir)
153
+ 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)
41
158
  end
42
-
43
- File.open(target, 'w') {|io| io << content }
159
+
160
+ sections
44
161
  end
45
162
 
46
- def default_sources(name)
47
- Dir.glob File.join(cookbook_dir, 'helpers', name, '**/*')
163
+ # helper to load and parse a definition file
164
+ def load_definition(path) # :nodoc:
165
+ extname = File.extname(path)
166
+ name = File.basename(path).chomp(extname)
167
+ desc, signature, body = parse_definition(File.read(path))
168
+
169
+ [desc, parse_method_name(name), signature, method_body(body, extname)]
170
+ rescue CommandError
171
+ err = CommandError.new("#{$!.message} (#{path.inspect})")
172
+ err.set_backtrace($!.backtrace)
173
+ raise err
48
174
  end
175
+
176
+ # helper to reformat special basenames (in particular -check and -bang)
177
+ # to their corresponding method_name
178
+ def parse_definition(str) # :nodoc:
179
+ head, body = str.split(/^--.*\n/, 2)
180
+ head, body = '', head if body.nil?
181
+
182
+ found_signature = false
183
+ signature, desc = head.split("\n").partition do |line|
184
+ found_signature = true if line =~ /^\s*\(.*?\)/
185
+ found_signature
186
+ end
187
+
188
+ [desc.join("\n"), found_signature ? signature.join("\n") : '()', body.to_s]
189
+ end
190
+
191
+ # helper to reformat special basenames (in particular -check and -bang)
192
+ # to their corresponding method_name
193
+ def parse_method_name(basename) # :nodoc:
194
+ case basename
195
+ when /-check\z/ then basename.sub(/-check$/, '?')
196
+ when /-bang\z/ then basename.sub(/-bang$/, '!')
197
+ when /-eq\z/ then basename.sub(/-eq$/, '=')
198
+ when /\A\w+\z/ then basename
199
+ else raise CommandError.new("invalid method name: #{basename.inspect}")
200
+ end
201
+ end
202
+
203
+ # helper to reformat a definition body according to a given extname. rb
204
+ # content is rstripped to improve formatting. erb content is compiled
205
+ # and the source is placed as a comment before it (to improve
206
+ # debugability).
207
+ def method_body(body, extname) # :nodoc:
208
+ case extname
209
+ when '.erb'
210
+ source = "# #{body.gsub(/\n/, "\n# ")}"
211
+ compiler = ERB::Compiler.new('<>')
212
+ compiler.put_cmd = "write"
213
+ compiler.insert_cmd = "write"
214
+ code = compiler.compile(body)
215
+
216
+ "#{source}\n#{code}".gsub(/^(\s*)/) do |m|
217
+ indent = 2 + $1.length - ($1.length % 2)
218
+ ' ' * indent
219
+ end
220
+
221
+ when '.rb'
222
+ body.rstrip
223
+
224
+ else
225
+ raise CommandError.new("invalid definition format: #{extname.inspect}")
226
+ end
227
+ end
228
+
229
+ # helper to nest a module body within a const_name. documentation
230
+ # can be provided for the innermost constant.
231
+ def module_nest(const_name, body, inner_doc=nil) # :nodoc:
232
+ body = body.strip.split("\n")
233
+
234
+ const_name.split(/::/).reverse_each do |name|
235
+ body.collect! {|line| " #{line}" }
236
+
237
+ body.unshift "module #{name}"
238
+ body.push "end"
239
+
240
+ # prepend the inner doc to the innermost const
241
+ if inner_doc
242
+ body = inner_doc.strip.split("\n") + body
243
+ inner_doc = nil
244
+ end
245
+ end
246
+
247
+ body.join("\n")
248
+ end
249
+
250
+ # Returns the code for a const_name module as defined by the source
251
+ # files.
252
+ def build(const_name, sources)
253
+ section_paths, definition_paths = partition(sources)
254
+ sections = load_sections(section_paths)
255
+ definitions = definition_paths.collect {|path| load_definition(path) }
256
+
257
+ body = eval DEFINITION_TEMPLATE, binding, __FILE__, DEFINITION_TEMPLATE_LINE
258
+ code = eval MODULE_TEMPLATE, binding, __FILE__, MODULE_TEMPLATE_LINE
259
+
260
+ code
261
+ end
262
+
263
+ # :stopdoc:
264
+ MODULE_TEMPLATE_LINE = __LINE__ + 2
265
+ MODULE_TEMPLATE = ERB.new(<<-DOC, nil, '<>').src
266
+ # Generated by Linecook
267
+ <%= sections['header'] %>
268
+
269
+ <%= module_nest(const_name, body, sections['doc']) %>
270
+
271
+ <%= sections['footer'] %>
272
+ DOC
273
+
274
+ DEFINITION_TEMPLATE_LINE = __LINE__ + 2
275
+ DEFINITION_TEMPLATE = ERB.new(<<-DOC, nil, '<>').src
276
+ <%= sections['head'] %>
277
+ <% definitions.each do |desc, method_name, signature, body| %>
278
+ <% desc.split("\n").each do |line| %>
279
+ # <%= line %><% end %>
280
+ def <%= method_name %><%= signature %>
281
+ <%= body %>
282
+
283
+ chain_proxy
284
+ 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
+ <% end %>
292
+
293
+ <%= sections['foot'] %>
294
+ DOC
295
+ # :startdoc:
49
296
  end
50
297
  end
51
298
  end