bee 0.1.1 → 0.2.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/LICENSE +202 -0
- data/README +28 -4
- data/bin/bee +2 -2
- data/lib/bee.rb +34 -579
- data/lib/bee_console.rb +539 -0
- data/lib/bee_default_context.rb +131 -0
- data/test/{tc_bee_consoleformatter.rb → tc_bee_console_formatter.rb} +3 -3
- data/test/ts_bee.rb +2 -4
- metadata +11 -27
- data/bin/beedoc +0 -6
- data/doc/html/documentation.html +0 -444
- data/doc/html/quickstart.html +0 -108
- data/doc/png/style.png +0 -0
- data/doc/yml/menu.yml +0 -49
- data/doc/yml/releases.yml +0 -42
- data/doc/yml/todo.yml +0 -17
- data/lib/beedoc.rb +0 -464
data/lib/bee_console.rb
ADDED
@@ -0,0 +1,539 @@
|
|
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
|
+
module Console
|
19
|
+
|
20
|
+
require 'bee'
|
21
|
+
require 'getoptlong'
|
22
|
+
|
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
|
+
-k [task] Print help about all tasks or given one.
|
28
|
+
-t Write template build file on disk.
|
29
|
+
-v Enable verbose mode.
|
30
|
+
-s style Define style for output (see documentation).
|
31
|
+
-f file Build file to run (defaults to "build.yml").
|
32
|
+
targets Targets to run (default target if omitted).'
|
33
|
+
# Name for default build file.
|
34
|
+
DEFAULT_BUILD_FILE = 'build.yml'
|
35
|
+
# Exit value on error parsing command line
|
36
|
+
EXIT_PARSING_CMDLINE = 1
|
37
|
+
# Exit value on build error
|
38
|
+
EXIT_BUILD_ERROR = 2
|
39
|
+
# Exit value on unknown error
|
40
|
+
EXIT_UNKNOWN_ERROR = 3
|
41
|
+
|
42
|
+
# Parse command line and return parsed arguments.
|
43
|
+
def self.parse_command_line
|
44
|
+
help = false
|
45
|
+
help_build = false
|
46
|
+
help_task = false
|
47
|
+
task = nil
|
48
|
+
template = false
|
49
|
+
verbose = false
|
50
|
+
style = nil
|
51
|
+
file = DEFAULT_BUILD_FILE
|
52
|
+
targets = []
|
53
|
+
# read options in BEEOPT environment variable
|
54
|
+
options = ENV['BEEOPT']
|
55
|
+
options.split(' ').reverse.each { |option| ARGV.unshift(option) } if
|
56
|
+
options
|
57
|
+
# parse command line arguments
|
58
|
+
opts = GetoptLong.new(['--help', '-h', GetoptLong::NO_ARGUMENT],
|
59
|
+
['--help-build', '-b', GetoptLong::NO_ARGUMENT],
|
60
|
+
['--help-task','-k', GetoptLong::OPTIONAL_ARGUMENT],
|
61
|
+
['--template', '-t', GetoptLong::NO_ARGUMENT ],
|
62
|
+
['--verbose', '-v', GetoptLong::NO_ARGUMENT],
|
63
|
+
['--style', '-s', GetoptLong::REQUIRED_ARGUMENT],
|
64
|
+
['--file', '-f', GetoptLong::REQUIRED_ARGUMENT])
|
65
|
+
opts.each do |opt, arg|
|
66
|
+
case opt
|
67
|
+
when '--help'
|
68
|
+
help = true
|
69
|
+
when '--help-build'
|
70
|
+
help_build = true
|
71
|
+
when '--help-task'
|
72
|
+
help_task = true
|
73
|
+
task = arg
|
74
|
+
when '--template'
|
75
|
+
template = true
|
76
|
+
when '--verbose'
|
77
|
+
verbose = true
|
78
|
+
when '--style'
|
79
|
+
style = arg
|
80
|
+
when '--file'
|
81
|
+
file = arg
|
82
|
+
end
|
83
|
+
end
|
84
|
+
targets = ARGV
|
85
|
+
return [help, help_build, help_task, task, template, verbose, style,
|
86
|
+
file, targets]
|
87
|
+
end
|
88
|
+
|
89
|
+
# Start build from command line.
|
90
|
+
def self.start_command_line
|
91
|
+
STDOUT.sync = true
|
92
|
+
begin
|
93
|
+
help, help_build, help_task, task, template, verbose, style, file,
|
94
|
+
targets = parse_command_line
|
95
|
+
rescue
|
96
|
+
puts "ERROR: parsing command line (type 'bee -h' for help)"
|
97
|
+
exit(EXIT_PARSING_CMDLINE)
|
98
|
+
end
|
99
|
+
formatter = Formatter.new(style)
|
100
|
+
begin
|
101
|
+
if help
|
102
|
+
readme = File.join(File.dirname(__FILE__), '..', 'README')
|
103
|
+
if File.exists?(readme)
|
104
|
+
copyright = File.read(readme).strip!.split("\n")[-1]
|
105
|
+
puts copyright
|
106
|
+
end
|
107
|
+
puts HELP
|
108
|
+
elsif help_build
|
109
|
+
build = Build.new(file)
|
110
|
+
puts formatter.help_build(build)
|
111
|
+
elsif help_task
|
112
|
+
build = Build.new(file)
|
113
|
+
puts formatter.help_task(build, task)
|
114
|
+
elsif template
|
115
|
+
puts "Writing build template in file '#{file}'..."
|
116
|
+
raise BuildError.new("Build file '#{file}' already exists") if
|
117
|
+
File.exists?(file)
|
118
|
+
File.open(file, 'w') { |file| file.write(BUILD_TEMPLATE) }
|
119
|
+
puts formatter.format_success("OK")
|
120
|
+
else
|
121
|
+
listener = ConsoleListener.new(formatter, verbose)
|
122
|
+
build = Build.new(file)
|
123
|
+
build.run(targets, listener)
|
124
|
+
end
|
125
|
+
rescue BuildError
|
126
|
+
puts "#{formatter.format_error('ERROR')}: #{$!}"
|
127
|
+
exit(EXIT_BUILD_ERROR)
|
128
|
+
rescue SystemExit
|
129
|
+
# do nothing, exit in code
|
130
|
+
rescue Exception => e
|
131
|
+
puts "#{formatter.format_error('ERROR')}: #{$!}"
|
132
|
+
puts e.backtrace.join("\n")
|
133
|
+
exit(EXIT_UNKNOWN_ERROR)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
#########################################################################
|
138
|
+
# FORMATTER CLASS #
|
139
|
+
#########################################################################
|
140
|
+
|
141
|
+
# Class to format build output on console.
|
142
|
+
class Formatter
|
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
|
+
# - build: running build.
|
286
|
+
def help_build(build)
|
287
|
+
help = ''
|
288
|
+
if build.name
|
289
|
+
help << "- Build: #{build.name.inspect}\n"
|
290
|
+
end
|
291
|
+
if build.description
|
292
|
+
help << format_description('Description', build.description, 2, false)
|
293
|
+
end
|
294
|
+
if build.properties.keys.length > 0
|
295
|
+
help << "- Properties:\n"
|
296
|
+
for property in build.properties.keys.sort
|
297
|
+
help << " - #{property}: #{build.properties[property].inspect}\n"
|
298
|
+
end
|
299
|
+
end
|
300
|
+
if build.targets.length > 0
|
301
|
+
help << "- Targets:\n"
|
302
|
+
for target in build.targets.values.sort { |a, b| a.name <=> b.name }
|
303
|
+
help << format_description(target.name, target.description, 2)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
help << "- Default: #{build.default}"
|
307
|
+
return help.strip
|
308
|
+
end
|
309
|
+
|
310
|
+
# Return help about task(s).
|
311
|
+
# - build: running build.
|
312
|
+
# - task: task to print help about (all tasks if nil).
|
313
|
+
def help_task(build, task)
|
314
|
+
task = nil if task.length == 0
|
315
|
+
help = ''
|
316
|
+
if task
|
317
|
+
error "Task '#{task}' not loaded" if not build.tasks[task]
|
318
|
+
help << build.tasks[task]
|
319
|
+
else
|
320
|
+
for task in build.tasks.keys.sort
|
321
|
+
help << format_description(task, build.tasks[task], 0, true)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
return help
|
325
|
+
end
|
326
|
+
|
327
|
+
private
|
328
|
+
|
329
|
+
# Apply style to a string:
|
330
|
+
# - string: the string to apply style to.
|
331
|
+
# - style: style to apply on string.
|
332
|
+
# - foreground: foreground color for string.
|
333
|
+
# - background: background color for string.
|
334
|
+
def style(string, style, foreground, background)
|
335
|
+
# check style, foreground and background colors
|
336
|
+
error "Unknown style '#{style}'" unless
|
337
|
+
STYLES.member?(style) or not style
|
338
|
+
error "Unknown color '#{foreground}'" unless
|
339
|
+
COLORS.member?(foreground) or not foreground
|
340
|
+
error "Unknown color '#{background}'" unless
|
341
|
+
COLORS.member?(background) or not background
|
342
|
+
# if no style nor colors, return raw string
|
343
|
+
return string if not foreground and not background and not style
|
344
|
+
# insert style and colors in string
|
345
|
+
colorized = "\e["
|
346
|
+
colorized << "#{STYLE_CODES[style]};" if style
|
347
|
+
colorized << "#{FOREGROUND_COLOR_CODES[foreground]};" if foreground
|
348
|
+
colorized << "#{BACKGROUND_COLOR_CODES[background]};" if background
|
349
|
+
colorized = colorized[0..-2]
|
350
|
+
colorized << "m#{string}\e[#{STYLE_CODES[:reset]}m"
|
351
|
+
return colorized
|
352
|
+
end
|
353
|
+
|
354
|
+
# Get line length calling IOCTL. Return nil if call failed.
|
355
|
+
def line_length
|
356
|
+
begin
|
357
|
+
tiocgwinsz = 0x5413
|
358
|
+
string = [0, 0, 0, 0].pack('SSSS')
|
359
|
+
if $stdin.ioctl(tiocgwinsz, string) >= 0 then
|
360
|
+
rows, cols, xpixels, ypixels = string.unpack('SSSS')
|
361
|
+
return cols
|
362
|
+
end
|
363
|
+
rescue
|
364
|
+
return nil
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
# Parse style from command line. If error occurs parsing style, return
|
369
|
+
# nil (which means default style).
|
370
|
+
# - string: style to parse.
|
371
|
+
def parse_style_from_command_line(string)
|
372
|
+
return if not string
|
373
|
+
style = {}
|
374
|
+
begin
|
375
|
+
for pair in string.split(',')
|
376
|
+
key, value = pair.split(':')
|
377
|
+
key = SHORT_STYLE_KEYS[key] || key
|
378
|
+
key = key.to_sym
|
379
|
+
if key == :line_length
|
380
|
+
value = value.to_i
|
381
|
+
elsif key == :line_character
|
382
|
+
value = ' ' if not value or value.length == 0
|
383
|
+
else
|
384
|
+
value = value.to_sym
|
385
|
+
error "Unkown color or style '#{value}'" if
|
386
|
+
not COLORS.member?(value) and not STYLES.member?(value)
|
387
|
+
end
|
388
|
+
style[key] = value
|
389
|
+
end
|
390
|
+
return style
|
391
|
+
rescue
|
392
|
+
# if parsing fails, return default style (nil)
|
393
|
+
return nil
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
# Format a description.
|
398
|
+
# - title: description title (project, property or target name).
|
399
|
+
# - text: description text.
|
400
|
+
# - indent: indentation width.
|
401
|
+
# - bullet: tells if we must put a bullet.
|
402
|
+
def format_description(title, text=nil, indent=0, bullet=true)
|
403
|
+
string = ' '*indent
|
404
|
+
string << '- ' if bullet
|
405
|
+
string << title
|
406
|
+
if text
|
407
|
+
string << ": "
|
408
|
+
if text.split("\n").length > 1
|
409
|
+
string << "\n"
|
410
|
+
text.split("\n").each do |line|
|
411
|
+
string << ' '*(indent+2) + line.strip + "\n"
|
412
|
+
end
|
413
|
+
else
|
414
|
+
string << text.strip + "\n"
|
415
|
+
end
|
416
|
+
end
|
417
|
+
return string
|
418
|
+
end
|
419
|
+
|
420
|
+
end
|
421
|
+
|
422
|
+
#########################################################################
|
423
|
+
# CONSOLE LISTENER #
|
424
|
+
#########################################################################
|
425
|
+
|
426
|
+
# Listener when running in a console. Prints messages on the console using
|
427
|
+
# a given formatter.
|
428
|
+
class ConsoleListener
|
429
|
+
|
430
|
+
# Formatter used by listener.
|
431
|
+
attr_reader :formatter
|
432
|
+
# Verbosity flag.
|
433
|
+
attr_reader :verbose
|
434
|
+
# Build start time.
|
435
|
+
attr_reader :start_time
|
436
|
+
# Build end time.
|
437
|
+
attr_reader :end_time
|
438
|
+
# Build duration.
|
439
|
+
attr_reader :duration
|
440
|
+
# Build success.
|
441
|
+
attr_reader :success
|
442
|
+
# Last target met.
|
443
|
+
attr_reader :last_target
|
444
|
+
# Last task met.
|
445
|
+
attr_reader :last_task
|
446
|
+
|
447
|
+
# Constructor.
|
448
|
+
# - formatter: the formatter to use to output on console.
|
449
|
+
# - verbose: tells if we run in verbose mode.
|
450
|
+
def initialize(formatter, verbose)
|
451
|
+
@formatter = formatter
|
452
|
+
@verbose = verbose
|
453
|
+
end
|
454
|
+
|
455
|
+
# Called when build is started.
|
456
|
+
# - build: the build object.
|
457
|
+
def build_started(build)
|
458
|
+
@start_time = Time.now
|
459
|
+
@end_time = nil
|
460
|
+
@duration = nil
|
461
|
+
@success = nil
|
462
|
+
@last_target = nil
|
463
|
+
@last_task = nil
|
464
|
+
puts "Starting build '#{build.file}'..." if @verbose
|
465
|
+
end
|
466
|
+
|
467
|
+
# Called when build is finished.
|
468
|
+
# - build: the build object.
|
469
|
+
def build_finished(build)
|
470
|
+
@end_time = Time.now
|
471
|
+
@duration = @end_time - @start_time
|
472
|
+
@success = true
|
473
|
+
puts "Built in #{@duration} s" if @verbose
|
474
|
+
puts @formatter.format_success('OK')
|
475
|
+
end
|
476
|
+
|
477
|
+
# Called when a target is met.
|
478
|
+
# - target: the target object.
|
479
|
+
def target(target)
|
480
|
+
@last_target = target
|
481
|
+
@last_task = nil
|
482
|
+
puts @formatter.format_target(target)
|
483
|
+
end
|
484
|
+
|
485
|
+
# Called when a task is met.
|
486
|
+
# - task: task source (shell, Ruby or task).
|
487
|
+
def task(task)
|
488
|
+
@last_task = task
|
489
|
+
puts @formatter.format_task(task) if @verbose
|
490
|
+
end
|
491
|
+
|
492
|
+
# Called when an error was raised.
|
493
|
+
# - exception: raised exception.
|
494
|
+
def error(exception)
|
495
|
+
@end_time = Time.now
|
496
|
+
@duration = @end_time - @start_time
|
497
|
+
@success = false
|
498
|
+
puts "Built in #{@duration} s" if @verbose
|
499
|
+
message = ''
|
500
|
+
message << "In target '#{@last_target.name}'" if @last_target
|
501
|
+
message << ", in task:\n#{@formatter.format_task(@last_task)}\n" if
|
502
|
+
@last_task
|
503
|
+
message << ': ' if @last_target and not @last_task
|
504
|
+
message << exception.to_s
|
505
|
+
puts "#{@formatter.format_error('ERROR')}: #{message}"
|
506
|
+
end
|
507
|
+
|
508
|
+
end
|
509
|
+
|
510
|
+
#########################################################################
|
511
|
+
# BUILD TEMPLATE #
|
512
|
+
#########################################################################
|
513
|
+
|
514
|
+
BUILD_TEMPLATE =
|
515
|
+
'# Template build file
|
516
|
+
- build: template
|
517
|
+
description: Template build file.
|
518
|
+
default: hello
|
519
|
+
|
520
|
+
# Build properties
|
521
|
+
- properties:
|
522
|
+
- user: "#{ENV[\'USER\']}"
|
523
|
+
|
524
|
+
# Build targets
|
525
|
+
- target: capitalize
|
526
|
+
description: Capitalize user name
|
527
|
+
script:
|
528
|
+
- rb: "who = user.capitalize"
|
529
|
+
|
530
|
+
- target: hello
|
531
|
+
depends: capitalize
|
532
|
+
description: Print greatings.
|
533
|
+
script:
|
534
|
+
- "echo \"Hello #{who}!\""
|
535
|
+
'
|
536
|
+
|
537
|
+
end
|
538
|
+
|
539
|
+
end
|