bee 0.1.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/README +4 -0
- data/bin/bee +6 -0
- data/bin/beedoc +6 -0
- data/doc/html/documentation.html +444 -0
- data/doc/html/quickstart.html +108 -0
- data/doc/png/style.png +0 -0
- data/doc/yml/#releases.yml# +36 -0
- data/doc/yml/menu.yml +49 -0
- data/doc/yml/releases.yml +36 -0
- data/doc/yml/todo.yml +17 -0
- data/lib/bee.rb +916 -0
- data/lib/beedoc.rb +464 -0
- data/test/tc_bee_consoleformatter.rb +46 -0
- data/test/ts_bee.rb +27 -0
- metadata +71 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
<!--
|
2
|
+
Copyright 2006 Michel Casabianca <casa@sweetohm.net>
|
3
|
+
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
you may not use this file except in compliance with the License.
|
6
|
+
You may obtain a copy of the License at
|
7
|
+
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
See the License for the specific language governing permissions and
|
14
|
+
limitations under the License.
|
15
|
+
-->
|
16
|
+
|
17
|
+
<h2>Scripts</h2>
|
18
|
+
|
19
|
+
<p>Let's start with traditional <i>Hello World!</i> example:</p>
|
20
|
+
|
21
|
+
<pre class="code"><code>- target: hello
|
22
|
+
script:
|
23
|
+
- rb: "name = ENV['USER'].capitalize"
|
24
|
+
- "echo \"Hello #{name}!\""</code></pre>
|
25
|
+
|
26
|
+
<p>A target script can be made of shell and/or Ruby scripts. Variables
|
27
|
+
defined in Ruby scripts can be accessed in shell using Ruby syntax
|
28
|
+
<code>#{variable}</code>. Each shell script is executed in its own
|
29
|
+
context while Ruby variables all live in a global one that persist
|
30
|
+
in the whole build. This script will output:</p>
|
31
|
+
|
32
|
+
<pre class="code"><code>----------------------------------------------------------------------- hello --
|
33
|
+
Hello Casa!
|
34
|
+
OK</code></pre>
|
35
|
+
|
36
|
+
<h2>Targets</h2>
|
37
|
+
|
38
|
+
<p>You can set default target and dependencies as follows:</p>
|
39
|
+
|
40
|
+
<pre class="code"><code>- build: hello
|
41
|
+
default: hello
|
42
|
+
|
43
|
+
- target: name
|
44
|
+
script:
|
45
|
+
- rb: "name = ENV['USER'].capitalize"
|
46
|
+
|
47
|
+
- target: hello
|
48
|
+
depends: name
|
49
|
+
script:
|
50
|
+
- "echo \"Hello #{name}!\""
|
51
|
+
</code></pre>
|
52
|
+
|
53
|
+
<p>When running without indicating any target, bee will run target
|
54
|
+
<i>hello</i> (default one, as stated in <code>default</code> key).
|
55
|
+
Nevertheless, this target depends on <i>name</i>, thus bee will first
|
56
|
+
run target <i>name</i>, then <i>hello</i>. This will write on console:</p>
|
57
|
+
|
58
|
+
<pre class="code"><code>------------------------------------------------------------------------ name --
|
59
|
+
----------------------------------------------------------------------- hello --
|
60
|
+
Hello Casa!
|
61
|
+
OK</code></pre>
|
62
|
+
|
63
|
+
<h2>Properties</h2>
|
64
|
+
|
65
|
+
<p>You can define build properies as follows:</p>
|
66
|
+
|
67
|
+
<pre class="code"><code>- properties:
|
68
|
+
- name: "casa"
|
69
|
+
- capitalized: "#{name.capitalize}"
|
70
|
+
|
71
|
+
- target: hello
|
72
|
+
script:
|
73
|
+
- "echo \"Hello #{capitalized}!\""</code></pre>
|
74
|
+
|
75
|
+
<p>First, we set property <i>name</i> to <i>casa</i>, a simple string.
|
76
|
+
Then, we set <i>capitalized</i> to <i>Casa</i>, using a Ruby expression.
|
77
|
+
Properties can use values defined in other properties. Furthermore, we
|
78
|
+
can use these properties in shell or Ruby scripts.</p>
|
79
|
+
|
80
|
+
<h2>Context</h2>
|
81
|
+
|
82
|
+
<p>Ruby scripts run in a global context that persist for the whole build.
|
83
|
+
This context is similar to the place you write your code in IRB. In
|
84
|
+
this context, you can define variables, functions or classes. You can
|
85
|
+
define your own context in a file loaded at startup. This file is the
|
86
|
+
place to define utility functions you would use in your embedded Ruby
|
87
|
+
scripts.</p>
|
88
|
+
|
89
|
+
<p>For instance, loading script <i>hello.rb</i>:</p>
|
90
|
+
|
91
|
+
<pre class="code"><code>def say_hello(who)
|
92
|
+
puts "Hello #{who}!"
|
93
|
+
end</code></pre>
|
94
|
+
|
95
|
+
<p>In following build file using <code>context</code> as follows:</p>
|
96
|
+
|
97
|
+
<pre class="code"><code>- build: hello
|
98
|
+
context: hello.rb
|
99
|
+
|
100
|
+
- properties:
|
101
|
+
- me: "Casa"
|
102
|
+
|
103
|
+
- target: hello
|
104
|
+
script:
|
105
|
+
- rb: "say_hello(me)"</code></pre>
|
106
|
+
|
107
|
+
<p>After this quick introduction, you should know enough to start writing
|
108
|
+
your first build file. Enjoy!</p>
|
data/doc/png/style.png
ADDED
Binary file
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Copyright 2006 Michel Casabianca <casa@sweetohm.net>
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
- description: >
|
16
|
+
Here is the list of Bee release with new features and links to download.
|
17
|
+
download: http://rubyforge.org/frs/?group_id=2479
|
18
|
+
|
19
|
+
- release: alpha
|
20
|
+
description: A simple night hack.
|
21
|
+
|
22
|
+
- release: beta-1
|
23
|
+
description: First public release.
|
24
|
+
new:
|
25
|
+
- Run Ruby scripts.
|
26
|
+
- Load context on startup.
|
27
|
+
- Added checks on build files syntax.
|
28
|
+
- Documentation system.
|
29
|
+
|
30
|
+
- release: 0.1.0
|
31
|
+
description: First Gem package.
|
32
|
+
new:
|
33
|
+
- Distribution as a Gem package.
|
34
|
+
- Template generation (-t option).
|
35
|
+
fixed:
|
36
|
+
- Directory in distribution archive postfixed with version.
|
data/doc/yml/menu.yml
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Copyright 2006 Michel Casabianca <casa@sweetohm.net>
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
- menu: bee
|
16
|
+
|
17
|
+
- title: Quick Start
|
18
|
+
file: ../html/quickstart.html
|
19
|
+
dest: index.html
|
20
|
+
type: html
|
21
|
+
|
22
|
+
- title: Downloads
|
23
|
+
dest: http://rubyforge.org/frs/?group_id=2479
|
24
|
+
type: link
|
25
|
+
|
26
|
+
- title: Documentation
|
27
|
+
file: ../html/documentation.html
|
28
|
+
dest: documentation.html
|
29
|
+
type: html
|
30
|
+
|
31
|
+
- title: Source API
|
32
|
+
dir: ../../build/api/
|
33
|
+
dest: api
|
34
|
+
type: dir
|
35
|
+
|
36
|
+
- title: License
|
37
|
+
file: ../../LICENSE
|
38
|
+
dest: license.html
|
39
|
+
type: text
|
40
|
+
|
41
|
+
- title: Syntax check
|
42
|
+
file: ../../build/syntax.txt
|
43
|
+
dest: syntax.html
|
44
|
+
type: text
|
45
|
+
|
46
|
+
- title: Unit tests
|
47
|
+
file: ../../build/tests.txt
|
48
|
+
dest: tests.html
|
49
|
+
type: text
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Copyright 2006 Michel Casabianca <casa@sweetohm.net>
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
- description: >
|
16
|
+
Here is the list of Bee release with new features and links to download.
|
17
|
+
download: http://rubyforge.org/frs/?group_id=2479
|
18
|
+
|
19
|
+
- release: alpha
|
20
|
+
description: A simple night hack.
|
21
|
+
|
22
|
+
- release: beta-1
|
23
|
+
description: First public release.
|
24
|
+
new:
|
25
|
+
- Run Ruby scripts.
|
26
|
+
- Load context on startup.
|
27
|
+
- Added checks on build files syntax.
|
28
|
+
- Documentation system.
|
29
|
+
|
30
|
+
- release: 0.1.0
|
31
|
+
description: Distribute bee as a gem.
|
32
|
+
new:
|
33
|
+
- Distribution as a Gem package.
|
34
|
+
- Template generation (-t option).
|
35
|
+
fixed:
|
36
|
+
- Directory in distribution archive postfixed with version.
|
data/doc/yml/todo.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Copyright 2006 Michel Casabianca <casa@sweetohm.net>
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
- Write documentation in RDoc format.
|
16
|
+
- Freeze and document extension mechanism.
|
17
|
+
- Write extension tasks to build bee on any platform.
|
data/lib/bee.rb
ADDED
@@ -0,0 +1,916 @@
|
|
1
|
+
# Copyright 2006 Michel Casabianca <casa@sweetohm.net>
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
# Module for Bee stuff.
|
16
|
+
module Bee
|
17
|
+
|
18
|
+
require 'yaml'
|
19
|
+
require 'getoptlong'
|
20
|
+
|
21
|
+
# Copyright notice.
|
22
|
+
COPYRIGHT = 'Bee version 0.1.0 (C) Michel Casabianca - 2006'
|
23
|
+
# Command line help.
|
24
|
+
HELP = 'Usage: bee [options] [targets]
|
25
|
+
-h Print help about usage and exit.
|
26
|
+
-b Print help about build and exit.
|
27
|
+
-t Write template build file on disk.
|
28
|
+
-v Enable verbose mode.
|
29
|
+
-s style Define style for output (see documentation).
|
30
|
+
-f file Build file to run (defaults to "build.yml").
|
31
|
+
targets Targets to run (default target if omitted).'
|
32
|
+
# Name for default build file.
|
33
|
+
DEFAULT_BUILD_FILE = 'build.yml'
|
34
|
+
# Exit value on error parsing command line
|
35
|
+
EXIT_PARSING_CMDLINE = 1
|
36
|
+
# Exit value on build error
|
37
|
+
EXIT_BUILD_ERROR = 2
|
38
|
+
# Exit value on unknown error
|
39
|
+
EXIT_UNKNOWN_ERROR = 3
|
40
|
+
|
41
|
+
# Parse command line and return parsed arguments.
|
42
|
+
def self.parse_command_line
|
43
|
+
help = false
|
44
|
+
help_build = false
|
45
|
+
template = false
|
46
|
+
verbose = false
|
47
|
+
style = nil
|
48
|
+
file = DEFAULT_BUILD_FILE
|
49
|
+
targets = []
|
50
|
+
# read options in BEEOPT environment variable
|
51
|
+
options = ENV['BEEOPT']
|
52
|
+
options.split(' ').reverse.each { |option| ARGV.unshift(option) } if options
|
53
|
+
# parse command line arguments
|
54
|
+
opts = GetoptLong.new(['--help', '-h', GetoptLong::NO_ARGUMENT ],
|
55
|
+
['--help-build', '-b', GetoptLong::NO_ARGUMENT ],
|
56
|
+
['--template', '-t', GetoptLong::NO_ARGUMENT ],
|
57
|
+
['--verbose', '-v', GetoptLong::NO_ARGUMENT],
|
58
|
+
['--style', '-s', GetoptLong::REQUIRED_ARGUMENT],
|
59
|
+
['--file', '-f', GetoptLong::REQUIRED_ARGUMENT])
|
60
|
+
opts.each do |opt, arg|
|
61
|
+
case opt
|
62
|
+
when '--help'
|
63
|
+
help = true
|
64
|
+
when '--help-build'
|
65
|
+
help_build = true
|
66
|
+
when '--template'
|
67
|
+
template = true
|
68
|
+
when '--verbose'
|
69
|
+
verbose = true
|
70
|
+
when '--style'
|
71
|
+
style = arg
|
72
|
+
when '--file'
|
73
|
+
file = arg
|
74
|
+
end
|
75
|
+
end
|
76
|
+
targets = ARGV
|
77
|
+
return [help, help_build, template, verbose, style, file, targets]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Start build from command line.
|
81
|
+
def self.start_command_line
|
82
|
+
STDOUT.sync = true
|
83
|
+
begin
|
84
|
+
help, help_build, template, verbose, style, file, targets =
|
85
|
+
parse_command_line
|
86
|
+
rescue
|
87
|
+
puts "ERROR: parsing command line (type 'bee -h' for help)"
|
88
|
+
exit(EXIT_PARSING_CMDLINE)
|
89
|
+
end
|
90
|
+
formatter = ConsoleFormatter.new(style)
|
91
|
+
begin
|
92
|
+
if help
|
93
|
+
puts COPYRIGHT
|
94
|
+
puts HELP
|
95
|
+
elsif help_build
|
96
|
+
build = Build.new(file)
|
97
|
+
puts formatter.help_build(build)
|
98
|
+
elsif template
|
99
|
+
puts "Writing build template in file '#{file}'..."
|
100
|
+
raise BuildError.new("Build file '#{file}' already exists") if
|
101
|
+
File.exists?(file)
|
102
|
+
File.open(file, 'w') { |file| file.write(BUILD_TEMPLATE) }
|
103
|
+
puts formatter.format_success("OK")
|
104
|
+
else
|
105
|
+
listener = ConsoleListener.new(formatter, verbose)
|
106
|
+
build = Build.new(file)
|
107
|
+
build.run(targets, listener)
|
108
|
+
end
|
109
|
+
rescue BuildError
|
110
|
+
puts "#{formatter.format_error('ERROR')}: #{$!}"
|
111
|
+
exit(EXIT_BUILD_ERROR)
|
112
|
+
rescue SystemExit
|
113
|
+
# do nothing, exit in code
|
114
|
+
rescue Exception => e
|
115
|
+
puts "#{formatter.format_error('ERROR')}: #{$!}"
|
116
|
+
puts e.backtrace.join("\n")
|
117
|
+
exit(EXIT_UNKNOWN_ERROR)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
#########################################################################
|
122
|
+
# BUILD ERROR #
|
123
|
+
#########################################################################
|
124
|
+
|
125
|
+
class BuildError < RuntimeError; end
|
126
|
+
|
127
|
+
module BuildErrorMixin
|
128
|
+
|
129
|
+
# Convenient method to raise a BuildError.
|
130
|
+
# - message: error message.
|
131
|
+
def error(message)
|
132
|
+
raise BuildError.new(message)
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
#########################################################################
|
138
|
+
# CONSOLE FORMATTER CLASS #
|
139
|
+
#########################################################################
|
140
|
+
|
141
|
+
# Class to format build output on console.
|
142
|
+
class ConsoleFormatter
|
143
|
+
|
144
|
+
include BuildErrorMixin
|
145
|
+
|
146
|
+
###################### ANSI COLORS AND STYLES #########################
|
147
|
+
|
148
|
+
# List of colors.
|
149
|
+
COLORS = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white]
|
150
|
+
# Foreground color codes.
|
151
|
+
FOREGROUND_COLOR_CODES = {
|
152
|
+
:black => 30,
|
153
|
+
:red => 31,
|
154
|
+
:green => 32,
|
155
|
+
:yellow => 33,
|
156
|
+
:blue => 34,
|
157
|
+
:magenta => 35,
|
158
|
+
:cyan => 36,
|
159
|
+
:white => 37
|
160
|
+
}
|
161
|
+
# Background color codes.
|
162
|
+
BACKGROUND_COLOR_CODES = {
|
163
|
+
:black => 40,
|
164
|
+
:red => 41,
|
165
|
+
:green => 42,
|
166
|
+
:yellow => 43,
|
167
|
+
:blue => 44,
|
168
|
+
:magenta => 45,
|
169
|
+
:cyan => 46,
|
170
|
+
:white => 47
|
171
|
+
}
|
172
|
+
# List of styles.
|
173
|
+
STYLES = [:reset, :bright, :dim, :underscore, :blink, :reverse, :hidden]
|
174
|
+
# Style codes.
|
175
|
+
STYLE_CODES = {
|
176
|
+
:reset => 0,
|
177
|
+
:bright => 1,
|
178
|
+
:dim => 2,
|
179
|
+
:underscore => 4,
|
180
|
+
:blink => 5,
|
181
|
+
:reverse => 7,
|
182
|
+
:hidden => 8
|
183
|
+
}
|
184
|
+
|
185
|
+
############################ DEFAULT STYLE ############################
|
186
|
+
|
187
|
+
# Default style (supposed to work on any configuration).
|
188
|
+
DEFAULT_STYLE = {
|
189
|
+
:line_character => '-'
|
190
|
+
}
|
191
|
+
# Default line length.
|
192
|
+
DEFAULT_LINE_LENGTH = 80
|
193
|
+
# Short style keys for command line
|
194
|
+
SHORT_STYLE_KEYS = {
|
195
|
+
'lc' => 'line_character',
|
196
|
+
'll' => 'line_length',
|
197
|
+
'ts' => 'target_style',
|
198
|
+
'tf' => 'target_foreground',
|
199
|
+
'tb' => 'target_background',
|
200
|
+
'ks' => 'task_style',
|
201
|
+
'kf' => 'task_foreground',
|
202
|
+
'kb' => 'task_background',
|
203
|
+
'ss' => 'success_style',
|
204
|
+
'sf' => 'success_foreground',
|
205
|
+
'sb' => 'success_background',
|
206
|
+
'es' => 'error_style',
|
207
|
+
'ef' => 'error_foreground',
|
208
|
+
'eb' => 'error_background'
|
209
|
+
}
|
210
|
+
|
211
|
+
############################## METHODS ################################
|
212
|
+
|
213
|
+
# Constructor.
|
214
|
+
# - style: style as a Hash or a String.
|
215
|
+
def initialize(style)
|
216
|
+
# if style is a String, this is command line argument
|
217
|
+
style = parse_style_from_command_line(style) if style.kind_of?(String)
|
218
|
+
# if style is nil, set default style
|
219
|
+
@style = style || DEFAULT_STYLE
|
220
|
+
# set default values for keys if nil
|
221
|
+
for key in DEFAULT_STYLE.keys
|
222
|
+
@style[key] = @style[key] || DEFAULT_STYLE[key]
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# Format a target.
|
227
|
+
# - target: target to format.
|
228
|
+
def format_target(target)
|
229
|
+
name = target.name
|
230
|
+
# generate title line
|
231
|
+
length = @style[:line_length] || line_length || DEFAULT_LINE_LENGTH
|
232
|
+
right = ' ' + @style[:line_character]*2
|
233
|
+
left = @style[:line_character]*(length - (name.length + 4)) + ' '
|
234
|
+
line = left + name + right
|
235
|
+
# apply style
|
236
|
+
formatted = style(line,
|
237
|
+
@style[:target_style],
|
238
|
+
@style[:target_foreground],
|
239
|
+
@style[:target_background])
|
240
|
+
return formatted
|
241
|
+
end
|
242
|
+
|
243
|
+
# Format a task.
|
244
|
+
# - task: task to format.
|
245
|
+
def format_task(task)
|
246
|
+
if task.kind_of?(String)
|
247
|
+
source = task
|
248
|
+
elsif task.kind_of?(Hash)
|
249
|
+
if task.key?('rb')
|
250
|
+
source = task['rb']
|
251
|
+
else
|
252
|
+
source = YAML::dump(task)
|
253
|
+
source = source.sub(/---/, '')
|
254
|
+
end
|
255
|
+
end
|
256
|
+
formatted = '- ' + source.strip.gsub(/\n/, "\n. ")
|
257
|
+
styled = style(formatted,
|
258
|
+
@style[:task_style],
|
259
|
+
@style[:task_foreground],
|
260
|
+
@style[:task_background])
|
261
|
+
return styled
|
262
|
+
end
|
263
|
+
|
264
|
+
# Format a success string.
|
265
|
+
# - string: string to format.
|
266
|
+
def format_success(string)
|
267
|
+
string = style(string,
|
268
|
+
@style[:success_style],
|
269
|
+
@style[:success_foreground],
|
270
|
+
@style[:success_background])
|
271
|
+
return string
|
272
|
+
end
|
273
|
+
|
274
|
+
# Format an error string.
|
275
|
+
# - string: string to format.
|
276
|
+
def format_error(string)
|
277
|
+
string = style(string,
|
278
|
+
@style[:error_style],
|
279
|
+
@style[:error_foreground],
|
280
|
+
@style[:error_background])
|
281
|
+
return string
|
282
|
+
end
|
283
|
+
|
284
|
+
# Return help about build.
|
285
|
+
def help_build(build)
|
286
|
+
help = ''
|
287
|
+
if build.name
|
288
|
+
help << "- Build: #{build.name.inspect}\n"
|
289
|
+
end
|
290
|
+
if build.description
|
291
|
+
help << format_description('Description', build.description, 2, false)
|
292
|
+
end
|
293
|
+
if build.properties.keys.length > 0
|
294
|
+
help << "- Properties:\n"
|
295
|
+
for property in build.properties.keys.sort
|
296
|
+
help << " - #{property}: #{build.properties[property].inspect}\n"
|
297
|
+
end
|
298
|
+
end
|
299
|
+
if build.targets.length > 0
|
300
|
+
help << "- Targets:\n"
|
301
|
+
for target in build.targets.values.sort { |a, b| a.name <=> b.name }
|
302
|
+
help << format_description(target.name, target.description, 2)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
help << "- Default: #{build.default}"
|
306
|
+
return help.strip
|
307
|
+
end
|
308
|
+
|
309
|
+
private
|
310
|
+
|
311
|
+
# Apply style to a string:
|
312
|
+
# - string: the string to apply style to.
|
313
|
+
# - style: style to apply on string.
|
314
|
+
# - foreground: foreground color for string.
|
315
|
+
# - background: background color for string.
|
316
|
+
def style(string, style, foreground, background)
|
317
|
+
# check style, foreground and background colors
|
318
|
+
error "Unknown style '#{style}'" unless
|
319
|
+
STYLES.member?(style) or not style
|
320
|
+
error "Unknown color '#{foreground}'" unless
|
321
|
+
COLORS.member?(foreground) or not foreground
|
322
|
+
error "Unknown color '#{background}'" unless
|
323
|
+
COLORS.member?(background) or not background
|
324
|
+
# if no style nor colors, return raw string
|
325
|
+
return string if not foreground and not background and not style
|
326
|
+
# insert style and colors in string
|
327
|
+
colorized = "\e["
|
328
|
+
colorized << "#{STYLE_CODES[style]};" if style
|
329
|
+
colorized << "#{FOREGROUND_COLOR_CODES[foreground]};" if foreground
|
330
|
+
colorized << "#{BACKGROUND_COLOR_CODES[background]};" if background
|
331
|
+
colorized = colorized[0..-2]
|
332
|
+
colorized << "m#{string}\e[#{STYLE_CODES[:reset]}m"
|
333
|
+
return colorized
|
334
|
+
end
|
335
|
+
|
336
|
+
# Get line length calling IOCTL. Return nil if call failed.
|
337
|
+
def line_length
|
338
|
+
begin
|
339
|
+
tiocgwinsz = 0x5413
|
340
|
+
string = [0, 0, 0, 0].pack('SSSS')
|
341
|
+
if $stdin.ioctl(tiocgwinsz, string) >= 0 then
|
342
|
+
rows, cols, xpixels, ypixels = string.unpack('SSSS')
|
343
|
+
return cols
|
344
|
+
end
|
345
|
+
rescue
|
346
|
+
return nil
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
# Parse style from command line. If error occurs parsing style, return
|
351
|
+
# nil (which means default style).
|
352
|
+
# - string: style to parse.
|
353
|
+
def parse_style_from_command_line(string)
|
354
|
+
return if not string
|
355
|
+
style = {}
|
356
|
+
begin
|
357
|
+
for pair in string.split(',')
|
358
|
+
key, value = pair.split(':')
|
359
|
+
key = SHORT_STYLE_KEYS[key] || key
|
360
|
+
key = key.to_sym
|
361
|
+
if key == :line_length
|
362
|
+
value = value.to_i
|
363
|
+
elsif key == :line_character
|
364
|
+
value = ' ' if not value or value.length == 0
|
365
|
+
else
|
366
|
+
value = value.to_sym
|
367
|
+
error "Unkown color or style '#{value}'" if
|
368
|
+
not COLORS.member?(value) and not STYLES.member?(value)
|
369
|
+
end
|
370
|
+
style[key] = value
|
371
|
+
end
|
372
|
+
return style
|
373
|
+
rescue
|
374
|
+
# if parsing fails, return default style (nil)
|
375
|
+
return nil
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
# Format a description.
|
380
|
+
# - title: description title (project, property or target name).
|
381
|
+
# - text: description text.
|
382
|
+
# - indent: indentation width.
|
383
|
+
# - bullet: tells if we must put a bullet.
|
384
|
+
def format_description(title, text=nil, indent=0, bullet=true)
|
385
|
+
string = ' '*indent
|
386
|
+
string << '- ' if bullet
|
387
|
+
string << title
|
388
|
+
if text
|
389
|
+
string << ": "
|
390
|
+
if text.split("\n").length > 1
|
391
|
+
string << "\n"
|
392
|
+
text.split("\n").each do |line|
|
393
|
+
string << ' '*(indent+2) + line.strip + "\n"
|
394
|
+
end
|
395
|
+
else
|
396
|
+
string << text.strip + "\n"
|
397
|
+
end
|
398
|
+
end
|
399
|
+
return string
|
400
|
+
end
|
401
|
+
|
402
|
+
end
|
403
|
+
|
404
|
+
#########################################################################
|
405
|
+
# CONSOLE LISTENER #
|
406
|
+
#########################################################################
|
407
|
+
|
408
|
+
# Listener when running in a console. Prints messages on the console using
|
409
|
+
# a given formatter.
|
410
|
+
class ConsoleListener
|
411
|
+
|
412
|
+
# Formatter used by listener.
|
413
|
+
attr_reader :formatter
|
414
|
+
# Verbosity flag.
|
415
|
+
attr_reader :verbose
|
416
|
+
# Build start time.
|
417
|
+
attr_reader :start_time
|
418
|
+
# Build end time.
|
419
|
+
attr_reader :end_time
|
420
|
+
# Build duration.
|
421
|
+
attr_reader :duration
|
422
|
+
# Build success.
|
423
|
+
attr_reader :success
|
424
|
+
# Last target met.
|
425
|
+
attr_reader :last_target
|
426
|
+
# Last task met.
|
427
|
+
attr_reader :last_task
|
428
|
+
|
429
|
+
# Constructor.
|
430
|
+
# - formatter: the formatter to use to output on console.
|
431
|
+
# - verbose: tells if we run in verbose mode.
|
432
|
+
def initialize(formatter, verbose)
|
433
|
+
@formatter = formatter
|
434
|
+
@verbose = verbose
|
435
|
+
end
|
436
|
+
|
437
|
+
# Called when build is started.
|
438
|
+
# - build: the build object.
|
439
|
+
def build_started(build)
|
440
|
+
@start_time = Time.now
|
441
|
+
@end_time = nil
|
442
|
+
@duration = nil
|
443
|
+
@success = nil
|
444
|
+
@last_target = nil
|
445
|
+
@last_task = nil
|
446
|
+
puts "Starting build '#{build.file}'..." if @verbose
|
447
|
+
end
|
448
|
+
|
449
|
+
# Called when build is finished.
|
450
|
+
# - build: the build object.
|
451
|
+
def build_finished(build)
|
452
|
+
@end_time = Time.now
|
453
|
+
@duration = @end_time - @start_time
|
454
|
+
@success = true
|
455
|
+
puts "Built in #{@duration} s" if @verbose
|
456
|
+
puts @formatter.format_success('OK')
|
457
|
+
end
|
458
|
+
|
459
|
+
# Called when a target is met.
|
460
|
+
# - target: the target object.
|
461
|
+
def target(target)
|
462
|
+
@last_target = target
|
463
|
+
@last_task = nil
|
464
|
+
puts @formatter.format_target(target)
|
465
|
+
end
|
466
|
+
|
467
|
+
# Called when a task is met.
|
468
|
+
# - task: task source (shell, Ruby or task).
|
469
|
+
def task(task)
|
470
|
+
@last_task = task
|
471
|
+
puts @formatter.format_task(task) if @verbose
|
472
|
+
end
|
473
|
+
|
474
|
+
# Called when an error was raised.
|
475
|
+
# - exception: raised exception.
|
476
|
+
def error(exception)
|
477
|
+
@end_time = Time.now
|
478
|
+
@duration = @end_time - @start_time
|
479
|
+
@success = false
|
480
|
+
puts "Built in #{@duration} s" if @verbose
|
481
|
+
message = ''
|
482
|
+
message << "In target '#{@last_target.name}'" if @last_target
|
483
|
+
message << ", in task:\n#{@formatter.format_task(@last_task)}\n" if
|
484
|
+
@last_task
|
485
|
+
message << ': ' if @last_target and not @last_task
|
486
|
+
message << exception.to_s
|
487
|
+
puts "#{@formatter.format_error('ERROR')}: #{message}"
|
488
|
+
end
|
489
|
+
|
490
|
+
end
|
491
|
+
|
492
|
+
#########################################################################
|
493
|
+
# BUILD CLASS #
|
494
|
+
#########################################################################
|
495
|
+
|
496
|
+
# Class for a given build.
|
497
|
+
class Build
|
498
|
+
|
499
|
+
include BuildErrorMixin
|
500
|
+
|
501
|
+
# Build file.
|
502
|
+
attr_reader :file
|
503
|
+
# Base directory.
|
504
|
+
attr_reader :base
|
505
|
+
# Build name.
|
506
|
+
attr_reader :name
|
507
|
+
# Default target.
|
508
|
+
attr_reader :default
|
509
|
+
# Build description.
|
510
|
+
attr_reader :description
|
511
|
+
# Properties hash (used for project help).
|
512
|
+
attr_reader :properties
|
513
|
+
# Hash for targets, indexed by target name.
|
514
|
+
attr_reader :targets
|
515
|
+
# Context for Ruby scripts and properties.
|
516
|
+
attr_reader :context
|
517
|
+
# Loaded extensions.
|
518
|
+
attr_reader :extensions
|
519
|
+
# Build listener.
|
520
|
+
attr_reader :listener
|
521
|
+
|
522
|
+
# Constructor:
|
523
|
+
# - file: build file to load.
|
524
|
+
def initialize(file)
|
525
|
+
@file = file
|
526
|
+
@properties = {}
|
527
|
+
@targets = {}
|
528
|
+
@context = Context.new
|
529
|
+
@extensions = {}
|
530
|
+
@base = File.expand_path(File.dirname(@file))
|
531
|
+
@context.set('base', @base)
|
532
|
+
@properties['base'] = @base
|
533
|
+
load_context(DEFAULT_CONTEXT)
|
534
|
+
# load build file
|
535
|
+
begin
|
536
|
+
source = File.read(@file)
|
537
|
+
build = YAML::load(source)
|
538
|
+
# parse object entries
|
539
|
+
for entry in build
|
540
|
+
if entry.key?('build')
|
541
|
+
# build info entry
|
542
|
+
error "Duplicate build info" if @name
|
543
|
+
@name = entry['build']
|
544
|
+
@default = entry['default']
|
545
|
+
@description = entry['description']
|
546
|
+
# load context files if any
|
547
|
+
if entry['context']
|
548
|
+
for script in entry['context']
|
549
|
+
begin
|
550
|
+
based_script = File.join(@base, script)
|
551
|
+
evaluated_script = @context.evaluate_string(based_script)
|
552
|
+
source = File.read(evaluated_script)
|
553
|
+
load_context(source)
|
554
|
+
rescue
|
555
|
+
error "Error loading context '#{script}': #{$!}"
|
556
|
+
end
|
557
|
+
end
|
558
|
+
end
|
559
|
+
elsif entry.key?('properties')
|
560
|
+
# properties entry
|
561
|
+
for property in entry['properties']
|
562
|
+
begin
|
563
|
+
name = property.keys[0]
|
564
|
+
value = @context.evaluate_string(property[name])
|
565
|
+
error "Duplicate property '#{name}' definition" if
|
566
|
+
@properties[name]
|
567
|
+
@properties[name] = value
|
568
|
+
@context.set(name, value)
|
569
|
+
rescue
|
570
|
+
error "Error evaluating property '#{name}': #{$!}"
|
571
|
+
end
|
572
|
+
end
|
573
|
+
elsif entry.key?('target')
|
574
|
+
# target entry
|
575
|
+
target = Target.new(entry, self)
|
576
|
+
@default = target.name if not @default and @targets.keys.length == 0
|
577
|
+
error "Duplicate target definition: '#{target.name}'" if
|
578
|
+
@targets[target.name]
|
579
|
+
@targets[target.name] = target
|
580
|
+
else
|
581
|
+
# unknown entry
|
582
|
+
error "Unknown entry:\n#{YAML::dump(entry)}"
|
583
|
+
end
|
584
|
+
end
|
585
|
+
rescue
|
586
|
+
error "Error loading build file '#{@file}': #{$!}"
|
587
|
+
end
|
588
|
+
end
|
589
|
+
|
590
|
+
# Run build:
|
591
|
+
# - targets: list of targets to run.
|
592
|
+
# - listener: listener for the build.
|
593
|
+
def run(targets, listener=nil)
|
594
|
+
@listener = listener
|
595
|
+
working_directory = Dir.getwd
|
596
|
+
begin
|
597
|
+
@listener.build_started(self) if @listener
|
598
|
+
Dir.chdir(base)
|
599
|
+
error "No default target given" if (!@default and targets.length == 0)
|
600
|
+
targets = [@default] if targets.length == 0
|
601
|
+
for target in targets
|
602
|
+
run_target(target)
|
603
|
+
end
|
604
|
+
@listener.build_finished(self)
|
605
|
+
rescue BuildError => e
|
606
|
+
if listener
|
607
|
+
@listener.error(e)
|
608
|
+
else
|
609
|
+
raise e
|
610
|
+
end
|
611
|
+
ensure
|
612
|
+
@listener = nil
|
613
|
+
Dir.chdir(working_directory)
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
# Run a given target.
|
618
|
+
# - target: the target to run.
|
619
|
+
def run_target(target)
|
620
|
+
error "Target '#{target}' not found" if not @targets[target]
|
621
|
+
@targets[target].run
|
622
|
+
end
|
623
|
+
|
624
|
+
private
|
625
|
+
|
626
|
+
# Load a given context.
|
627
|
+
# - source: the context source.
|
628
|
+
def load_context(source)
|
629
|
+
@context.evaluate(source)
|
630
|
+
@extensions = @context.get('TASKS')
|
631
|
+
end
|
632
|
+
|
633
|
+
end
|
634
|
+
|
635
|
+
#########################################################################
|
636
|
+
# TARGET CLASS #
|
637
|
+
#########################################################################
|
638
|
+
|
639
|
+
# Class for a given target.
|
640
|
+
class Target
|
641
|
+
|
642
|
+
include BuildErrorMixin
|
643
|
+
|
644
|
+
# Build that encapsulates target.
|
645
|
+
attr_reader :build
|
646
|
+
# Name of the target.
|
647
|
+
attr_reader :name
|
648
|
+
# Target dependencies.
|
649
|
+
attr_reader :depends
|
650
|
+
# Target description.
|
651
|
+
attr_reader :description
|
652
|
+
# Script that run in the target.
|
653
|
+
attr_reader :script
|
654
|
+
|
655
|
+
# Constructor.
|
656
|
+
# - target: target for target, resulting from YAML parsing.
|
657
|
+
# - build: build that encapsulate this target.
|
658
|
+
def initialize(target, build)
|
659
|
+
@build = build
|
660
|
+
@name = target['target']
|
661
|
+
@depends = target['depends']||[]
|
662
|
+
@description = target['description']
|
663
|
+
@script = target['script']
|
664
|
+
end
|
665
|
+
|
666
|
+
# Run target.
|
667
|
+
def run
|
668
|
+
if not @evaluated
|
669
|
+
@evaluated = true
|
670
|
+
for depend in @depends
|
671
|
+
@build.run_target(depend)
|
672
|
+
end
|
673
|
+
@build.listener.target(self) if @build.listener
|
674
|
+
if @script
|
675
|
+
case @script
|
676
|
+
when String
|
677
|
+
run_task(@script)
|
678
|
+
when Array
|
679
|
+
for task in @script
|
680
|
+
run_task(task)
|
681
|
+
end
|
682
|
+
end
|
683
|
+
end
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
private
|
688
|
+
|
689
|
+
# Run a task.
|
690
|
+
# - task: the task to run.
|
691
|
+
def run_task(task)
|
692
|
+
@build.listener.task(task) if @build.listener
|
693
|
+
case task
|
694
|
+
when String
|
695
|
+
# shell script
|
696
|
+
run_shell(task)
|
697
|
+
when Hash
|
698
|
+
error "A task entry must be a Hash with a single key" if
|
699
|
+
task.keys.length != 1
|
700
|
+
if task.key?('rb')
|
701
|
+
# ruby script
|
702
|
+
script = task['rb']
|
703
|
+
run_ruby(script)
|
704
|
+
else
|
705
|
+
# must be a task
|
706
|
+
run_extension(task)
|
707
|
+
end
|
708
|
+
end
|
709
|
+
end
|
710
|
+
|
711
|
+
# Run a given shell script.
|
712
|
+
# - script: the scrip to run.
|
713
|
+
def run_shell(script)
|
714
|
+
@listener.task(script) if @listener
|
715
|
+
evaluated_script = @build.context.evaluate_string(script)
|
716
|
+
system(evaluated_script)
|
717
|
+
error "Script exited with value #{$?}" if $? != 0
|
718
|
+
end
|
719
|
+
|
720
|
+
# Run a given shell script.
|
721
|
+
# - script: the scrip to run.
|
722
|
+
def run_ruby(script)
|
723
|
+
@listener.task(script) if @listener
|
724
|
+
@build.context.evaluate(script)
|
725
|
+
end
|
726
|
+
|
727
|
+
# Run a given task.
|
728
|
+
# - task: task to run as a Hash.
|
729
|
+
def run_extension(task)
|
730
|
+
@listener.task(task) if @listener
|
731
|
+
name = task.keys[0]
|
732
|
+
error "Unknown task '#{name}'" if
|
733
|
+
not @build.extensions.key?(name)
|
734
|
+
parameters = task[name]
|
735
|
+
script = "#{name}(#{parameters.inspect})"
|
736
|
+
@build.context.evaluate(script)
|
737
|
+
end
|
738
|
+
|
739
|
+
end
|
740
|
+
|
741
|
+
#########################################################################
|
742
|
+
# CONTEXT CLASS #
|
743
|
+
#########################################################################
|
744
|
+
|
745
|
+
# Class for Ruby scripts context.
|
746
|
+
class Context
|
747
|
+
|
748
|
+
include BuildErrorMixin
|
749
|
+
|
750
|
+
# Constructor.
|
751
|
+
def initialize
|
752
|
+
@binding = get_binding
|
753
|
+
end
|
754
|
+
|
755
|
+
# Set a given value in context.
|
756
|
+
# - name: the variable name to set.
|
757
|
+
# - value: the variable value.
|
758
|
+
def set(name, value)
|
759
|
+
begin
|
760
|
+
eval("#{name} = #{value.inspect}", @binding)
|
761
|
+
rescue
|
762
|
+
error "Error setting property '#{name} = #{value.inspect}': #{$!}"
|
763
|
+
end
|
764
|
+
end
|
765
|
+
|
766
|
+
# Get a given value in context.
|
767
|
+
# - name: the variable name.
|
768
|
+
def get(name)
|
769
|
+
begin
|
770
|
+
eval("#{name}", @binding)
|
771
|
+
rescue
|
772
|
+
error "Error getting property '#{name}': #{$!}"
|
773
|
+
end
|
774
|
+
end
|
775
|
+
|
776
|
+
# Evaluate a script in context.
|
777
|
+
# - script: script to evaluate.
|
778
|
+
def evaluate(script)
|
779
|
+
begin
|
780
|
+
eval(script, @binding)
|
781
|
+
rescue
|
782
|
+
error "Error running script: #{$!}"
|
783
|
+
end
|
784
|
+
end
|
785
|
+
|
786
|
+
# Process a given string, replacing properties references with their
|
787
|
+
# value. Property references have same form than variable references
|
788
|
+
# in ruby strings: '#{variable}' will be replaced with variable value.
|
789
|
+
# - string: string to process.
|
790
|
+
def evaluate_string(string)
|
791
|
+
return nil if string == nil
|
792
|
+
return string if not string.kind_of?(String)
|
793
|
+
string = string.gsub(/#\{.+?\}/) do |match|
|
794
|
+
property = match[2..-2]
|
795
|
+
value = get(property)
|
796
|
+
error ("Property '#{property}' was not defined") unless value
|
797
|
+
value
|
798
|
+
end
|
799
|
+
return string
|
800
|
+
end
|
801
|
+
|
802
|
+
private
|
803
|
+
|
804
|
+
# Get a binding as script context.
|
805
|
+
def get_binding
|
806
|
+
return binding
|
807
|
+
end
|
808
|
+
|
809
|
+
end
|
810
|
+
|
811
|
+
#########################################################################
|
812
|
+
# DEFAULT CONTEXT #
|
813
|
+
#########################################################################
|
814
|
+
|
815
|
+
DEFAULT_CONTEXT = '
|
816
|
+
# Default context script loaded at startup.
|
817
|
+
|
818
|
+
# Hash of tasks help indexed by their name.
|
819
|
+
TASKS = {}
|
820
|
+
|
821
|
+
######################### TASKS UTILITY FUNCTIONS #########################
|
822
|
+
|
823
|
+
# Bind tasks. This method must be called by context scripts defining tasks.
|
824
|
+
# - tasks: Hash indexing task help with task names.
|
825
|
+
def bind_tasks(tasks)
|
826
|
+
for name, help in tasks
|
827
|
+
raise "Task \'#{name}\' already bound" if TASKS.key?(name)
|
828
|
+
TASKS[name] = help
|
829
|
+
end
|
830
|
+
end
|
831
|
+
|
832
|
+
# Check a task parameters. Raise a RuntimeError with explanation message if
|
833
|
+
# a mandatory parameter is missing or an unknown parameter was found.
|
834
|
+
# - params: task parameters as a Hash.
|
835
|
+
# - description: parameters description as a Hash associating a parameter
|
836
|
+
# name with symbol :mandatory or :optional.
|
837
|
+
def check_parameters(params, description)
|
838
|
+
raise "Parameters must be a Hash" unless params.kind_of?(Hash)
|
839
|
+
for param in description.keys
|
840
|
+
raise "Missing mandatory parameter \'#{param}\'" unless
|
841
|
+
params[param] or description[param] == :optional
|
842
|
+
end
|
843
|
+
for param in params.keys
|
844
|
+
raise "Unknown parameter \'#{param}\'" if not description.key?(param)
|
845
|
+
end
|
846
|
+
end
|
847
|
+
|
848
|
+
############################ TASK DEFINITIONS #############################
|
849
|
+
|
850
|
+
# bind tasks defined hereafter
|
851
|
+
bind_tasks({
|
852
|
+
"mkdir" =>
|
853
|
+
"Create a directory and all parent directories if necessary. Do nothing if
|
854
|
+
directory already exists. Parameter is a String for directory to create."
|
855
|
+
})
|
856
|
+
|
857
|
+
# Make a directory and parent directories if necessary.
|
858
|
+
def self.mkdir(dir)
|
859
|
+
raise "Parameter must be a String" unless dir.kind_of?(String)
|
860
|
+
require "fileutils"
|
861
|
+
FileUtils.makedirs(dir)
|
862
|
+
end
|
863
|
+
|
864
|
+
############################ UTILITY FUNCTIONS ############################
|
865
|
+
|
866
|
+
# Run a shell script and raise an exception if script returned a value
|
867
|
+
# different of 0.
|
868
|
+
# - script: script to run.
|
869
|
+
def sh(script)
|
870
|
+
system(script) or raise "Script \'#{script}\' exited with value \'#{$?}\'"
|
871
|
+
end
|
872
|
+
|
873
|
+
# Find files that match a given pattern.
|
874
|
+
# - pattern: pattern to select files.
|
875
|
+
# - base: base directory for search.
|
876
|
+
# - dir: tells if we must include directories.
|
877
|
+
# Return: a file list.
|
878
|
+
def find(pattern=//, base=\'.\', dir=true)
|
879
|
+
require \'find\'
|
880
|
+
files = []
|
881
|
+
Find.find(base) do |path|
|
882
|
+
if path =~ pattern and (File.file?(path) or (File.directory?(path) and dir))
|
883
|
+
files << path
|
884
|
+
end
|
885
|
+
end
|
886
|
+
return files
|
887
|
+
end
|
888
|
+
'
|
889
|
+
|
890
|
+
#########################################################################
|
891
|
+
# BUILD TEMPLATE #
|
892
|
+
#########################################################################
|
893
|
+
|
894
|
+
BUILD_TEMPLATE =
|
895
|
+
'# Template build file
|
896
|
+
- build: template
|
897
|
+
description: Template build file.
|
898
|
+
default: hello
|
899
|
+
|
900
|
+
# Build properties
|
901
|
+
- properties:
|
902
|
+
- user: "#{ENV[\'USER\']}"
|
903
|
+
|
904
|
+
# Build targets
|
905
|
+
- target: capitalize
|
906
|
+
description: Capitalize user name
|
907
|
+
script:
|
908
|
+
- rb: "who = user.capitalize"
|
909
|
+
|
910
|
+
- target: hello
|
911
|
+
depends: capitalize
|
912
|
+
description: Print greatings.
|
913
|
+
script:
|
914
|
+
- "echo \"Hello #{who}!\""
|
915
|
+
'
|
916
|
+
end
|