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.
- data/History +139 -0
- data/HowTo/Control Virtual Machines +106 -0
- data/HowTo/Generate Scripts +263 -0
- data/HowTo/Run Scripts +87 -0
- data/HowTo/Setup Virtual Machines +76 -0
- data/License.txt +1 -1
- data/README +78 -59
- data/bin/linecook +12 -5
- data/bin/linecook_run +45 -0
- data/bin/linecook_scp +50 -0
- data/lib/linecook.rb +1 -3
- data/lib/linecook/attributes.rb +49 -12
- data/lib/linecook/commands.rb +9 -4
- data/lib/linecook/commands/build.rb +69 -0
- data/lib/linecook/commands/command.rb +13 -3
- data/lib/linecook/commands/command_error.rb +6 -0
- data/lib/linecook/commands/env.rb +74 -8
- data/lib/linecook/commands/helper.rb +271 -24
- data/lib/linecook/commands/init.rb +10 -6
- data/lib/linecook/commands/package.rb +36 -18
- data/lib/linecook/commands/run.rb +66 -0
- data/lib/linecook/commands/snapshot.rb +114 -0
- data/lib/linecook/commands/ssh.rb +39 -0
- data/lib/linecook/commands/start.rb +34 -0
- data/lib/linecook/commands/state.rb +32 -0
- data/lib/linecook/commands/stop.rb +22 -0
- data/lib/linecook/commands/vbox_command.rb +130 -0
- data/lib/linecook/cookbook.rb +112 -55
- data/lib/linecook/package.rb +293 -109
- data/lib/linecook/proxy.rb +19 -0
- data/lib/linecook/recipe.rb +321 -62
- data/lib/linecook/template.rb +7 -101
- data/lib/linecook/test.rb +196 -141
- data/lib/linecook/test/command_parser.rb +75 -0
- data/lib/linecook/test/file_test.rb +153 -35
- data/lib/linecook/test/shell_test.rb +176 -0
- data/lib/linecook/utils.rb +25 -7
- data/lib/linecook/version.rb +4 -4
- data/templates/Rakefile +44 -47
- data/templates/_gitignore +1 -1
- data/templates/attributes/project_name.rb +4 -4
- data/templates/config/ssh +15 -0
- data/templates/files/help.txt +1 -0
- data/templates/helpers/project_name/assert_content_equal.erb +15 -0
- data/templates/helpers/project_name/create_dir.erb +9 -0
- data/templates/helpers/project_name/create_file.erb +8 -0
- data/templates/helpers/project_name/install_file.erb +8 -0
- data/templates/packages/abox.yml +4 -0
- data/templates/recipes/abox.rb +22 -0
- data/templates/recipes/abox_test.rb +14 -0
- data/templates/templates/todo.txt.erb +3 -0
- data/templates/test/project_name_test.rb +19 -0
- data/templates/test/test_helper.rb +14 -0
- metadata +43 -41
- data/cookbook +0 -0
- data/lib/linecook/commands/helpers.rb +0 -28
- data/lib/linecook/commands/vbox.rb +0 -85
- data/lib/linecook/helper.rb +0 -117
- data/lib/linecook/shell.rb +0 -11
- data/lib/linecook/shell/posix.rb +0 -145
- data/lib/linecook/shell/test.rb +0 -254
- data/lib/linecook/shell/unix.rb +0 -117
- data/lib/linecook/shell/utils.rb +0 -138
- data/templates/README +0 -90
- data/templates/files/file.txt +0 -1
- data/templates/helpers/project_name/echo.erb +0 -5
- data/templates/recipes/project_name.rb +0 -20
- data/templates/scripts/project_name.yml +0 -7
- data/templates/templates/template.txt.erb +0 -3
- data/templates/vbox/setup/virtual_box +0 -86
- data/templates/vbox/ssh/id_rsa +0 -27
- data/templates/vbox/ssh/id_rsa.pub +0 -1
data/lib/linecook.rb
CHANGED
data/lib/linecook/attributes.rb
CHANGED
@@ -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
|
-
|
6
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
14
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
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
|
data/lib/linecook/commands.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
|
-
require 'linecook/commands/
|
1
|
+
require 'linecook/commands/build'
|
2
|
+
require 'linecook/commands/env'
|
2
3
|
require 'linecook/commands/helper'
|
3
|
-
require 'linecook/commands/
|
4
|
+
require 'linecook/commands/init'
|
4
5
|
require 'linecook/commands/package'
|
5
|
-
require 'linecook/commands/
|
6
|
-
require 'linecook/commands/
|
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
|
-
|
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
|
-
|
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,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
|
8
|
+
# :startdoc::desc prints a package env
|
8
9
|
#
|
9
|
-
#
|
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 :
|
13
|
-
config :
|
17
|
+
config :project_dir, '.', :short => :d # the project directory
|
18
|
+
config :package_file, nil, :short => :p # the package file
|
14
19
|
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
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 :
|
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(
|
17
|
-
|
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
|
-
|
23
|
-
|
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
|
107
|
+
raise CommandError, "no sources specified (and none found under 'helpers/#{const_path}')"
|
27
108
|
end
|
28
109
|
|
29
|
-
if
|
30
|
-
|
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
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
159
|
+
|
160
|
+
sections
|
44
161
|
end
|
45
162
|
|
46
|
-
|
47
|
-
|
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
|