Bee C0 Coverage Information - RCov

lib/bee_task_default.rb

Name Total Lines Lines of Code Total Coverage Code Coverage
lib/bee_task_default.rb 1828 1017
98.74%
97.74%

Key

Code reported as executed by Ruby looks like this...and this: this line is also marked as covered.Lines considered as run by rcov, but not reported by Ruby, look like this,and this: these lines were inferred by rcov (using simple heuristics).Finally, here's a line marked as not executed.

Coverage Details

1 # Copyright 2006-2012 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 'rubygems'
16 require 'bee_build'
17 require 'bee_task_package'
18 require 'bee_util'
19 require 'bee_version_dependant'
20 require 'erb'
21 require 'fileutils'
22 require 'net/smtp'
23 require 'highline/import'
24 require 'net/ftp'
25 
26 module Bee
27   
28   module Task
29   
30     # Package for default tasks (tasks with no package).
31     class Default < Package
32       
33       ######################################################################
34       #                        MISCELLANEOUS TASKS                         #
35       ######################################################################
36     
37       # Print a message on console. If message is not a string, this task
38       # outputs the inspected value of the object.
39       # 
40       # - message: message to print.
41       # 
42       # Example
43       # 
44       #  - echo: "Hello World!"
45       def echo(message)
46         message = '' if message == nil
47         case message
48         when String
49           puts message
50         else
51           puts message.inspect
52         end
53       end
54 
55       # Alias for echo.
56       alias :print :echo
57 
58       # Wait for a given amount of time.
59       # 
60       # - time: time to wait, in seconds, as an integer or float.
61       # 
62       # Example
63       # 
64       #  - sleep: 3.5
65       def sleep(time)
66         error "sleep parameter must be a float or a integer" if
67           not time.kind_of?(Numeric)
68         seconds = time.to_f
69         puts "Waiting #{time} seconds..."
70         Kernel.sleep seconds
71       end
72 
73       # Alias for sleep.
74       alias :wait :sleep
75 
76       # Prompt the user for the value of a given property matching a pattern.
77       # 
78       # - message: message to print at prompt. Should include a description
79       #   of the expected pattern.
80       # - property: the name of the property to set.
81       # - default: default value if user doesn't type anything. Written
82       #   into square brakets after prompt message. Optional.
83       # - pattern: a Ruby pattern for prompted value. If this pattern is not
84       #   matched, this task will prompt again. Optional, if no pattern is
85       #   given, any value is accepted.
86       # - error: the error message to print when pattern is not matched.
87       # - attempts: number of allowed attempts. Optional, defaults to 0, which
88       #   means an unlimited number of attempts.
89       # - echo: the character to echo while typing. Useful for passwords,
90       #   echoing '*' for instance.
91       #
92       # Example
93       #
94       #  - prompt:
95       #      message:  "Enter your age"
96       #      property: "age"
97       #      default:  "18"
98       #      pattern:  "^\\d+$"
99       #      error:    "Age must be a positive integer"
100       def prompt(params)
101         params_desc = {
102           :message  => { :mandatory => true,  :type => :string },
103           :property => { :mandatory => true,  :type => :string },
104           :default  => { :mandatory => false, :type => :string },
105           :pattern  => { :mandatory => false, :type => :string },
106           :error    => { :mandatory => false, :type => :string },
107           :attempts => { :mandatory => false, :type => :integer,
108                          :default   => 0 },
109           :echo     => { :mandatory => false, :type => :string,
110                          :default   => false }
111         }
112         check_parameters(params, params_desc)
113         message = params[:message]
114         property = params[:property]
115         default = params[:default]
116         pattern = params[:pattern]
117         error = params[:error]
118         attempts = params[:attempts]
119         echo_char = params[:echo]
120         message << " [#{default}]" if default
121         message << ': '
122         ok = false
123         nb_attempts = 1
124         while not (ok or (nb_attempts > attempts and attempts != 0))
125           if echo_char
126             value = ask(message) {|q| q.echo=echo_char}
127           else
128             value = ask(message)
129           end
130           value = default if default and value.length == 0
131           if pattern
132             if value =~ /#{pattern}/
133               ok = true
134             elsif error
135               puts error
136             end
137           else
138             ok = true
139           end
140           nb_attempts += 1
141         end
142         error "Failed to obtain a matching prompt" if not ok
143         @build.context.set_property(property, value)
144       end
145 
146       # Throw a build error with a given message.
147       #
148       # - message: the error message. Will be printed on the console as the
149       #   build failure reason.
150       #
151       # Example
152       #
153       #   - if: "not File.exists?('/etc/config')"
154       #     then:
155       #     - throw: "No /etc/config file found!"
156       def throw(message)
157         error "throw parameter must be a string" if not message.kind_of?(String)
158         error message
159       end
160 
161       # Alias for throw.
162       alias :raise :throw
163 
164       # Get a given URL and store its content in a given file. Parameters
165       # is a Hash with following entries:
166       #
167       # - url: the URL to get.
168       # - dest: destination file. Optional, defaults to retrieved file name
169       #   in current directory. If destination is a directory, file is saved
170       #   in destination directory with the name of the retrieved file.
171       # - prop: Property to set with content of the response body. Optional
172       #   defaults to output in a file.
173       # - limit: the redirections limit. Optional, defaults to 10.
174       # - username: username for HTTP basic authentication. Optional.
175       # - password: password for HTTP basic authentication. Optional.
176       #
177       # Example
178       #
179       #   - get:
180       #       url: http://rubyforge.org/frs/download.php/22185/bee-0.4.0.zip
181       def http_get(parameters)
182         params_desc = {
183           :url      => { :mandatory => true,  :type => :string },
184           :dest     => { :mandatory => false, :type => :string },
185           :prop     => { :mandatory => false, :type => :string },
186           :limit    => { :mandatory => false, :type => :integer,
187                          :default => 10 },
188           :username => { :mandatory => false, :type => :string },
189           :password => { :mandatory => false, :type => :string },
190         }
191         check_parameters(parameters, params_desc)
192         url      = parameters[:url]
193         dest     = parameters[:dest]
194         prop     = parameters[:prop]
195         username = parameters[:username]
196         password = parameters[:password]
197         if not dest and not prop
198           destination = File.basename(url)
199         elsif dest and File.directory?(dest)
200           destination = File.join(dest, File.basename(url))
201         elsif dest
202           destination = dest
203         else
204           destination = nil
205         end
206         limit = parameters[:limit]
207         puts "Getting URL '#{url}'..."
208         begin
209           content = Util::fetch(url, limit, username, password)
210         rescue Exception
211           error "Error getting URL: #{$!}"
212         end
213         if destination
214           todir = File.dirname(destination)
215           begin
216             FileUtils.makedirs(todir) if not File.exists?(todir)
217             File.open(destination, 'w') { |file| file.write(content) }
218           rescue Exception
219             error "Error saving file: #{$!}"
220           end
221         end
222         if prop
223           @build.context.set_property(prop, content)
224         end
225       end
226       
227       # Send an email using SMTP.
228       #
229       # - from: The sender of the email.
230       # - to: Recipient of the email. This may be a list of recipients.
231       # - subject: The subject of the email.
232       # - message: The body of the email.
233       # - smtp: The address of the SMTP server.
234       # - encoding: The message encoding. Defaults to ASCII.
235       #
236       # Example
237       #
238       #   - mail:
239       #       from:    "foo@bee.com"
240       #       to:      "bar@bee.com"
241       #       subject: "Bee Release 0.6.2"
242       #       message: "Hi! There is a new Bee release!"
243       #       smtp:    "smtp.bee.com"
244       def mail(parameters)
245         params_desc = {
246           :from     => { :mandatory => true,  :type => :string },
247           :to       => { :mandatory => true,  :type => :string_or_array },
248           :subject  => { :mandatory => true,  :type => :string },
249           :message  => { :mandatory => true,  :type => :string },
250           :smtp     => { :mandatory => true,  :type => :string },
251           :encoding => { :mandatory => false, :type => :string,
252                          :default   => 'UTF-8'}
253         }
254         check_parameters(parameters, params_desc)
255         from     = parameters[:from]
256         to       = Array(parameters[:to])
257         subject  = parameters[:subject]
258         message  = parameters[:message]
259         smtp     = parameters[:smtp]
260         encoding = parameters[:encoding]
261         body = <<EOF
262 MIME-Version: 1.0
263 Content-Type: text/plain; charset=#{encoding}
264 From: #{from}
265 To: #{to.join(', ')}
266 Subject: #{subject}
267 
268 #{message}
269 EOF
270         puts "Sending email about '#{subject}'..."
271         begin
272           Net::SMTP.start(smtp) do |smtp_server|
273             smtp_server.send_message(body, from, to)
274           end
275         rescue Exception
276           error "Error sending email: #{$!}"
277         end
278       end
279 
280       ######################################################################
281       #                         FILE RELATED TASKS                         #
282       ######################################################################
283     
284       # Print contents of a given file on the console. Parameter is the name
285       # of the file to output, as a String.
286       # 
287       # Example
288       # 
289       #  - cat: "doc/welcome-message.txt"
290       def cat(file)
291         error "Parameter must be a string" unless file.kind_of?(String)
292         error "File '#{file}' not a regular file or not readable" unless 
293           File.file?(file) and File.readable?(file)
294         puts File.read(file).strip
295       end
296 
297       # Change working directory. This change will persist for all tasks in
298       # the current target. Entering a new target, working directory will
299       # recover its default value, which is the directory of the build file
300       # (or property 'base'). Parameter is a String with directory to change
301       # to.
302       # 
303       # Example
304       # 
305       #  - cd: "build"
306       def cd(dir)
307         error "cd parameter must be a string" unless dir.kind_of?(String)
308         error "cd parameter must be a readable existing directory" unless
309           File.directory?(dir) and File.executable?(dir)
310         puts "Changing directory to '#{dir}'"
311         Dir.chdir(dir)
312       end
313 
314       # Put working directory in a given property. Parameter is the name of
315       # the property to write current directory into.
316       #
317       # Example
318       #
319       #   - pwd: current_dir
320       def pwd(property)
321         error "pwd parameter must be a string" unless property.kind_of?(String)
322         pwd = FileUtils.pwd
323         @build.context.set_property(property, pwd)
324       end
325 
326       # Make a symbolic link from a source file to a destination one.
327       # Parameter is a Hash with following entries:
328       #
329       # - old: source of the link, as a glob. If there are more than one
330       #   file to link, this task will make links 'new/file' for each file
331       #   of the glob.
332       # - new: destination of the link.
333       #
334       # Example
335       #
336       #   - ln:
337       #       old: /usr/local
338       #       new: /opt
339       #
340       # Note:
341       #
342       #   This task is not implemented under Windows.
343       def ln(parameters)
344         params_desc = {
345           :old => { :mandatory => true, :type => :string },
346           :new => { :mandatory => true, :type => :string }
347         }
348         check_parameters(parameters, params_desc)
349         old = parameters[:old]
350         new = parameters[:new]
351         files = Dir.glob(old)
352         files = files.first if files.length == 1
353         puts "Linking #{files.length} file(s) to '#{new}'"
354         begin
355           FileUtils.ln_s(files, new)
356         rescue Exception
357           error "Error making the link: #{$!}"
358         end
359       end
360 
361       # Alias for ln.
362       alias :link :ln
363 
364       # Change permissions for a set of files. Parameters is a Hash with
365       # following entries:
366       #
367       # - files: files to change permissions for, as a glob.
368       # - mode: permissons as an Unix integer (such as 0644 or 0755). Note that
369       #   numbers starting with 0 are considered octal, with 0x, they are
370       #   supposed to be hexa and in base 10 otherwise.
371       # - recursive: tells if should process directories recursively.
372       #   Optional, defaults to 'false'.
373       #
374       # Example:
375       #
376       #   - chmod:
377       #       files: /usr/local/bin/*
378       #       mode:  0755
379       #
380       # Note:
381       #
382       #   This task is not implemented under Windows.
383       def chmod(parameters)
384         params_desc = {
385           :files     => { :mandatory => true,  :type => :string_or_array },
386           :mode      => { :mandatory => true,  :type => :integer },
387           :recursive => { :mandatory => false, :type => :boolean,
388                           :default   => false }
389         }
390         check_parameters(parameters, params_desc)
391         files = parameters[:files]
392         mode = parameters[:mode]
393         recursive = parameters[:recursive]
394         files = Dir.glob(files)
395         if files.length > 0
396           puts "Changing permissions for #{files.length} file(s) to '#{mode}'"
397           begin
398             if recursive
399               FileUtils.chmod_R(mode, files)
400             else
401               FileUtils.chmod(mode, files)
402             end
403           rescue Exception
404             error "Error changing permissions: #{$!}"
405           end
406         end
407       end
408 
409       # Change owner and group for a set of files. Parameters is a Hash with
410       # following entries:
411       #
412       # - files: files to change owner for, as a glob.
413       # - user: the user to change for, may be a name or an ID (integer). If
414       #   not set, the user is not changed.
415       # - group: the group to change for, may be a name or an ID (integer). If
416       #   not set, the group is not changed.
417       # - recursive: tells if should process directories recursively.
418       #   Optional, defaults to 'false'.
419       #
420       # Example:
421       #
422       #   - chown:
423       #       files:     /home/casa
424       #       user:      casa
425       #       group:     staff
426       #       recursive: true
427       #
428       # Note:
429       #
430       #   This task is not implemented under Windows.
431       def chown(parameters)
432         params_desc = {
433           :files     => { :mandatory => true,  :type => :string_or_array },
434           :user      => { :mandatory => false, :type => :string_or_integer },
435           :group     => { :mandatory => false, :type => :string_or_integer },
436           :recursive => { :mandatory => false, :type => :boolean,
437                           :default   => false }
438         }
439         check_parameters(parameters, params_desc)
440         files = parameters['files']
441         user = parameters['user']
442         group = parameters['group']
443         recursive = parameters['recursive']
444         files = Dir.glob(files)
445         if files.length > 0
446           puts "Changing owner of #{files.length} file(s) to '#{user}/#{group}'"
447           begin
448             if recursive
449               FileUtils.chown_R(user, group, files)
450             else
451               FileUtils.chown(user, group, files)
452             end
453           rescue Exception
454             error "Error changing owner: #{$!}"
455           end
456         end
457       end
458 
459       # Make a directory, and parent directories if necessary. Doesn't
460       # complain if directory already exists. Parameter is directory to
461       # create as a String or a list of directories as an Array of Strings.
462       # 
463       # Example
464       # 
465       #  - mkdir: "foo/bar"
466       def mkdir(dirs)
467         error "mkdir parameter must a String or an array of Strings" unless
468           dirs.kind_of?(String) or dirs.kind_of?(Array)
469         dirs = Array(dirs)
470         for dir in dirs
471           error "mkdir parameter must a String or an array of Strings" unless
472             dir.kind_of?(String)
473           puts "Creating directory '#{dir}'"
474           begin
475             FileUtils.makedirs(dir)
476           rescue Exception
477             error "Error creating directory '#{dir}': #{$!}"
478           end
479         end
480       end
481 
482       # Copy files or directories to destination file or directory. Parameter 
483       # is a Hash with following entries:
484       # 
485       # - src: glob or list of globs for source files or directories to copy.
486       #   Included source directories are copied recursively.
487       # - dest: destination file or directory.
488       # 
489       # Example
490       # 
491       #  - cp:
492       #      src:  "img/*"
493       #      dest: :doc
494       def cp(params)
495         params_desc = {
496           :src  => { :mandatory => true, :type => :string_or_array },
497           :dest => { :mandatory => true, :type => :string }
498         }
499         check_parameters(params, params_desc)
500         src = params['src']
501         dest = params['dest']
502         src = Array(src).map { |s| Dir.glob(s) }.flatten.uniq
503         src = src.first if src.length == 1
504         if src.kind_of?(Array)
505           nb_copies = src.length
506         else
507           nb_copies = 1
508         end
509         puts "Copying #{nb_copies} file(s) to '#{dest}'"
510         begin
511           FileUtils.cp_r(src, dest)
512         rescue Exception
513           error "Error copying file(s): #{$!}"
514         end
515       end
516 
517       # Moves files or directories to destination file or directory. Parameter 
518       # is a Hash with following entries:
519       # 
520       # - src: glob or list of globs for source files or directories to move.
521       #   Included source directories are moved recursively.
522       # - dest: destination file or directory.
523       # 
524       # Example
525       # 
526       #  - mv:
527       #      src:  "**/*~"
528       #      dest: :trash
529       def mv(params)
530         params_desc = {
531           :src  => { :mandatory => true, :type => :string_or_array },
532           :dest => { :mandatory => true, :type => :string }
533         }
534         check_parameters(params, params_desc)
535         src = params['src']
536         dest = params['dest']
537         src = Array(src).map { |s| Dir.glob(s) }.flatten.uniq
538         src = src.first if src.length == 1
539         if src.kind_of?(Array)
540           nb_moves = src.length
541         else
542           nb_moves = 1
543         end
544         puts "Moving #{nb_moves} file(s) to '#{dest}'"
545         begin
546           FileUtils.mv(src, dest)
547         rescue Exception
548           error "Error moving file(s): #{$!}"
549         end
550       end
551 
552       # Copy filtered files. Parameter is a hash with following entries:
553       #
554       # - root: root directory for files to copy. Optional, defaults to current
555       #   directory.
556       # - includes: list of globs for files to copy. Optional, defaults to 
557       #   '**/*' to include all files recursively.
558       # - excludes: list of globs for files to exclude from copy. Optional,
559       #   default to nil to exclude no file.
560       # - dotmatch: tells if joker matches dot files. Optional, defaults to
561       #   false.
562       # - flatten: tells if included files should be copied in destination
563       #   directory, ignoring their subdirectory. Optional, defaults to false.
564       # - dest: destination directory for the copy, must be an existing
565       #   directory.
566       # - lenient: tells if copy is lenient, which will silently succeed on
567       #   errors (for instance if root or destination directory don't exist).
568       #   Optional, defaults to false.
569       #
570       # Example:
571       #
572       # To copy all files from directory 'src', except those living in 'CVS'
573       # directories, into directory 'destination', you could write:
574       #
575       #   - copy:
576       #       root:     src
577       #       includes: **/*
578       #       excludes: **/CVS/**/*
579       #       dest:     destination
580       # 
581       # Note: this task only deals with files. Thus, 'includes' and 'excludes'
582       # globs should be ones for files.
583       def copy(params)
584         # check parameters and set default values
585         params_desc = {
586           :root     => { :mandatory => false, :type => :string },
587           :includes => { :mandatory => false, :type => :string_or_array },
588           :excludes => { :mandatory => false, :type => :string_or_array },
589           :dotmatch => { :mandatory => false, :type => :boolean },
590           :dest     => { :mandatory => true,  :type => :string },
591           :flatten  => { :mandatory => false, :type => :boolean,
592                          :default   => false },
593           :lenient  => { :mandatory => false, :type => :boolean,
594                          :default   => false }
595         }
596         check_parameters(params, params_desc)
597         root     = params[:root]
598         includes = params[:includes]
599         excludes = params[:excludes]
600         dotmatch = params[:dotmatch]
601         dest     = params[:dest]
602         flatten  = params[:flatten]
603         lenient  = params[:lenient]
604         # check that destination is an existing directory
605         if not (File.exists?(dest) and File.directory?(dest))
606           if lenient
607             return
608           else
609             error "copy 'dest' parameter must be an existing directory"
610           end
611         end
612         root = '.' if root == nil
613         dotmatch = false if dotmatch == nil
614         if not (File.exists?(root) and File.directory?(root))
615           if lenient
616             return
617           else
618             error "copy 'root' parameter must be an existing directory"
619           end
620         end
621         files = filter_files(root, includes, excludes, dotmatch)
622         copy_files(root, files, dest, flatten)
623       end
624 
625       # Move filtered files. Parameter is a hash with following entries:
626       #
627       # - root: root directory for files to move. Optional, defaults to
628       #   current directory.
629       # - includes: list of globs for files to move. Optional, defaults to 
630       #   '**/*' to include all files recursively.
631       # - excludes: list of globs for files to exclude from move. Optional,
632       #   default to nil to exclude no file.
633       # - flatten: tells if included files should be moved to destination
634       #   directory, ignoring their subdirectory. Optional, defaults to false.
635       # - dotmatch: tells if joker matches dot files. Optional, defaults to
636       #   false.
637       # - lenient: tells if move is lenient, which will silently succeed on
638       #   errors (for instance if root or destination directory don't exist).
639       #   Optional, defaults to false.
640       #
641       # Example:
642       #
643       # To move all files from directory 'src', except those living in 'CVS'
644       # directories, into directory 'destination', you could write:
645       #
646       #   - move:
647       #       root:     src
648       #       includes: **/*
649       #       excludes: **/CVS/**/*
650       #       dest:     destination
651       #
652       # Note: this task only deals with files. Thus, 'includes' and 'excludes'
653       # globs should be ones for files and directories from root will not
654       # be affected by this task.
655       def move(params)
656         # check parameters and set default values
657         params_desc = {
658           :root     => { :mandatory => false, :type => :string,
659                          :default   => '.' },
660           :includes => { :mandatory => false, :type => :string_or_array },
661           :excludes => { :mandatory => false, :type => :string_or_array },
662           :dest     => { :mandatory => true,  :type => :string },
663           :flatten  => { :mandatory => false, :type => :boolean,
664                          :default   => false },
665           :dotmatch => { :mandatory => false, :type => :boolean,
666                          :default   => false },
667           :lenient  => { :mandatory => false, :type => :boolean,
668                          :default   => false }
669         }
670         check_parameters(params, params_desc)
671         root     = params[:root]
672         includes = params[:includes]
673         excludes = params[:excludes]
674         dest     = params[:dest]
675         flatten  = params[:flatten]
676         dotmatch = params[:dotmatch]
677         lenient  = params[:lenient]
678         # check that root and dest are existing directories
679         if not (File.exists?(root) and File.directory?(root))
680           if lenient
681             return
682           else
683             error "move 'root' parameter must be an existing directory"
684           end
685         end
686         if not (File.exists?(dest) and File.directory?(dest))
687           if lenient
688             return
689           else
690             error "move 'dest' parameter must be an existing directory"
691           end
692         end
693         # select files and make move
694         files = filter_files(root, includes, excludes, dotmatch)
695         puts "Moving #{files.length} file(s) to '#{dest}'"
696         for file in files
697           from_file = File.join(root, file)
698           if flatten
699             to_file = File.join(dest, File.basename(file))
700           else
701             to_file = File.join(dest, file)
702           end
703           to_dir    = File.dirname(to_file)
704           FileUtils.makedirs(to_dir) if not File.exists?(to_dir)
705           FileUtils.mv(from_file, to_file)
706         end
707       end
708 
709       # Delete files for a given glob or list of globs. Parameter is a glob or 
710       # list of globs for files to delete. This task will raise an error if
711       # told to delete a directory. Use task 'rmrf' to do so.
712       # 
713       # Example
714       # 
715       #  - rm: ["**/*~", "**/.DS_Store"]
716       def rm(globs)
717         error "rm parameter is a String or Array of Strings" unless
718           globs.kind_of?(String) or globs.kind_of?(Array)
719         globs = Array(globs)
720         for glob in globs
721           error "rm parameter is a String or Array of Strings" unless
722             glob.kind_of?(String)
723           files = Dir.glob(glob)
724           size = (files.kind_of?(Array) ? files.size : 1)
725           puts "Deleting #{size} file(s)" if files.length > 0
726           for file in files
727             begin
728               FileUtils.rm(file)
729             rescue Exception
730               error "Error deleting files: #{$!}"
731             end
732           end
733         end
734       end
735 
736       # Delete files and directories recursively. Parameter is a glob or list
737       # of globs for files and directories to delete.
738       # 
739       # Example
740       # 
741       #  - rmrf: :build
742       def rmrf(globs)
743         error "rmrf parameter is a String or an Array of Strings" unless
744           globs.kind_of?(String) or globs.kind_of?(Array)
745         globs = Array(globs)
746         for glob in globs
747           error "rmrf parameter is a String or an Array of Strings" unless
748             glob.kind_of?(String)
749           dirs = Dir.glob(glob)
750           size = (dirs.kind_of?(Array) ? dirs.size : 1)
751           puts "Deleting #{size} directory(ies)" if dirs.length > 0
752           for dir in dirs
753             begin
754               FileUtils.rm_rf(dir)
755             rescue Exception
756               error "Error deleting directory(ies): #{$!}"
757             end
758           end
759         end
760       end
761 
762       # Alias for rmrf.
763       alias :rmdir :rmrf
764 
765       # Update modification time and access time of files in a list. Files
766       # are created if they don't exist. Parameter is a glob or list of
767       # globs for files to touch.
768       #
769       # Example
770       # 
771       #   - touch: '#{target}/classes/**/*.class'
772       def touch(globs)
773         error "touch parameter is a String or an Array of Strings" unless
774           globs.kind_of?(String) or globs.kind_of?(Array)
775         globs = Array(globs)
776         files = []
777         for glob in globs
778           error "touch parameter is a String or an Array of Strings" unless
779             glob.kind_of?(String)
780           new_files = Dir.glob(glob)
781           if new_files.length == 0
782             files << glob
783           else
784             files += new_files
785           end
786         end
787         files.uniq!
788         size = (files.kind_of?(Array) ? files.size : 1)
789         puts "Touching #{size} file(s)" if size > 0
790         begin
791           FileUtils.touch(files)
792         rescue Exception
793           error "Error touching file(s): #{$!}"
794         end
795       end
796 
797       # Find files for a glob or list of globs and store list in a property. 
798       # Parameter is a Hash with entries:
799       # 
800       # - root: root directory for file search. Defaults to '.' (current
801       #   directory).
802       # - includes: glob or list of globs for files to look for. Defaults to
803       #   '**/*' to include all files recursively.
804       # - excludes: glob or list of globs for files to exclude from search.
805       #   Defaults to nil to exclude no file.
806       # - dotmatch: tells if joker matches dot files. Optional, defaults to
807       #   false.
808       # - property: name of the property to set.
809       # - join: a character used to join the list in a string. Defaults
810       #   to nil so that list is not joined.
811       # 
812       # Example
813       #
814       # To find all PNG in files in 'img' directory, and store the list in
815       # property image_files, one could write:
816       # 
817       #  - find:
818       #      root:     "img"
819       #      includes: "**/*.png"
820       #      property: "image_files"
821       def find(params)
822         params_desc = {
823           :root     => { :mandatory => false, :type => :string,
824                          :default => '.' },
825           :includes => { :mandatory => false, :type => :string_or_array },
826           :excludes => { :mandatory => false, :type => :string_or_array },
827           :property => { :mandatory => true,  :type => :string },
828           :dotmatch => { :mandatory => false, :type => :boolean,
829                          :default => false },
830           :join     => { :mandatory => false, :type => :string }
831         }
832         check_parameters(params, params_desc)
833         root     = params[:root]
834         includes = params[:includes]
835         excludes = params[:excludes]
836         property = params[:property]
837         dotmatch = params[:dotmatch]
838         join     = params[:join]
839         files = filter_files(root, includes, excludes, dotmatch)
840         if join
841           files = files.join(join)
842         end
843         @build.context.set_property(property, files)
844       end
845 
846       # Load a YAML file in a given property.
847       # 
848       # - file: the YAML file name to load.
849       # - prop: the property name to set with YAML parsed content.
850       # 
851       # Example
852       #
853       #  - yaml_load:
854       #      file: "my_list.yml"
855       #      prop: "my_list"
856       def yaml_load(params)
857         params_desc = {
858           :file  => { :mandatory => true, :type => :string },
859           :prop  => { :mandatory => true, :type => :string },
860         }
861         check_parameters(params, params_desc)
862         file = params[:file]
863         prop = params[:prop]
864         error "YAML file '#{file}' not found" if not File.exists?(file)
865         script = "#{prop} = YAML.load(File.read('#{file}'))"
866         begin
867           @build.context.evaluate_script(script)
868         rescue Exception
869           error "Error loading YAML file '#{file}': #{$!}"
870         end
871       end      
872 
873       # Dump the content of a given property into a YAML file.
874       # 
875       # - prop: the property to dump.
876       # - file: the YAML file name to dump into.
877       # 
878       # Example
879       #
880       #  - yaml_dump:
881       #      prop: "my_list"
882       #      file: "my_list.yml"
883       def yaml_dump(params)
884         params_desc = {
885           :prop  => { :mandatory => true, :type => :string },
886           :file  => { :mandatory => true, :type => :string }
887         }
888         check_parameters(params, params_desc)
889         prop = params[:prop]
890         file = params[:file]
891         script = "File.open('#{file}', 'w') {|f| f.write(YAML.dump(#{prop}))}"
892         begin
893           @build.context.evaluate_script(script)
894         rescue Exception
895           error "Error dumping YAML file '#{file}': #{$!}"
896         end
897       end      
898 
899       ######################################################################
900       #                        RUBY RELATED TASKS                          #
901       ######################################################################
902     
903       # Tests a required library and prints an error message if import
904       # fails. Parameter is a Hash with entries:
905       #
906       # - library: required library (as in require call).
907       # - message: error message to print if require fails.
908       #
909       # Example
910       #
911       #  - required:
912       #      library: foo
913       #      message: >
914       #        Library foo must be installed (gem install foo) to run
915       #        task bar.
916       def required(params)
917         require 'rubygems'
918         require 'rubygems/gem_runner'
919         params_desc = {
920           :library => { :mandatory => true, :type => :string },
921           :message => { :mandatory => true, :type => :string }
922         }
923         check_parameters(params, params_desc)
924         library = params[:library]
925         message = params[:message]
926         available = Bee::VersionDependant::gem_available?(library)
927         error message if not available
928       end
929 
930       # Run Ruby unit tests listed as a glob or list of globs in a given
931       # directory (that defaults to current one). Parameter is a Hash with
932       # following entries:
933       # 
934       # - root: root directory for files to include. Defaults to current
935       #   directory.
936       # - includes: glob or list of globs for unit test files to run.
937       #   Defaults to '**/*' to include all files recursively.
938       # - excludes: glob or list of globs for unit test files to exclude.
939       #   Defaults to nil to exclude no file.
940       # - dotmatch: tells if joker matches dot files. Optional, defaults to
941       #   false.
942       # - dir: directory where to run unit tests.
943       # 
944       # Example
945       # 
946       #  - find:
947       #      root:     :test
948       #      includes: "**/tc_*.rb"
949       #      dir:      "test"
950       #
951       # Notes
952       #
953       # For ruby 1.9 and later, you must install gem 'test-unit' to run this
954       # task.
955       def test(params)
956         require 'test/unit'
957         params_desc = {
958           :root     => { :mandatory => false, :type => :string,
959                          :default   => '.' },
960           :includes => { :mandatory => true,  :type => :string },
961           :excludes => { :mandatory => false, :type => :string },
962           :dotmatch => { :mandatory => false, :type => :boolean,
963                          :default => false },
964           :dir      => { :mandatory => false, :type => :string,
965                          :default => '.' }
966         }
967         check_parameters(params, params_desc)
968         root     = params[:root]
969         includes = params[:includes]
970         excludes = params[:excludes]
971         dotmatch = params[:dotmatch]
972         dir      = params[:dir]
973         error "Test directory '#{dir}' not found" if 
974           not (File.exists?(dir) and File.directory?(dir))
975         files = filter_files(root, includes, excludes, dotmatch)
976         files.map! { |file| File.expand_path(File.join(root, file)) }
977         size = (files.kind_of?(Array) ? files.size : 1)
978         puts "Running #{size} unit test(s)"
979         for file in files
980           load file
981         end
982         old_dir = Dir.pwd
983         begin
984           Dir.chdir(dir)
985           if Bee::VersionDependant::ruby_lower_than('1.9.2')
986             runner = Test::Unit::AutoRunner.new(false)
987           else
988             runner = MiniTest::Unit.new()
989           end
990           ok = runner.run
991           error "Test failure" if not ok
992         ensure
993           Dir.chdir(old_dir)
994         end
995       end
996 
997       # Run an ERB file or source in bee context and store result in a file or
998       # property. Parameter is a Hash with following entries:
999       # 
1000       # - source: ERB source text (if no 'src').
1001       # - src: ERB file name (if no 'source').
1002       # - dest: file where to store result (if no 'property').
1003       # - property: property name where to store result (if no 'dest').
1004       # - options: ERB options, a String containing one or more of the
1005       #   following modifiers:
1006       #   %  enables Ruby code processing for lines beginning with %
1007       #   <> omit newline for lines starting with <% and ending in %>
1008       #   >  omit newline for lines ending in %>
1009       #
1010       # For more information ebout ERB syntax, please see documentation at:
1011       # http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/.
1012       # 
1013       # Example
1014       # 
1015       #  - erb: { src: "gem.spec.erb", dest: "gem.spec" }
1016       # 
1017       # Notes
1018       # 
1019       # In these ERB files, you can access a property _foo_ writing:
1020       # 
1021       #  <p>Hello <%= foo %>!</p>
1022       def erb(params)
1023         params_desc = {
1024           :source   => { :mandatory => false, :type => :string },
1025           :src      => { :mandatory => false, :type => :string },
1026           :dest     => { :mandatory => false, :type => :string },
1027           :property => { :mandatory => false, :type => :string },
1028           :options  => { :mandatory => false, :type => :string }
1029         }
1030         check_parameters(params, params_desc)
1031         source   = params[:source]
1032         src      = params[:src]
1033         dest     = params[:dest]
1034         property = params[:property]
1035         options  = params[:options]
1036         error "Must pass one of 'source' or 'src' parameters to erb task" if
1037           not source and not src
1038         error "Must pass one of 'dest' or 'property' parameters to erb task" if
1039           not dest and not property
1040         error "erb src file '#{src}' not found" if src and
1041           (not File.exists?(src) or not File.file?(src) or
1042            not File.readable?(src))
1043         # load ERB source
1044         erb_source = source||File.open(src, 'r') {|f| f.read}
1045         if options
1046           template = ERB.new(erb_source, 0, options)
1047         else
1048           template = ERB.new(erb_source)
1049         end
1050         if src
1051           puts "Processing ERB '#{src}'"
1052         else
1053           puts "Processing ERB"
1054         end
1055         begin
1056           result = template.result(@build.context.context_binding)
1057         rescue Exception
1058           error "Error processing ERB: #{$!}"
1059         end
1060         # write result in file or set property
1061         if dest
1062           begin
1063             File.open(dest, 'w') { |file| file.write(result) }
1064           rescue Exception
1065             error "Error writing ERB result in file: #{$!}"
1066           end
1067         else
1068           @build.context.set_property(property, result)
1069         end
1070       end
1071       
1072       # Generate RDoc documentation for a given list of globs to include or
1073       # exclude and a destination directory. Parameter is a Hash with following
1074       # entries:
1075       # 
1076       # - root: root directory for files to include. Defaults to current
1077       #   directory.
1078       # - includes: glob or list of globs for files or directories to document.
1079       #   Defaults to '**/*' to include all files.
1080       # - excludes: glob or list of globs for files or directories that should
1081       #   not be documented. Defaults to nil to exclude no file.
1082       # - dotmatch: tells if joker matches dot files. Optional, defaults to
1083       #   false.
1084       # - dest: destination directory for generated documentation.
1085       # - options: additional options as a string or list of strings.
1086       # 
1087       # Example
1088       # 
1089       #  - rdoc:
1090       #      includes: ["README", "LICENSE", "#{src}/**/*"]
1091       #      dest: :api
1092       def rdoc(params)
1093         require 'rdoc/rdoc'
1094         params_desc= {
1095           :root     => { :mandatory => false, :type => :string },
1096           :includes => { :mandatory => true,  :type => :string_or_array },
1097           :excludes => { :mandatory => false, :type => :string_or_array },
1098           :dotmatch => { :mandatory => false, :type => :boolean,
1099                          :default   => false },
1100           :dest     => { :mandatory => true,  :type => :string },
1101           :options  => { :mandatory => false, :type => :string_or_array }
1102         }
1103         check_parameters(params, params_desc)
1104         root     = params[:root]
1105         includes = params[:includes]
1106         excludes = params[:excludes]
1107         dotmatch = params[:dotmatch]
1108         dest     = params[:dest]
1109         options  = params[:options]
1110         files = filter_files(root, includes, excludes, dotmatch)
1111         command_line = ['-S', '-o', dest]
1112         command_line += options if options
1113         command_line += files
1114         begin
1115           rdoc = RDoc::RDoc.new
1116           rdoc.document(command_line)
1117         rescue Exception
1118           error "Error generating RDoc: #{$!}"
1119         end
1120       end
1121       
1122       # Generate a Gem package in current directory, named after the Gem name
1123       # and version. Parameter is the name of the Gem description file.
1124       # 
1125       # Example
1126       # 
1127       #  - gem: :gem_spec
1128       def gem(description)
1129         require 'rubygems'
1130         require 'rubygems/gem_runner'
1131         error "gem parameter must be an existing file" if
1132           not description.kind_of?(String) or not File.exists?(description)
1133         arguments = ['build', description]
1134         begin
1135           Gem::GemRunner.new.run(arguments)
1136         rescue Exception
1137           error "Error generating Gem: #{$!}"
1138         end
1139       end
1140       
1141       # Run another bee build file.
1142       # 
1143       # - file: the build file to run, relative to current build file. 
1144       #   Optional, defaults to 'build.yml'.
1145       # - target: target or list of targets to run (default target if omitted).
1146       # - properties: boolean (true or false) that tells if properties of
1147       #   current build file should be sent and overwrite those of target
1148       #   build. Properties modified in child build don't change in parent
1149       #   one. Defaults to false.
1150       # 
1151       # Example
1152       # 
1153       #  - bee:
1154       #      file:       "doc/build.yml"
1155       #      target:     "pdf"
1156       #      properties: true
1157       def bee(parameters)
1158         # parse parameters
1159         params_desc = {
1160           :file       => { :mandatory => false, :type => :string, :default => 'build.yml' },
1161           :target     => { :mandatory => false, :type => :string_or_array,  :default => '' },
1162           :properties => { :mandatory => false, :type => :boolean, :default => false }
1163         }
1164         check_parameters(parameters, params_desc)
1165         file = parameters[:file]
1166         target = parameters[:target]
1167         properties = parameters[:properties]
1168         # run target build
1169         props = {}
1170         if properties
1171           for name in @build.context.properties
1172             props[name] = @build.context.get_property(name)
1173           end
1174         end
1175         begin
1176           build = Bee::Build.load(file, false, props)
1177           build.run(target, @build.listener.clone)
1178         rescue Exception
1179           error "Error invoking build file '#{file}': #{$!}"
1180         end
1181       end
1182 
1183       ######################################################################
1184       #                            ARCHIVE TASKS                           #
1185       ######################################################################
1186     
1187       # Generate a ZIP archive. Parameter is a Hash with following entries:
1188       # 
1189       # - root: root directory for files to include in the archive. Defaults
1190       #   to '.' for current directory.
1191       # - includes: glob or list of globs for files to select for the archive.
1192       #   Defaults to '**/*' to include all files recursively.
1193       # - excludes: glob or list of globs for files to exclude from the archive.
1194       #   Defaults to nil to exclude no file.
1195       # - dotmatch: tells if joker matches dot files. Optional, defaults to
1196       #   false.
1197       # - dest: the archive file to generate.
1198       # - prefix: prefix for archive entries (default to nil).
1199       # 
1200       # Example
1201       # 
1202       #  - zip:
1203       #      excludes: ["build/**/*", "**/*~"]
1204       #      dest:     :zip_archive
1205       # 
1206       # Note
1207       # 
1208       # If archive already exists, files are added to the archive.
1209       def zip(parameters)
1210         require 'zip/zip'
1211         params_desc = {
1212           :root     => { :mandatory => false, :type => :string },
1213           :includes => { :mandatory => false, :type => :string_or_array,
1214                          :default   => "**/*"},
1215           :excludes => { :mandatory => false, :type => :string_or_array,
1216                          :default => nil },
1217           :dotmatch => { :mandatory => false, :type => :boolean,
1218                          :default => false },
1219           :dest     => { :mandatory => true,  :type => :string },
1220           :prefix   => { :mandatory => false, :type => :string }
1221         }
1222         check_parameters(parameters, params_desc)
1223         root     = parameters[:root]
1224         includes = parameters[:includes]
1225         excludes = parameters[:excludes]
1226         dotmatch = parameters[:dotmatch]
1227         dest     = parameters[:dest]
1228         prefix   = parameters[:prefix]
1229         files = filter_files(root, includes, excludes, dotmatch)
1230         # build the archive
1231         puts "Building ZIP archive '#{dest}'"
1232         begin
1233           Zip::ZipFile.open(dest, Zip::ZipFile::CREATE) do |zip|
1234             for file in files
1235               path = (root == nil ? file : File.join(root, file))
1236               entry = prefix ? File.join(prefix, file) : file
1237               puts "Adding '#{entry}'" if @verbose
1238               zip.add(entry, path)
1239             end
1240             zip.close
1241           end
1242         rescue Exception
1243           error "Error building ZIP archive: #{$!}"
1244         end
1245       end
1246 
1247       # Extract ZIP archive to a destination directory. Existing extracted
1248       # files are not overwritten and result in an error. Parameter is a Hash
1249       # with following entries:
1250       #
1251       # - src: archive to extract.
1252       # - dest: destination directory for extracted files. Optional, defaults
1253       #   to current directory.
1254       #
1255       # Example
1256       #
1257       #   - unzip:
1258       #       src:  myarchive.zip
1259       #       dest: mydir
1260       def unzip(parameters)
1261         require 'zip/zip'
1262         params_desc = {
1263           :src  => { :mandatory => true,  :type => :string },
1264           :dest => { :mandatory => false, :type => :string, :default => '.' }
1265         }
1266         check_parameters(parameters, params_desc)
1267         src  = parameters[:src]
1268         dest = parameters[:dest]
1269         error "unzip 'src' parameter must be an readable ZIP archive" unless
1270           File.exists?(src) and File.readable?(src)
1271         FileUtils.makedirs(dest) if not File.exists?(dest)
1272         puts "Extracting ZIP file '#{src}' to '#{dest}'"
1273         begin
1274           Zip::ZipFile.foreach(src) do |entry|
1275             puts "Writing '#{entry}'" if @verbose
1276             tofile = File.join(dest, entry.name)
1277             if entry.file?
1278               dir = File.dirname(tofile)
1279               FileUtils.makedirs(dir) if not File.exists?(dir)
1280               entry.extract(tofile)
1281             elsif entry.directory?
1282               FileUtils.makedirs(tofile)
1283             end
1284           end
1285         rescue Exception
1286           error "Error extracting ZIP archive: #{$!}"
1287         end
1288       end
1289       
1290       # Generate a TAR archive. Parameter is a Hash with following entries:
1291       # 
1292       # - root: root directory for files to include. Defaults to current
1293       #   directory.
1294       # - includes: glob or list of globs for files to select for the archive.
1295       #   Defaults to '**/*' to include all files recursively.
1296       # - excludes: glob or list of globs for files to exclude from the archive.
1297       #   Defaults to nil to exclude no file.
1298       # - dotmatch: tells if joker matches dot files. Optional, defaults to
1299       #   false.
1300       # - dest: the archive file to generate.
1301       # 
1302       # Example
1303       # 
1304       #  - tar:
1305       #      includes: "**/*"
1306       #      excludes: ["build", "build/**/*", "**/*~"]
1307       #      dest: :tar_archive
1308       # 
1309       # Note
1310       # 
1311       # If archive already exists, it's overwritten.
1312       def tar(parameters)
1313         require 'archive/tar/minitar'
1314         # parse parameters
1315         params_desc = {
1316           :root     => { :mandatory => false, :type => :string },
1317           :includes => { :mandatory => true,  :type => :string_or_array },
1318           :excludes => { :mandatory => false, :type => :string_or_array,
1319                          :default => nil },
1320           :dotmatch => { :mandatory => false, :type => :boolean,
1321                          :default => false },
1322           :dest     => { :mandatory => true,  :type => :string }
1323         }
1324         check_parameters(parameters, params_desc)
1325         root     = parameters[:root]
1326         includes = parameters[:includes]
1327         excludes = parameters[:excludes]
1328         dotmatch = parameters[:dotmatch]
1329         dest     = parameters[:dest]
1330         files = filter_files(root, includes, excludes, dotmatch)
1331         # build the archive
1332         puts "Processing TAR archive '#{dest}'"
1333         begin
1334           current_dir = Dir.pwd
1335           abs_dest = File.expand_path(dest)
1336           Dir.chdir(root) if root
1337           Archive::Tar::Minitar::Output.open(abs_dest) do |tarfile|
1338             for file in files
1339               puts "Adding '#{file}'" if @verbose
1340               Archive::Tar::Minitar.pack_file(file, tarfile)
1341             end
1342           end
1343         rescue Exception
1344           error "Error generating TAR archive: #{$!}"
1345         ensure
1346           Dir.chdir(current_dir)
1347         end
1348       end
1349       
1350       # Generate a GZIP archive for a given file. Parameter is a Hash with
1351       # following entries:
1352       # 
1353       # - src: source file to generate GZIP for.
1354       # - dest: GZIP file to generate. Defaults to the src file with '.gz'
1355       #   extension added.
1356       # 
1357       # Example
1358       # 
1359       #  - gzip:
1360       #      src: "dist.tar"
1361       #      dest: "dist.tar.gz"
1362       def gzip(parameters)
1363         require 'zlib'
1364         # parse parameters
1365         params_desc = {
1366           :src  => { :mandatory => true,  :type => :string },
1367           :dest => { :mandatory => false, :type => :string }
1368         }
1369         check_parameters(parameters, params_desc)
1370         src  = parameters[:src]
1371         dest = parameters[:dest]
1372         dest = src + '.gz' if not dest
1373         # compress file
1374         puts "Processing GZIP archive '#{dest}'"
1375         begin
1376           File.open(src) do |input|
1377             output = Zlib::GzipWriter.new(File.open(dest, 'wb'))
1378             output.write(input.read)
1379             output.close
1380           end
1381         rescue Exception
1382           error "Error generating GZIP archive: #{$!}"
1383         end
1384       end
1385 
1386       # Expand a GZIP archive for a given file. Parameter is a Hash with
1387       # following entries:
1388       # 
1389       # - src: GZIP file to expand.
1390       # - dest: destination for expanded file. Destination file can be guessed
1391       #   (and thus omitted) for src files '.gz', '.gzip' and '.tgz';
1392       #   corresponding dest for latest will be '.tar'.
1393       # 
1394       # Example
1395       # 
1396       #  - gunzip:
1397       #      src: "dist.tar.gz"
1398       #      dest: "dist.tar"
1399       def gunzip(parameters)
1400         require 'zlib'
1401         # parse parameters
1402         params_desc = {
1403           :src  => { :mandatory => true,  :type => :string },
1404           :dest => { :mandatory => false, :type => :string }
1405         }
1406         check_parameters(parameters, params_desc)
1407         src  = parameters[:src]
1408         dest = parameters[:dest]
1409         error "gunzip 'src' parameter must be an readable GZIP archive" unless
1410           File.exists?(src) and File.readable?(src)
1411         if not dest
1412           if src =~ /.*\.gz$/
1413             dest = src[0..-4]
1414           elsif src =~ /.*\.gzip$/
1415             dest = src[0..-6]
1416           elsif src =~ /.*\.tgz/
1417             dest = src[0..-5]+'.tar'
1418           else
1419             error "gunzip can't guess 'dest' parameter from 'src' file name"
1420           end
1421         end
1422         # expand file
1423         puts "Expanding GZIP archive '#{dest}'"
1424         begin
1425           Zlib::GzipReader.open(src) do |input|
1426             output = File.open(dest, 'wb')
1427             output.write(input.read)
1428             output.close
1429           end
1430         rescue Exception
1431           error "Error expanding GZIP archive: #{$!}"
1432         end
1433       end
1434 
1435       # Generate a TAR.GZ archive. Parameter is a Hash with following entries:
1436       # 
1437       # - root: root directory for files to include. Defaults to current 
1438       #   directory.
1439       # - includes: glob or list of globs for files to select for the archive.
1440       #   Defaults to '**/*' to include all files recursively.
1441       # - excludes: glob or list of globs for files to exclude from the archive.
1442       #   Defaults to nil to exclude no file.
1443       # - dotmatch: tells if joker matches dot files. Optional, defaults to
1444       #   false.
1445       # - dest: the archive file to generate.
1446       # 
1447       # Example
1448       # 
1449       #  - targz:
1450       #      excludes: ["build/**/*", "**/*~"]
1451       #      dest:     :targz_archive
1452       # 
1453       # Note
1454       # 
1455       # If archive already exists, it's overwritten.
1456       def targz(parameters)
1457         require 'archive/tar/minitar'
1458         require 'zlib'
1459         # parse parameters
1460         params_desc = {
1461           :root     => { :mandatory => false, :type => :string,
1462                          :default => '.' },
1463           :includes => { :mandatory => false, :type => :string_or_array,
1464                          :default => '**/*' },
1465           :excludes => { :mandatory => false, :type => :string_or_array,
1466                          :default => nil },
1467           :dotmatch => { :mandatory => false, :type => :boolean,
1468                          :default => false },
1469           :dest     => { :mandatory => true,  :type => :string }
1470         }
1471         check_parameters(parameters, params_desc)
1472         root     = parameters[:root]
1473         includes = parameters[:includes]
1474         excludes = parameters[:excludes]
1475         dotmatch = parameters[:dotmatch]
1476         dest     = parameters[:dest]
1477         files = filter_files(root, includes, excludes, dotmatch)
1478         # build the archive
1479         puts "Building TARGZ archive '#{dest}'"
1480         begin
1481           current_dir = Dir.pwd
1482           abs_dest = File.expand_path(dest)
1483           Dir.chdir(root) if root
1484           Archive::Tar::Minitar::Output.
1485             open(Zlib::GzipWriter.new(File.open(abs_dest, 'wb'))) do |tgz|
1486             for file in files
1487               puts "Adding '#{file}'" if @verbose
1488               Archive::Tar::Minitar.pack_file(file, tgz)
1489             end
1490           end
1491         rescue Exception
1492           error "Error generating TARGZ archive: #{$!}"
1493         ensure
1494           Dir.chdir(current_dir)
1495         end
1496       end
1497 
1498       # Extract TAR archive to a destination directory. Gziped archives are
1499       # managed if their extension is '.tgz' or '.tar.gz'. Extracted files 
1500       # are overwritten if they already exist. Parameter is a Hash with
1501       # following entries:
1502       #
1503       # - src: archive to extract.
1504       # - dest: destination directory for extracted files. Optional, defaults
1505       #   to current directory.
1506       #
1507       # Example
1508       #
1509       #   - untar:
1510       #       src:  myarchive.tar.gz
1511       #       dest: mydir
1512       def untar(parameters)
1513         require 'archive/tar/minitar'
1514         require 'zlib'
1515         params_desc = {
1516           :src  => { :mandatory => true,  :type => :string },
1517           :dest => { :mandatory => false, :type => :string, :default => '.' }
1518         }
1519         check_parameters(parameters, params_desc)
1520         src  = parameters[:src]
1521         dest = parameters[:dest]
1522         error "untar 'src' parameter must be an readable TAR archive" unless
1523           File.exists?(src) and File.readable?(src)
1524         FileUtils.makedirs(dest) if not File.exists?(dest)
1525         puts "Extracting TAR file '#{src}' to '#{dest}'"
1526         begin
1527           if src =~ /\.tar\.gz$/ or src =~ /\.tgz$/
1528             tgz = Zlib::GzipReader.new(File.open(src, 'rb'))
1529             Archive::Tar::Minitar.unpack(tgz, dest)
1530           else
1531             Archive::Tar::Minitar.unpack(src, dest)
1532           end
1533         rescue Exception
1534           error "Error extracting TAR archive: #{$!}"
1535         end
1536       end
1537       
1538       ######################################################################
1539       #                             FTP TASKS                              #
1540       ######################################################################
1541 
1542       # Login to a remote FTP site. Useful to test a connection. Raises a
1543       # build error if connection fails. Parameter is a hash with following
1544       # entries:
1545       #
1546       # - username: the username to connect to FTP. Defaults to anonymous.
1547       # - password: the password to connect to FTP. Defaults to no password.
1548       # - host: the hostname to connect to.
1549       #
1550       # Example
1551       #
1552       #   - ftp_login:
1553       #       username: foo
1554       #       password: bar
1555       #       host:     example.com
1556       def ftp_login(params)
1557         params_desc = {
1558           :username => { :mandatory => false, :type => :string },
1559           :password => { :mandatory => false, :type => :string },
1560           :host     => { :mandatory => true,  :type => :string }
1561         }
1562         check_parameters(params, params_desc)
1563         username = params[:username]
1564         password = params[:password]
1565         host     = params[:host]
1566         begin
1567           Net::FTP.open(host) do |ftp|
1568             ftp.login(username, password)
1569             ftp.close
1570           end
1571           puts "Connection to FTP host '#{host}' sucessful"
1572         rescue Exception
1573           error "Error connecting to FTP host: #{$!}"
1574         end
1575       end
1576 
1577       # Get a file from a remote FTP site. Raises a build error this operation
1578       # fails. Parameter is a hash with following entries:
1579       #
1580       # - username: the username to connect to FTP. Defaults to anonymous.
1581       # - password: the password to connect to FTP. Defaults to no password.
1582       # - host: the hostname to connect to.
1583       # - file: the FTP path to remote file to get.
1584       # - output: the local path to file to write. Defaults to same file name
1585       #   in current directory.
1586       # - binary: sets the binary mode for download. Defaults to true.
1587       #
1588       # Example:
1589       #
1590       #   - ftp_get:
1591       #       username: foo
1592       #       password: bar
1593       #       host: foo
1594       #       file: test.txt
1595       def ftp_get(params)
1596         params_desc = {
1597           :username => { :mandatory => false, :type => :string },
1598           :password => { :mandatory => false, :type => :string },
1599           :host     => { :mandatory => true,  :type => :string },
1600           :file     => { :mandatory => true,  :type => :string },
1601           :output   => { :mandatory => false, :type => :string },
1602           :binary   => { :mandatory => false, :type => :boolean,
1603                          :default   => true }
1604         }
1605         check_parameters(params, params_desc)
1606         username = params[:username]
1607         password = params[:password]
1608         host     = params[:host]
1609         file     = params[:file]
1610         output   = params[:output]||File.basename(file)
1611         binary   = params[:binary]
1612         basename = File.basename(file)
1613         puts "Getting file '#{basename}'..."
1614         begin
1615           Net::FTP.open(host) do |ftp|
1616             ftp.login(username, password)
1617             if binary
1618               ftp.getbinaryfile(file, output)
1619             else
1620               ftp.gettextfile(file, output)
1621             end
1622             ftp.close
1623           end
1624         rescue Exception
1625           error "Error getting file '#{basename}': #{$!}"
1626         end
1627       end
1628 
1629       # Put a file to a remote FTP site. Raises a build error this operation
1630       # fails. Parameter is a hash with following entries:
1631       #
1632       # - username: the username to connect to FTP. Defaults to anonymous.
1633       # - password: the password to connect to FTP. Defaults to no password.
1634       # - host: the hostname to connect to.
1635       # - file: locale file to send.
1636       # - tofile: remote file to write on remote server. Defaults to base name
1637       #   of local file.
1638       # - binary: sets the binary mode for upload. Defaults to true.
1639       #
1640       # Example:
1641       #
1642       #   - ftp_put:
1643       #       username: foo
1644       #       password: bar
1645       #       host: foo
1646       #       file: test.txt
1647       def ftp_put(params)
1648         params_desc = {
1649           :username => { :mandatory => false, :type => :string },
1650           :password => { :mandatory => false, :type => :string },
1651           :host     => { :mandatory => true,  :type => :string },
1652           :file     => { :mandatory => true,  :type => :string },
1653           :tofile   => { :mandatory => false, :type => :string },
1654           :binary   => { :mandatory => false, :type => :boolean,
1655                          :default   => true }
1656         }
1657         check_parameters(params, params_desc)
1658         username = params[:username]
1659         password = params[:password]
1660         host     = params[:host]
1661         file     = params[:file]
1662         tofile   = params[:tofile]
1663         binary   = params[:binary]
1664         basename = File.basename(file)
1665         puts "Putting file '#{basename}'..."
1666         begin
1667           Net::FTP.open(host) do |ftp|
1668             ftp.login(username, password)
1669             if binary
1670               ftp.putbinaryfile(file, tofile)
1671             else
1672               ftp.puttextfile(file, tofile)
1673             end
1674             ftp.close
1675           end
1676         rescue Exception
1677           error "Error putting file '#{basename}': #{$!}"
1678         end
1679       end
1680 
1681       # Make a directory on a remote FTP site. Raises a build error this
1682       # operation fails. Parameter is a hash with following entries:
1683       #
1684       # - username: the username to connect to FTP. Defaults to anonymous.
1685       # - password: the password to connect to FTP. Defaults to no password.
1686       # - host: the hostname to connect to.
1687       # - dir: the path of the remote directory to create.
1688       #
1689       # Example:
1690       #
1691       #   - ftp_mkdir:
1692       #       username: foo
1693       #       password: bar
1694       #       host: foo
1695       #       dir:  test
1696       def ftp_mkdir(params)
1697         params_desc = {
1698           :username  => { :mandatory => false, :type => :string },
1699           :password  => { :mandatory => false, :type => :string },
1700           :host      => { :mandatory => true,  :type => :string },
1701           :dir       => { :mandatory => true,  :type => :string }
1702         }
1703         check_parameters(params, params_desc)
1704         username  = params[:username]
1705         password  = params[:password]
1706         host      = params[:host]
1707         dir       = params[:dir]
1708         basename  = File.basename(dir)
1709         puts "Making directory '#{basename}'..."
1710         begin
1711           Net::FTP.open(host) do |ftp|
1712             ftp.login(username, password)
1713             ftp.mkdir(dir)
1714             ftp.close
1715           end
1716         rescue Exception
1717           error "Error making directory '#{basename}': #{$!}"
1718         end
1719       end
1720 
1721       ######################################################################
1722       #                             CONSTRUCTS                             #
1723       ######################################################################
1724     
1725       # If construct will evaluate the expression in the 'if' entry and run
1726       # block in the 'then' entry or 'else' entry accordingly.
1727       #
1728       # - if: the condition to evaluate. This is a Ruby expression (thus a 
1729       #   string) evaluated in the build context, a symbol that refers to a
1730       #   property or a boolean.
1731       # - then: block that is evaluated if condition in if is true.
1732       # - else: block that is evaluated if condition in if is false.
1733       #
1734       # Example
1735       #
1736       #   - if: RUBY_PLATFORM =~ /darwin/
1737       #     then:
1738       #     - print: Hello, I'm a Mac
1739       #     else:
1740       #     - print: Hello, I'm a PC
1741       def if
1742       end
1743 
1744       # While construct will run the block in the 'do' entry while the
1745       # condition in the 'while' entry is true.
1746       #
1747       # - while: the condition to evaluate. This is a Ruby expression evaluated
1748       #   in the build context.
1749       # - do: the block to run while the condition is true.
1750       #
1751       # Example:
1752       #
1753       #   - while: i > 0
1754       #     do:
1755       #     - print: :i
1756       #     - rb: i -= 1
1757       def while
1758       end
1759 
1760       # For construct iterates on a list in the 'in' entry, putting values in
1761       # a property which name is in the 'for' entry and running the block in
1762       # the 'do' entry for each value.
1763       #
1764       # - for: the name of the property which receives values of the iteration,
1765       #   as a string.
1766       # - in: a list on which to iterate. This can be a list, a ruby expression
1767       #   to evaluate in the context of the build to obtain the Enumerable on
1768       #   which to iterate or a symbol that refers to a property that is a list.
1769       # - do: the block to run at each iteration.
1770       #
1771       # Example
1772       #
1773       #   - for: file
1774       #     in:  [foo, bar]
1775       #     do:
1776       #       - print: "Creating #{file}..."
1777       #       - touch: :file
1778       #
1779       # The same using a reference to a property that is a list:
1780       #
1781       #   - properties:
1782       #       list: ['foo', 'bar']
1783       #
1784       #   - target: test
1785       #     script:
1786       #     - for: name
1787       #       in:  :list
1788       #       do:
1789       #       - print: "Hi #{name}!"
1790       #
1791       # To iterate five times, we could write (using a Ruby Range):
1792       #
1793       #   - for: i
1794       #     in:  (1..5)
1795       #     do:
1796       #       - print: :i
1797       #
1798       # To iterate on files in current directory, we could write:
1799       #
1800       #   - for: file
1801       #     in:  "Dir.glob('*')"
1802       #     do:
1803       #       - print: :file
1804       def for
1805       end
1806 
1807       # Try construct will run the block in the 'try' entry and will switch to
1808       # block in the 'catch' entry if an error occurs.
1809       #
1810       # - try: the block to run.
1811       # - catch: the block to switch to if an error occurs.
1812       #
1813       # Example:
1814       #
1815       #   - try:
1816       #     - print: "In the try block"
1817       #     - throw: "Something went terribly wrong!"
1818       #     catch:
1819       #     - print: "An error occured"
1820       def try
1821       end
1822 
1823     end
1824 
1825   end
1826 
1827 end
1828 

Generated on Fri Oct 09 02:07:49 +0200 2015 with rcov 1.0.0