bee 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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)