bee 0.4.0 → 0.5.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/lib/bee_console.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2006-2007 Michel Casabianca <michel.casabianca@gmail.com>
1
+ # Copyright 2006-2008 Michel Casabianca <michel.casabianca@gmail.com>
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@ require 'bee'
16
16
  require 'bee_task'
17
17
  require 'bee_util'
18
18
  require 'getoptlong'
19
+ require 'yaml'
19
20
 
20
21
  # Module for Bee stuff.
21
22
  module Bee
@@ -24,17 +25,22 @@ module Bee
24
25
 
25
26
  # Command line help.
26
27
  HELP = 'Usage: bee [options] [targets]
27
- -h Print help about usage and exit.
28
- -b Print help about build and exit.
29
- -k task Print help about tasks in a package (writing "foo.?") or a
30
- given one (writing "foo.bar") and exit.
31
- -t Write template build file on disk (use -f to write build file
32
- in another one than default "build.yml").
33
- -v Enable verbose mode.
34
- -s style Define style for output (see documentation).
35
- -f file Build file to run (defaults to "build.yml").
36
- -r Look for build file recursively up in file system.
37
- targets Targets to run (default target if omitted).'
28
+ -V Print version and exit.
29
+ -h Print help about usage and exit.
30
+ -b Print help about build and exit.
31
+ -n Don\'t actually run any commands; just print them.
32
+ -k task Print help about tasks in a package (writing "foo.?") or a
33
+ given one (writing "foo.bar") and exit.
34
+ -e egg Print help about templates in a given package (writing
35
+ "foo.?") or a given one (writing "foo.bar") and exit.
36
+ -p name=value Set a named property with a given value.
37
+ -t egg Run a given egg to generate a template project.
38
+ -v Enable verbose mode.
39
+ -s style Define style for output (see documentation).
40
+ -f file Build file to run (defaults to "build.yml").
41
+ -r Look for build file recursively up in file system.
42
+ -l Print bee logo on console.
43
+ targets Targets to run (default target if omitted).'
38
44
  # Name for default build file.
39
45
  DEFAULT_BUILD_FILE = 'build.yml'
40
46
  # Exit value on error parsing command line
@@ -45,36 +51,55 @@ targets Targets to run (default target if omitted).'
45
51
  EXIT_UNKNOWN_ERROR = 3
46
52
  # Bee options environment variable.
47
53
  BEE_OPT_ENV = 'BEEOPT'
48
- # README file where copyright is extracted.
49
- README_FILE = 'README'
54
+ # Bee text logo (generated with http://www.network-science.de/ascii/)
55
+ BEE_LOGO = <<'EOF'
56
+ _
57
+ | |__ ___ ___
58
+ _____ | '_ \ / _ \/ _ \ _____ _____ _____ _____ _____ _____ _____ _____
59
+ |_____| | |_) | __/ __/ |_____|_____|_____|_____|_____|_____|_____|_____|
60
+ |_.__/ \___|\___| 0.5.0 http://bee.rubyforge.org
61
+
62
+ EOF
50
63
 
51
64
  # Parse command line and return parsed arguments.
52
65
  def self.parse_command_line
66
+ version = false
53
67
  help = false
54
68
  help_build = false
55
69
  help_task = false
70
+ help_template = false
71
+ properties = {}
56
72
  task = nil
57
- template = false
73
+ dry_run = false
74
+ template = nil
58
75
  verbose = false
59
76
  style = nil
60
77
  file = DEFAULT_BUILD_FILE
61
78
  recursive = false
79
+ logo = false
62
80
  targets = []
63
81
  # read options in BEEOPT environment variable
64
82
  options = ENV[BEE_OPT_ENV]
65
83
  options.split(' ').reverse.each { |option| ARGV.unshift(option) } if
66
84
  options
67
85
  # parse command line arguments
68
- opts = GetoptLong.new(['--help', '-h', GetoptLong::NO_ARGUMENT],
86
+ opts = GetoptLong.new(['--version', '-V', GetoptLong::NO_ARGUMENT],
87
+ ['--help', '-h', GetoptLong::NO_ARGUMENT],
69
88
  ['--help-build', '-b', GetoptLong::NO_ARGUMENT],
70
89
  ['--help-task','-k', GetoptLong::REQUIRED_ARGUMENT],
71
- ['--template', '-t', GetoptLong::NO_ARGUMENT ],
90
+ ['--help-template','-e', GetoptLong::REQUIRED_ARGUMENT],
91
+ ['--dry-run', '-n', GetoptLong::NO_ARGUMENT],
92
+ ['--property', '-p', GetoptLong::REQUIRED_ARGUMENT],
93
+ ['--template', '-t', GetoptLong::REQUIRED_ARGUMENT],
72
94
  ['--verbose', '-v', GetoptLong::NO_ARGUMENT],
73
95
  ['--style', '-s', GetoptLong::REQUIRED_ARGUMENT],
74
96
  ['--file', '-f', GetoptLong::REQUIRED_ARGUMENT],
75
- ['--recursive', '-r', GetoptLong::NO_ARGUMENT])
97
+ ['--recursive', '-r', GetoptLong::NO_ARGUMENT],
98
+ ['--logo', '-l', GetoptLong::NO_ARGUMENT])
76
99
  opts.each do |opt, arg|
77
100
  case opt
101
+ when '--version'
102
+ version = true
78
103
  when '--help'
79
104
  help = true
80
105
  when '--help-build'
@@ -82,8 +107,17 @@ targets Targets to run (default target if omitted).'
82
107
  when '--help-task'
83
108
  help_task = true
84
109
  task = arg
110
+ when '--help-template'
111
+ help_template = true
112
+ template = arg
113
+ when '--dry-run'
114
+ dry_run = true
115
+ verbose = true
116
+ when '--property'
117
+ name, value = parse_property(arg)
118
+ properties[name] = value
85
119
  when '--template'
86
- template = true
120
+ template = arg
87
121
  when '--verbose'
88
122
  verbose = true
89
123
  when '--style'
@@ -92,48 +126,69 @@ targets Targets to run (default target if omitted).'
92
126
  file = arg
93
127
  when '--recursive'
94
128
  recursive = true
129
+ when '--logo'
130
+ logo = true
95
131
  end
96
132
  end
97
- targets = ARGV
98
- return [help, help_build, help_task, task, template, verbose, style,
99
- file, recursive, targets]
133
+ targets = Array.new(ARGV)
134
+ return [version, help, help_build, help_task, help_template, task,
135
+ properties, dry_run, template, verbose, style, file, recursive,
136
+ logo, targets]
137
+ end
138
+
139
+ # Parse a command line property.
140
+ # - property: property definition as "name=value".
141
+ # Return: name and value of the property.
142
+ def self.parse_property(property)
143
+ begin
144
+ index = property.index('=')
145
+ raise "No = sign" if not index
146
+ name = property[0..index-1]
147
+ value = YAML::load(property[index+1..-1])
148
+ return name, value
149
+ rescue
150
+ raise "Error parsing property '#{property}': #{$!}"
151
+ end
100
152
  end
101
153
 
102
154
  # Start build from command line.
103
155
  def self.start_command_line
104
156
  STDOUT.sync = true
105
157
  begin
106
- help, help_build, help_task, task, template, verbose, style, file,
107
- recursive, targets = parse_command_line
158
+ version, help, help_build, help_task, help_template, task,
159
+ properties, dry_run, template, verbose, style, file, recursive,
160
+ logo, targets = parse_command_line
108
161
  rescue
109
162
  puts "ERROR: parsing command line (type 'bee -h' for help)"
110
163
  exit(EXIT_PARSING_CMDLINE)
111
164
  end
112
165
  formatter = Formatter.new(style)
113
166
  begin
114
- if help
115
- readme = File.join(File.dirname(__FILE__), '..', README_FILE)
116
- if File.exists?(readme) and File.file?(readme) and
117
- File.readable?(readme)
118
- copyright = File.read(readme).strip!.split("\n")[-1]
119
- puts copyright
120
- end
167
+ if logo
168
+ puts BEE_LOGO
169
+ end
170
+ if version
171
+ copyright = Bee::Util::copyright
172
+ puts copyright if copyright
173
+ elsif help
121
174
  puts HELP
122
175
  elsif help_build
123
- build = Build.load(file, recursive)
176
+ build = Build.load(file, recursive, properties)
177
+ build.evaluate_properties
124
178
  puts formatter.help_build(build)
125
179
  elsif help_task
126
180
  puts formatter.help_task(task)
181
+ elsif help_template
182
+ puts formatter.help_template(template)
127
183
  elsif template
128
- puts "Writing build template in file '#{file}'..."
129
- raise Bee::Util::BuildError.new("Build file '#{file}' already exists") if
130
- File.exists?(file)
131
- File.open(file, 'w') { |file| file.write(BUILD_TEMPLATE) }
132
- puts formatter.format_success("OK")
184
+ file = Bee::Util::find_template(template)
185
+ listener = Listener.new(formatter, verbose)
186
+ build = Build.load(file, false, properties)
187
+ build.run(targets, listener, dry_run)
133
188
  else
134
189
  listener = Listener.new(formatter, verbose)
135
- build = Build.load(file, recursive)
136
- build.run(targets, listener)
190
+ build = Build.load(file, recursive, properties)
191
+ build.run(targets, listener, dry_run)
137
192
  end
138
193
  rescue Bee::Util::BuildError => e
139
194
  puts "#{formatter.format_error('ERROR')}: #{$!}"
@@ -192,8 +247,6 @@ targets Targets to run (default target if omitted).'
192
247
  DEFAULT_STYLE = {
193
248
  :line_character => '-'
194
249
  }
195
- # Default line length.
196
- DEFAULT_LINE_LENGTH = 80
197
250
  # Short style keys for command line
198
251
  SHORT_STYLE_KEYS = {
199
252
  'lc' => 'line_character',
@@ -279,7 +332,10 @@ targets Targets to run (default target if omitted).'
279
332
  help = ''
280
333
  # print build name and description
281
334
  if build.name
282
- help << "- Build: #{build.name.inspect}\n"
335
+ help << "- Build: #{build.name}\n"
336
+ end
337
+ if build.extends
338
+ help << " Extends: #{build.extends}\n"
283
339
  end
284
340
  if build.description
285
341
  help << format_description('Description', build.description, 2, false)
@@ -293,28 +349,63 @@ targets Targets to run (default target if omitted).'
293
349
  end
294
350
  end
295
351
  # print build targets
296
- if build.targets.length > 0
352
+ description = build.targets.description
353
+ if description.length > 0
297
354
  help << "- Targets:\n"
298
- for target in build.targets.values.sort { |a, b| a.name <=> b.name }
299
- help << format_description(target.name, target.description, 2)
355
+ for name in description.keys.sort
356
+ help << format_description(name, description[name], 2)
300
357
  end
301
358
  end
302
359
  # print default target
303
- help << "- Default: #{build.default}"
360
+ help << "- Default: #{build.default}\n"
304
361
  return help.strip
305
362
  end
306
363
 
307
364
  # Return help about task(s).
308
365
  # - task: task to print help about (all tasks if nil).
309
366
  def help_task(task)
310
- task = '*' if task == nil or task.length == 0
367
+ task = '?' if task == nil or task.length == 0
311
368
  package_manager = Bee::Task::PackageManager.new(nil)
312
369
  methods = package_manager.help_task(task)
313
370
  help = ''
314
- for method in methods.keys
371
+ for method in methods.keys.sort
372
+ text = methods[method].strip
315
373
  help << format_title(method)
316
374
  help << "\n"
317
- help << methods[method].strip
375
+ help << text
376
+ help << "\n"
377
+ if text =~ /Alias for \w+/
378
+ alias_method = text.scan(/Alias for (\w+)/).flatten[0]
379
+ help << "\n"
380
+ help << package_manager.help_task(alias_method)[alias_method].strip
381
+ help << "\n"
382
+ end
383
+ end
384
+ return help
385
+ end
386
+
387
+ # Return help about template(s).
388
+ # - template: template to print help about (all templates if nil).
389
+ def help_template(template)
390
+ templates = Bee::Util::search_templates(template)
391
+ help = ''
392
+ for name in templates.keys
393
+ build = YAML::load(File.read(templates[name]))
394
+ properties = nil
395
+ for entry in build
396
+ properties = entry['properties'] if entry['properties']
397
+ end
398
+ description = 'No description found'
399
+ if properties
400
+ for property in properties
401
+ if property.keys == ['description']
402
+ description = property['description']
403
+ end
404
+ end
405
+ end
406
+ help << format_title(name)
407
+ help << "\n"
408
+ help << description
318
409
  help << "\n"
319
410
  end
320
411
  return help
@@ -400,15 +491,15 @@ targets Targets to run (default target if omitted).'
400
491
  end
401
492
  return string
402
493
  end
403
-
494
+
404
495
  # Format a title.
405
496
  # - title: title to format.
406
497
  def format_title(title)
407
- length = @style[:line_length] ||
408
- Bee::Util::term_width ||
409
- DEFAULT_LINE_LENGTH
498
+ length = @style[:line_length] || Bee::Util::term_width
410
499
  right = ' ' + @style[:line_character]*2
411
- left = @style[:line_character]*(length - (title.length + 4)) + ' '
500
+ size = length - (title.length + 4)
501
+ size = 2 if size <= 0
502
+ left = @style[:line_character]*size + ' '
412
503
  line = left + title + right
413
504
  # apply style
414
505
  formatted = style(line,
@@ -451,24 +542,15 @@ targets Targets to run (default target if omitted).'
451
542
 
452
543
  # Called when build is started.
453
544
  # - build: the build object.
454
- def build_started(build)
545
+ def build_started(build, dry_run)
455
546
  @start_time = Time.now
456
547
  @end_time = nil
457
548
  @duration = nil
458
549
  @success = nil
459
550
  @last_target = nil
460
551
  @last_task = nil
461
- puts "Starting build '#{build.file}'..." if @verbose
462
- end
463
-
464
- # Called when build is finished.
465
- # - build: the build object.
466
- def build_finished(build)
467
- @end_time = Time.now
468
- @duration = @end_time - @start_time
469
- @success = true
470
- puts "Built in #{@duration} s" if @verbose
471
- puts @formatter.format_success('OK')
552
+ build_type = dry_run ? "dry run of" : "build"
553
+ puts "Starting #{build_type} '#{build.file}'..." if @verbose
472
554
  end
473
555
 
474
556
  # Called when a target is met.
@@ -486,22 +568,28 @@ targets Targets to run (default target if omitted).'
486
568
  puts @formatter.format_task(task) if @verbose
487
569
  end
488
570
 
571
+ # Called when build is finished.
572
+ # - build: the build object.
573
+ def build_finished(build, dry_run)
574
+ duration unless dry_run
575
+ @success = true
576
+ puts @formatter.format_success('OK')
577
+ end
578
+
489
579
  # Called when an error was raised.
490
580
  # - exception: raised exception.
491
581
  def error(exception)
492
- @end_time = Time.now
493
- @duration = @end_time - @start_time
582
+ duration
494
583
  @success = false
495
- puts "Built in #{@duration} s" if @verbose
496
584
  message = ''
497
585
  message << "In target '#{@last_target.name}'" if @last_target
498
586
  message << ", in task:\n#{@formatter.format_task(@last_task)}\n" if
499
587
  @last_task
500
588
  message << ': ' if @last_target and not @last_task
501
589
  message << exception.to_s
502
- puts "#{@formatter.format_error('ERROR')}: #{message}"
590
+ raise Bee::Util::BuildError.new(message)
503
591
  end
504
-
592
+
505
593
  # Print text on the console.
506
594
  # - text: text to print.
507
595
  def print(text)
@@ -514,30 +602,22 @@ targets Targets to run (default target if omitted).'
514
602
  Kernel.puts(text)
515
603
  end
516
604
 
517
- end
605
+ # Get input string.
606
+ # Return string entered by the user.
607
+ def gets
608
+ return STDIN.gets
609
+ end
610
+
611
+ private
518
612
 
519
- # Build template.
520
- BUILD_TEMPLATE =
521
- '# Template build file
522
- - build: template
523
- description: Template build file.
524
- default: hello
525
-
526
- # Build properties
527
- - properties:
528
- - user: "#{ENV[\'USER\']}"
529
-
530
- # Build targets
531
- - target: capitalize
532
- description: Capitalize user name
533
- script:
534
- - rb: "who = user.capitalize"
535
-
536
- - target: hello
537
- depends: capitalize
538
- description: Print greatings.
539
- script:
540
- - print: "Hello #{who}!"'
613
+ # Compute build duration and print it if verbose.
614
+ def duration
615
+ @end_time = Time.now
616
+ @duration = @end_time - @start_time
617
+ puts "Built in #{@duration} s" if @verbose
618
+ end
619
+
620
+ end
541
621
 
542
622
  end
543
623
 
data/lib/bee_task.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2006-2007 Michel Casabianca <michel.casabianca@gmail.com>
1
+ # Copyright 2006-2008 Michel Casabianca <michel.casabianca@gmail.com>
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -20,8 +20,10 @@ module Bee
20
20
  # Module for bee tasks.
21
21
  module Task
22
22
 
23
- # Base class for task package. Provides methods to access build context
24
- # (to set and get properties and evaluate an object in this context).
23
+ # Base class for task package. Provides methods to print output using
24
+ # the build formatter and a method to check task parameters. Furthermore,
25
+ # this base class extends MethodInfoBase which provides methods
26
+ # comments, for autodocumentation purpose.
25
27
  class Package < Bee::Util::MethodInfoBase
26
28
 
27
29
  include Bee::Util::BuildErrorMixin
@@ -34,22 +36,116 @@ module Bee
34
36
 
35
37
  protected
36
38
 
37
- # Check a task parameters. Raise a RuntimeError with explanation message
39
+ # Check task parameters. Raise a RuntimeError with explanation message
38
40
  # if a mandatory parameter is missing or an unknown parameter was found.
39
41
  # - params: task parameters as a Hash.
40
- # - description: parameters description as a Hash associating a parameter
41
- # name with symbol :mandatory or :optional.
42
- def check_task_parameters(params, description)
43
- error "Parameters must be a Hash" unless params.kind_of?(Hash)
42
+ # - description: parameters description as a Hash with following keys:
43
+ # :mandatory telling if the parameter is mandatory (true or false),
44
+ # :type which is the class name of the parameter and
45
+ # :default for default value.
46
+ def check_parameters(params, description)
47
+ task = caller[0].match(/`(.*)'/)[1]
48
+ error "'#{task}' parameters must be a hash" unless params.kind_of?(Hash)
44
49
  for param in description.keys
45
- error "Missing mandatory parameter '#{param}'" unless
46
- params[param] or description[param] == :optional
50
+ error "#{task} '#{param}' parameter is mandatory" unless
51
+ params[param.to_s] or description[param][:mandatory] == false
52
+ if params[param.to_s]
53
+ case description[param][:type]
54
+ when :string
55
+ error "#{task} '#{param}' parameter must be a string" unless
56
+ params[param.to_s].kind_of?(String)
57
+ when :integer
58
+ error "#{task} '#{param}' parameter must be an integer" unless
59
+ params[param.to_s].kind_of?(Integer)
60
+ when :float
61
+ error "#{task} '#{param}' parameter must be a float" unless
62
+ params[param.to_s].kind_of?(Float)
63
+ when :number
64
+ error "#{task} '#{param}' parameter must be a number" unless
65
+ params[param.to_s].kind_of?(Numeric)
66
+ when :boolean
67
+ error "#{task} '#{param}' parameter must be a boolean" unless
68
+ params[param.to_s] == true or params[param] == false
69
+ when :array
70
+ error "#{task} '#{param}' parameter must be an array" unless
71
+ params[param.to_s].kind_of?(Array)
72
+ when :string_or_array
73
+ error "#{task} '#{param}' parameter must be a string or an array" unless
74
+ params[param.to_s].kind_of?(String) or params[param.to_s].kind_of?(Array)
75
+ when :string_or_integer
76
+ error "#{task} '#{param}' parameter must be a string or an integer" unless
77
+ params[param.to_s].kind_of?(String) or params[param.to_s].kind_of?(Integer)
78
+ when :hash
79
+ error "#{task} '#{param}' parameter must be a hash" unless
80
+ params[param.to_s].kind_of?(Hash)
81
+ else
82
+ error "Unknown parameter type '#{description[param][:type]}'"
83
+ end
84
+ params[param.to_sym] = params[param.to_s]
85
+ else
86
+ params[param.to_sym] = description[param][:default] if
87
+ description[param][:default]
88
+ params[param.to_s] = description[param][:default] if
89
+ description[param][:default]
90
+ end
47
91
  end
48
92
  for param in params.keys
49
- error "Unknown parameter '#{param}'" if not description.key?(param)
93
+ error "Unknown parameter '#{param}'" if
94
+ not (description.key?(param) or description.key?(param.to_sym))
50
95
  end
51
96
  end
52
97
 
98
+ # Utility method to find and filter files.
99
+ # - root: root directory for files to search.
100
+ # - includes: list of globs for files to include in search.
101
+ # - excludes: list of globs for files to exclude from search.
102
+ # - dotmatch: tells if joker matches dot files.
103
+ # Return: the list of found files (no directories included).
104
+ def filter_files(includes, excludes, root, dotmatch=true)
105
+ error "includes must be a glob or a list of globs" unless
106
+ !includes or includes.kind_of?(String) or includes.kind_of?(Array)
107
+ error "excludes must be a glob or a list of globs" unless
108
+ !excludes or excludes.kind_of?(String) or excludes.kind_of?(Array)
109
+ error "root must be an existing directory" unless
110
+ !root or File.exists?(root)
111
+ current_dir = Dir.pwd
112
+ begin
113
+ if dotmatch
114
+ options = File::FNM_PATHNAME | File::FNM_DOTMATCH
115
+ else
116
+ options = File::FNM_PATHNAME
117
+ end
118
+ Dir.chdir(root) if root
119
+ included = []
120
+ includes = '**/*' if not includes
121
+ for include in includes
122
+ error "includes must be a glob or a list of globs" unless
123
+ include.kind_of?(String)
124
+ # should expand directories ?
125
+ # include = "#{include}/**/*" if File.directory?(include)
126
+ entries = Dir.glob(include, options)
127
+ included += entries if entries
128
+ end
129
+ included.uniq!
130
+ if excludes
131
+ included.reject! do |file|
132
+ rejected = false
133
+ for exclude in excludes
134
+ if File.fnmatch?(exclude, file, options)
135
+ rejected = true
136
+ break
137
+ end
138
+ end
139
+ rejected
140
+ end
141
+ end
142
+ included.reject! { |file| File.directory?(file) }
143
+ return included
144
+ ensure
145
+ Dir.chdir(current_dir)
146
+ end
147
+ end
148
+
53
149
  # Print text on the console.
54
150
  # - text: text to print.
55
151
  def print(text)
@@ -69,6 +165,16 @@ module Bee
69
165
  Kernel.puts(text)
70
166
  end
71
167
  end
168
+
169
+ # Prompts the user for a string.
170
+ # Return the user input string.
171
+ def gets
172
+ if @build.listener
173
+ return @build.listener.gets
174
+ else
175
+ return Kernel::STDIN.gets
176
+ end
177
+ end
72
178
 
73
179
  end
74
180
 
@@ -126,14 +232,10 @@ module Bee
126
232
  # named after the package capitalized, in module Bee::Task.
127
233
  # - package: the package name.
128
234
  def load_package(package)
129
- if package
130
- package.downcase!
131
- script = "bee_task_#{package.downcase}"
132
- clazz = package.capitalize
133
- else
134
- script = 'bee_task'
135
- clazz = 'Default'
136
- end
235
+ package = 'default' if not package
236
+ package.downcase!
237
+ script = "bee_task_#{package}"
238
+ clazz = package.capitalize
137
239
  begin
138
240
  require script
139
241
  @packages[package] = Bee::Task.const_get(clazz).new(@build)