bee 0.9.0 → 0.10.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.
@@ -62,7 +62,7 @@ targets Targets to run (default target if omitted).'
62
62
  | |__ ___ ___
63
63
  ____ | '_ \ / _ \/ _ \ _____ _____ _____ _____ _____ _____ _____ _____ _____
64
64
  |____| | |_) | __/ __/ |_____|_____|_____|_____|_____|_____|_____|_____|_____|
65
- |_.__/ \___|\___| 0.9.0 http://bee.rubyforge.org
65
+ |_.__/ \___|\___| 0.10.0 http://bee.rubyforge.org
66
66
 
67
67
  EOF
68
68
 
@@ -180,7 +180,12 @@ EOF
180
180
  puts "ERROR: parsing command line (type 'bee -h' for help)"
181
181
  exit(EXIT_PARSING_CMDLINE)
182
182
  end
183
- formatter = Formatter.new(style, color)
183
+ begin
184
+ formatter = Formatter.new(style, color)
185
+ rescue
186
+ puts "ERROR: bad format string '#{style}'"
187
+ exit(EXIT_PARSING_CMDLINE)
188
+ end
184
189
  begin
185
190
  if logo
186
191
  puts BEE_LOGO
@@ -372,7 +377,7 @@ EOF
372
377
  help << "build: #{build.name}\n"
373
378
  end
374
379
  if build.extends
375
- help << "extends: #{build.extends}\n"
380
+ help << "extends: #{build.extends.map{|b| b.name}.join(', ')}\n"
376
381
  end
377
382
  if build.description
378
383
  help << format_description('description', build.description, 0, false)
@@ -434,10 +439,8 @@ EOF
434
439
  end
435
440
  description = 'No description found'
436
441
  if properties
437
- for property in properties
438
- if property.keys == ['description']
439
- description = property['description']
440
- end
442
+ if properties['description']
443
+ description = properties['description']
441
444
  end
442
445
  end
443
446
  help << format_title(name)
@@ -475,33 +478,27 @@ EOF
475
478
  return colorized
476
479
  end
477
480
 
478
- # Parse style from command line. If error occurs parsing style, return
479
- # nil (which means default style).
481
+ # Parse style from command line.
480
482
  # - string: style to parse.
481
483
  def parse_style_from_command_line(string)
482
484
  return if not string
483
485
  style = {}
484
- begin
485
- for pair in string.split(',')
486
- key, value = pair.split(':')
487
- key = SHORT_STYLE_KEYS[key] || key
488
- key = key.to_sym
489
- if key == :line_length
490
- value = value.to_i
491
- elsif key == :line_character
492
- value = ' ' if not value or value.length == 0
493
- else
494
- value = value.to_sym
495
- error "Unkown color or style '#{value}'" if
496
- not COLORS.member?(value) and not STYLES.member?(value)
497
- end
498
- style[key] = value
486
+ for pair in string.split(',')
487
+ key, value = pair.split(':')
488
+ key = SHORT_STYLE_KEYS[key] || key
489
+ key = key.to_sym
490
+ if key == :line_length
491
+ value = value.to_i
492
+ elsif key == :line_character
493
+ value = ' ' if not value or value.length == 0
494
+ else
495
+ value = value.to_sym
496
+ error "Unkown color or style '#{value}'" if
497
+ not COLORS.member?(value) and not STYLES.member?(value)
499
498
  end
500
- return style
501
- rescue
502
- # if parsing fails, return default style (nil)
503
- return nil
499
+ style[key] = value
504
500
  end
501
+ return style
505
502
  end
506
503
 
507
504
  # Format a description.
@@ -0,0 +1,138 @@
1
+ # Copyright 2006-2010 Michel Casabianca <michel.casabianca@gmail.com>
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 core classes.
16
+ module Bee
17
+
18
+ # Class for Ruby scripts context. All embedded Ruby scripts run in this
19
+ # context where build properties are defined as Ruby variables.
20
+ class Context
21
+
22
+ include Bee::Util::BuildErrorMixin
23
+
24
+ # The binding of this context.
25
+ attr_reader :context_binding
26
+
27
+ # Constructor.
28
+ def initialize(properties)
29
+ @properties = properties
30
+ @context_binding = get_binding
31
+ end
32
+
33
+ # Set a given property in context.
34
+ # - name: the property name.
35
+ # - value: the property value.
36
+ # - override: tells if we can overwrite an existing value.
37
+ def set_property(name, value, overwrite=true)
38
+ error "Property '#{name}' was already defined" if
39
+ !overwrite and properties.include?(name.to_s)
40
+ begin
41
+ eval("#{name} = #{value.inspect}", @context_binding)
42
+ rescue Exception
43
+ error "Error setting property '#{name} = #{value.inspect}': #{$!}"
44
+ end
45
+ end
46
+
47
+ # Get a given property in context.
48
+ # - name: the property name.
49
+ # - strict: raise an error if given property was not set.
50
+ def get_property(name, strict=false)
51
+ error "Property '#{name}' was not set" if
52
+ strict and !properties.include?(name.to_s)
53
+ begin
54
+ eval("#{name}", @context_binding)
55
+ rescue NameError
56
+ error "Property '#{name}' was not set"
57
+ rescue Exception
58
+ error "Error getting property '#{name}': #{$!}"
59
+ end
60
+ end
61
+
62
+ # Return list of properties (as local variables of binding).
63
+ def properties
64
+ return eval('local_variables', @context_binding).map{ |var| var.to_s }
65
+ end
66
+
67
+ # Evaluate a script in context.
68
+ # - script: source of the script to evaluate.
69
+ def evaluate_script(script)
70
+ eval(script, @context_binding)
71
+ end
72
+
73
+ # Process a given object, replacing properties references with their
74
+ # string value, symbol with their raw value. Property references have
75
+ # same form than variable references in ruby strings: '#{variable}'
76
+ # will be replaced with variable string value.
77
+ # - object: object to process.
78
+ def evaluate_object(object)
79
+ case object
80
+ when NilClass
81
+ # nil: return nil
82
+ return nil
83
+ when String
84
+ # string: replace embedded Ruby expressions
85
+ object = object.gsub(/#\{.+?\}/) do |match|
86
+ expression = match[2..-2]
87
+ begin
88
+ value = evaluate_script(expression)
89
+ rescue
90
+ error "Error evaluating expression '#{expression}': #{$!}"
91
+ end
92
+ value
93
+ end
94
+ return object
95
+ when Symbol
96
+ # symbol: return property object
97
+ property = object.to_s
98
+ begin
99
+ value = eval("#{property}", @context_binding)
100
+ rescue
101
+ error "Property '#{property}' was not set"
102
+ end
103
+ return evaluate_object(value)
104
+ when Array
105
+ # array: evaluate each element
106
+ return object.collect { |element| evaluate_object(element) }
107
+ when Hash
108
+ # hash: evaluate all keys and values
109
+ evaluated = {}
110
+ object.each_pair do |key, value|
111
+ evaluated[evaluate_object(key)] = evaluate_object(value)
112
+ end
113
+ return evaluated
114
+ else
115
+ return object
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ # Get a binding as script context.
122
+ def get_binding
123
+ return binding
124
+ end
125
+
126
+ # Catch missing properties
127
+ def method_missing(name, *args, &block)
128
+ stack = Thread.current[:stack]
129
+ if stack
130
+ @properties.evaluate_property(name, self)
131
+ else
132
+ raise NoMethodError.new("undefined method `#{name}'", name, args)
133
+ end
134
+ end
135
+
136
+ end
137
+
138
+ end
@@ -0,0 +1,145 @@
1
+ # Copyright 2006-2010 Michel Casabianca <michel.casabianca@gmail.com>
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
+ require 'bee_context'
16
+
17
+ # Module for Bee core classes.
18
+ module Bee
19
+
20
+ # Class to manage properties.
21
+ class Properties
22
+
23
+ include Bee::Util::BuildErrorMixin
24
+
25
+ # Key for properties entry.
26
+ KEY = 'properties'
27
+
28
+ # List of system properties
29
+ SYSTEM_PROPERTIES = [:base, :here]
30
+
31
+ # Properties expressions as a hash.
32
+ attr_reader :expressions
33
+ # Properties values as a hash
34
+ attr_reader :values
35
+
36
+ # Constructor.
37
+ # - build: build object.
38
+ def initialize(build)
39
+ @build = build
40
+ @expressions = {}
41
+ @values = {}
42
+ defaults
43
+ end
44
+
45
+ # Write properties. Will raise an error on duplicate definitions.
46
+ # - object: object tree resulting from YAML build file parsing or string
47
+ # for a properties file to load.
48
+ def write(object)
49
+ properties = object[Properties::KEY]
50
+ if properties.kind_of?(String)
51
+ begin
52
+ source = Bee::Util::get_file(properties, @build.base)
53
+ hash = YAML::load(source)
54
+ set_properties(hash, false)
55
+ rescue Exception
56
+ error "Error loading properties file '#{properties}': #{$!}"
57
+ end
58
+ else
59
+ set_properties(properties, false)
60
+ end
61
+ end
62
+
63
+ # Overwrite properties with those passed.
64
+ # - properties: properties as a hash.
65
+ def overwrite(properties)
66
+ set_properties(properties, true)
67
+ end
68
+
69
+ # Extend with properties of parent build.
70
+ # - properties: properties of parent build.
71
+ def extend(properties)
72
+ for name in properties.expressions.keys
73
+ @expressions[name] = properties.expressions[name] if
74
+ !@expressions.include?(name)
75
+ end
76
+ end
77
+
78
+ # Evaluate properties in context.
79
+ # - context: build context where properties must be evaluated.
80
+ def evaluate(context)
81
+ for name in @expressions.keys
82
+ Thread.current[:stack] = []
83
+ evaluate_property(name, context) if !@values[name]
84
+ end
85
+ Thread.current[:stack] = nil
86
+ end
87
+
88
+ # Evaluate a given property.
89
+ # - name: the name of the property to evaluate.
90
+ # - context: the context in which the property must be evaluated.
91
+ # - stack: the stack of evaluated objects.
92
+ def evaluate_property(name, context)
93
+ stack = Thread.current[:stack]
94
+ error "Unknown property '#{name}'" if !@expressions.keys.include?(name)
95
+ error "Circular properties: #{stack.join(', ')}" if stack.include?(name)
96
+ begin
97
+ stack.push(name)
98
+ value = context.evaluate_object(@expressions[name])
99
+ stack.pop
100
+ @values[name] = value
101
+ context.set_property(name, value, true)
102
+ rescue
103
+ error "Error evaluating property '#{name}': #{$!}"
104
+ end
105
+ value
106
+ end
107
+
108
+ private
109
+
110
+ # Set default properties.
111
+ def defaults
112
+ set(:base, @build.base, false)
113
+ set(:here, @build.here, false)
114
+ end
115
+
116
+ # Set properties.
117
+ # - properties: properties as a hash.
118
+ # - overwrite: tells if we can overwrite existing properties.
119
+ def set_properties(properties, overwrite)
120
+ error "Properties must be a hash" if not properties.kind_of?(Hash)
121
+ for name in properties.keys
122
+ expression = properties[name]
123
+ set(name, expression, overwrite)
124
+ end
125
+ end
126
+
127
+ # Set a property.
128
+ # - name: property name (as a string or symbol).
129
+ # - expression: property expression (as a string) to be evaluated in
130
+ # context to get value.
131
+ # - overwrite: tells if we can overwrite existing properties.
132
+ def set(name, expression, overwrite)
133
+ error "Property '#{name}' collides with a function of the context" if
134
+ Bee::Context.method_defined?(name) or
135
+ Bee::Context.private_method_defined?(name)
136
+ return if expression == nil
137
+ name = name.to_sym
138
+ error "Duplicate property definition: '#{name}'" if
139
+ not overwrite and @expressions.has_key?(name)
140
+ @expressions[name] = expression
141
+ end
142
+
143
+ end
144
+
145
+ end
@@ -0,0 +1,287 @@
1
+ # Copyright 2006-2010 Michel Casabianca <michel.casabianca@gmail.com>
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 core classes.
16
+ module Bee
17
+
18
+ # Class for a target. It is built from the YAML build file and manages a
19
+ # target, in particular, tasks execution.
20
+ class Target
21
+
22
+ include Bee::Util::BuildErrorMixin
23
+ include Bee::Util::HashCheckerMixin
24
+
25
+ # Target key.
26
+ KEY = 'target'
27
+ # Target entry description.
28
+ DESCRIPTION = {
29
+ 'target' => :mandatory,
30
+ 'depends' => :optional,
31
+ 'description' => :optional,
32
+ 'script' => :optional}
33
+
34
+ # Targets for build.
35
+ attr_accessor :targets
36
+ # Name of the target.
37
+ attr_reader :name
38
+ # Target dependencies.
39
+ attr_accessor :depends
40
+ # Target description.
41
+ attr_accessor :description
42
+ # Script that run in the target.
43
+ attr_reader :script
44
+
45
+ # Constructor.
46
+ # - object: object for target, resulting from YAML parsing.
47
+ # - targets: build targets.
48
+ def initialize(object, targets)
49
+ check_hash(object, Target::DESCRIPTION)
50
+ @targets = targets
51
+ @name = object[Target::KEY]
52
+ error "Target name cannot be 'null'" if @name == nil
53
+ @depends = object['depends']||[]
54
+ @depends = Array(@depends)
55
+ @description = object['description']
56
+ @script = object['script']
57
+ @script = [@script] if @script.kind_of?(String)
58
+ @script = [] if not @script
59
+ end
60
+
61
+ # Run target.
62
+ # - dry: tells if we run in dry mode. Defaults to false.
63
+ def run(dry=false)
64
+ current_dir = Dir.pwd
65
+ begin
66
+ for depend in @depends
67
+ @targets.run_target(depend, dry)
68
+ end
69
+ @targets.build.listener.target(self) if
70
+ @targets.build.listener and @targets.is_last(self)
71
+ run_block(@script, dry)
72
+ ensure
73
+ Dir.chdir(current_dir)
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ # Run a task.
80
+ # - task: the task to run.
81
+ # - dry: whether to just print.
82
+ def run_task(task, dry=false)
83
+ @targets.build.listener.task(task) if @targets.build.listener
84
+ case task
85
+ when String
86
+ # shell script
87
+ run_shell(task, dry)
88
+ when Hash
89
+ if task.keys.length > 1
90
+ # construct
91
+ run_construct(task, dry)
92
+ else
93
+ if task.key?('rb')
94
+ # ruby script
95
+ script = task['rb']
96
+ run_ruby(script, dry)
97
+ elsif task.key?('sh')
98
+ # shell script
99
+ script = task['sh']
100
+ run_shell(script, dry)
101
+ elsif task.key?('super')
102
+ # call super target
103
+ targets.call_super(self, dry)
104
+ else
105
+ # must be a task
106
+ run_bee_task(task, dry)
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ # Run a given shell script.
113
+ # - script: the scrip to run.
114
+ def run_shell(script, dry=false)
115
+ @listener.task(script) if @listener
116
+ return if dry
117
+ evaluated_script = @targets.build.context.evaluate_object(script)
118
+ if evaluated_script != ''
119
+ system(evaluated_script)
120
+ error "Script exited with value #{$?}" if $? != 0
121
+ end
122
+ end
123
+
124
+ # Run a given shell script.
125
+ # - script: the scrip to run.
126
+ # - dry: tells if we run in dry mode.
127
+ def run_ruby(script, dry=false)
128
+ @listener.task(script) if @listener
129
+ return if dry
130
+ begin
131
+ @targets.build.context.evaluate_script(script)
132
+ rescue Exception
133
+ error "Error running Ruby script: #{$!}"
134
+ end
135
+ end
136
+
137
+ # Run a given bee task.
138
+ # - task: task to run as a Hash.
139
+ def run_bee_task(task, dry=false)
140
+ @listener.task(task) if @listener
141
+ return if dry
142
+ @targets.build.package_manager.run_task(task)
143
+ end
144
+
145
+ # Run a given construct.
146
+ # - construct: construct to run as a Hash.
147
+ def run_construct(construct, dry=false)
148
+ @listener.task(construct) if @listener
149
+ return if dry
150
+ # if construct
151
+ if construct.keys.include?('if')
152
+ construct_if(construct, dry)
153
+ # while construct
154
+ elsif construct.keys.include?('while')
155
+ construct_while(construct, dry)
156
+ # for construct
157
+ elsif construct.keys.include?('for')
158
+ construct_for(construct, dry)
159
+ # try construct
160
+ elsif construct.keys.include?('try')
161
+ construct_try(construct, dry)
162
+ else
163
+ error "Unknown construct '#{construct.keys.join('-')}'"
164
+ end
165
+ end
166
+
167
+ # Run if construct.
168
+ # - task: the construct as a hash.
169
+ # - dry: tells if we run in dry mode.
170
+ def construct_if(task, dry)
171
+ # test entries
172
+ error "If-then-else construct must include 'then' entry" if
173
+ not task.keys.include?('then')
174
+ unknown_keys = task.keys - ['if', 'then', 'else']
175
+ error "If-then-else construct may only include 'if', 'then' and 'else' entries" if
176
+ unknown_keys.length > 0
177
+ error "If entry in if-then-else construct must be a string" if
178
+ not task['if'].kind_of?(String)
179
+ error "Then entry in if-then-else construct must be a list" if
180
+ not task['then'].kind_of?(Array)
181
+ error "Else entry in if-then-else construct must be a list" if
182
+ task['else'] and not task['else'].kind_of?(Array)
183
+ # evaluate condition in the build context
184
+ if evaluate(task['if'])
185
+ run_block(task['then'], dry)
186
+ else
187
+ run_block(task['else'], dry) if task['else']
188
+ end
189
+ end
190
+
191
+ # Run while construct.
192
+ # - task: the construct as a hash.
193
+ # - dry: tells if we run in dry mode.
194
+ def construct_while(task, dry)
195
+ # test entries
196
+ error "While-do construct must include 'do' entry" if
197
+ not task.keys.include?('do')
198
+ unknown_keys = task.keys - ['while', 'do']
199
+ error "While-do construct may only include 'while' and 'do' entries" if
200
+ unknown_keys.length > 0
201
+ error "While entry in while-do construct must be a string" if
202
+ not task['while'].kind_of?(String)
203
+ error "Do entry in while-do construct must be a list" if
204
+ not task['do'].kind_of?(Array)
205
+ # evaluate condition in the build context
206
+ while evaluate(task['while'])
207
+ run_block(task['do'], dry)
208
+ end
209
+ end
210
+
211
+ # Run for construct.
212
+ # - task: the construct as a hash.
213
+ # - dry: tells if we run in dry mode.
214
+ def construct_for(task, dry)
215
+ # test entries
216
+ error "For-in-do construct must include 'in' and 'do' entries" if
217
+ not task.keys.include?('in') or not task.keys.include?('do')
218
+ unknown_keys = task.keys - ['for', 'in', 'do']
219
+ error "For-in-do construct may only include 'for', 'in' and 'do' entries" if
220
+ unknown_keys.length > 0
221
+ error "For entry in for-in-do construct must be a string" if
222
+ not task['for'].kind_of?(String)
223
+ error "In entry in for-in-do construct must be a list or a string" if
224
+ not task['in'].kind_of?(Enumerable) and not task['in'].kind_of?(String)
225
+ error "Do entry in for-in-do construct must be a list" if
226
+ not task['do'].kind_of?(Array)
227
+ # iterate over list
228
+ if task['in'].kind_of?(String)
229
+ enumerable = evaluate(@targets.build.context.
230
+ evaluate_object(task['in']))
231
+ else
232
+ enumerable = task['in']
233
+ end
234
+ for element in enumerable
235
+ begin
236
+ @targets.build.context.set_property(task['for'], element)
237
+ rescue
238
+ error "Error setting property '#{task['for']}'"
239
+ end
240
+ run_block(task['do'], dry)
241
+ end
242
+ end
243
+
244
+ # Run try-catch construct.
245
+ # - task: the construct as a hash.
246
+ # - dry: tells if we run in dry mode.
247
+ def construct_try(task, dry)
248
+ # test entries
249
+ error "Try-catch construct must include 'catch' entry" if
250
+ not task.keys.include?('catch')
251
+ unknown_keys = task.keys - ['try', 'catch']
252
+ error "Try-catch construct may only include 'try' and 'catch' entries" if
253
+ unknown_keys.length > 0
254
+ error "Try entry in try-catch construct must be a list" if
255
+ not task['try'].kind_of?(Array)
256
+ error "Catch entry in try-catch construct must be a list" if
257
+ not task['catch'].kind_of?(Array)
258
+ # try and catch errors
259
+ begin
260
+ run_block(task['try'], dry)
261
+ rescue
262
+ @targets.build.listener.recover if @targets.build.listener
263
+ run_block(task['catch'], dry)
264
+ end
265
+ end
266
+
267
+ # Run a block, that is a list of tasks.
268
+ # - block: the block to run as a list of tasks.
269
+ # - dry: tells if we run in dry mode.
270
+ def run_block(block, dry)
271
+ for task in block
272
+ run_task(task, dry)
273
+ end
274
+ end
275
+
276
+ # Evaluate a given expression and raise a BuildError if an error happens.
277
+ def evaluate(expression)
278
+ begin
279
+ return @targets.build.context.evaluate_script(expression)
280
+ rescue
281
+ error "Error evaluating expression: #{$!}"
282
+ end
283
+ end
284
+
285
+ end
286
+
287
+ end