lib/core/common.rb in buildr-1.1.3 vs lib/core/common.rb in buildr-1.2.0

- old
+ new

@@ -1,12 +1,88 @@ require "tempfile" require "pathname" require "core/transports" +require "open-uri" +require "uri/open-sftp" + +class Hash + + # :call-seq: + # only(keys*) => hash + # + # Returns a new hash with only the specified keys. + # + # For example: + # { :a=>1, :b=>2, :c=>3, :d=>4 }.only(:a, :c) + # => { :b=>2, :d=>4 } + def only(*keys) + self.inject({}) { |hash, pair| hash[pair[0]] = pair[1] if keys.include?(pair[0]) ; hash } + end + + + # :call-seq: + # except(keys*) => hash + # + # Returns a new hash without the specified keys. + # + # For example: + # { :a=>1, :b=>2, :c=>3, :d=>4 }.except(:a, :c) + # => { :a=>1, :c=>3 } + def except(*keys) + self.inject({}) { |hash, pair| hash[pair[0]] = pair[1] unless keys.include?(pair[0]) ; hash } + end + +end + + module Buildr + # Collection of options for controlling Buildr. For example for running builds without running + # test cases, using a proxy server, JVM arguments, etc. You access this object by calling options, + # for example: + # options.proxy.http = "http://proxy.acme.com:8080" + # options.java_args = "-Xmx512M" + class Options + + # :call-seq: + # proxy() => options + # + # Returns the proxy options. Currently supported options are: + # * :http -- HTTP proxy for use when downloading. + # + # For example: + # options.proxy.http = "http://proxy.acme.com:8080" + # You can also set it using the environment variable HTTP_PROXY. + def proxy() + @proxy ||= Struct.new(:http).new(ENV['HTTP_PROXY'] || ENV['http_proxy']) + end + + end + + class << self + + # :call-seq: + # options() => Options + # + # Returns the Buildr options. See Options. + def options() + @options ||= Options.new + end + + end + # :call-seq: + # options() => Options + # + # Returns the Buildr options. See Options. + def options() + Buildr.options + end + + + # :call-seq: # struct(hash) => Struct # # Convenience method for creating an anonymous Struct. # # For example: @@ -74,31 +150,34 @@ # checksums on the server it will verify the download before saving it. # # For example: # download "image.jpg"=>"http://example.com/theme/image.jpg" def download(args) - if String === args || URI === args + args = URI.parse(args) if String === args + if URI === args # Given only a download URL, download into a temporary file. # You can infer the file from task name. - temp = Tempfile.new(File.basename(args.to_s)) - task = file_create(temp.path) do |task| - Transports.download task.source, task.name + temp = Tempfile.open(File.basename(args.to_s)) + file(temp.path).tap do |task| + # Since temporary file exists, force a download. + class << task ; def needed?() ; true ; end ; end + task.sources << args + task.enhance { args.download temp, :proxy=>Buildr.options.proxy } end - task.sources << args else # Download to a file created by the task. fail unless args.keys.size == 1 - url = args.values.first - task = file_create(args.keys.first) do |task| - mkpath File.dirname(task.name), :verbose=>false - Transports.download task.source, task.name + uri = URI.parse(args.values.first.to_s) + file_create(args.keys.first).tap do |task| + task.sources << uri + task.enhance { uri.download task.name, :proxy=>Buildr.options.proxy } end - task.sources << url end - task + end + # A filter knows how to copy files from one directory to another, applying mappings to the # contents of these files. # # You can specify the mapping using a Hash, and it will map ${key} fields found in each source # file into the appropriate value in the target file. For example: @@ -186,76 +265,63 @@ # Without any mapping, all files are copied as is. # # For example: # filter.using "version"=>"1.2" # will replace all occurrences of "${version}" with "1.2". - def using(mapping, &block) + def using(mapping = nil, &block) self.mapping = mapping || block self end + # :call-seq: + # run() => boolean + # # Runs the filter. def run() - if needed? - unless copy_map.empty? - verbose(Rake.application.options.trace || false) do - mkpath target.to_s - copy_map do |dest, src| - mkpath File.dirname(dest) rescue nil - case mapping - when Proc, Method # Call on input, accept output. - mapped = mapping.call(src, File.open(src, "rb") { |file| file.read }) - File.open(dest, "wb") { |file| file.write mapped } - when Hash # Map ${key} to value - mapped = File.open(src, "rb") { |file| file.read }. - gsub(/\$\{.*\}/) { |str| mapping[str[2..-2]] || str } - File.open(dest, "wb") { |file| file.write mapped } - when nil # No mapping. - cp src, dest - else - fail "Filter can be a hash (key=>value), or a proc/method; I don't understand #{mapping}" - end - end - touch target.to_s + raise "No source directory specified, where am I going to find the files to filter?" if source.nil? + raise "Source directory #{source} doesn't exist" unless File.exist?(source.to_s) + raise "No target directory specified, where am I going to copy the files to?" if target.nil? + + includes = @include.empty? ? ["*"] : @include + src_base = Pathname.new(source.to_s) + copy_map = Dir[File.join(source.to_s, "**/*")].reject { |file| File.directory?(file) }. + map { |src| Pathname.new(src).relative_path_from(src_base).to_s }. + select { |file| includes.any? { |pattern| File.fnmatch(pattern, file) } }. + reject { |file| @exclude.any? { |pattern| File.fnmatch(pattern, file) } }. + map { |file| [File.expand_path(file, target.to_s), File.expand_path(file, source.to_s)] }. + select { |dest, src| !File.exist?(dest) || File.stat(src).mtime > File.stat(dest).mtime } + return false if copy_map.empty? + + verbose(Rake.application.options.trace || false) do + mkpath target.to_s + copy_map.each do |dest, src| + mkpath File.dirname(dest) rescue nil + case mapping + when Proc, Method # Call on input, accept output. + relative = Pathname.new(src).relative_path_from(src_base).to_s + mapped = mapping.call(relative, File.open(src, "rb") { |file| file.read }) + File.open(dest, "wb") { |file| file.write mapped } + when Hash # Map ${key} to value + mapped = File.open(src, "rb") { |file| file.read }. + gsub(/\$\{[^}]*\}/) { |str| mapping[str[2..-2]] || str } + File.open(dest, "wb") { |file| file.write mapped } + when nil # No mapping. + cp src, dest + else + fail "Filter can be a hash (key=>value), or a proc/method; I don't understand #{mapping}" end end + touch target.to_s end + true end # Returns the target directory. def to_s() @target.to_s end - private - - def needed?() - return false if target.nil? || source.nil? || !File.exist?(source.to_s) - return true unless File.exist?(target.to_s) - !copy_map.empty? - end - - # Return a copy map of all the files that need copying: the key is the file to copy to, - # the value is the source file. If called with a block, yields with each dest/source pair. - def copy_map(&block) - unless @copy_map - @include = ["*"] if @include.empty? - base = Pathname.new(source.to_s) - @copy_map = Dir[File.join(source.to_s, "**/*")].reject { |file| File.directory?(file) }. - map { |src| Pathname.new(src).relative_path_from(base).to_s }. - select { |file| @include.any? { |pattern| File.fnmatch(pattern, file) } }. - reject { |file| @exclude.any? { |pattern| File.fnmatch(pattern, file) } }. - map { |file| [File.expand_path(file, target.to_s), File.expand_path(file, source.to_s)] }. - select { |dest, src| !File.exist?(dest) || File.stat(src).mtime > File.stat(dest).mtime } - end - if block_given? - @copy_map.each(&block) - else - @copy_map - end - end - end # :call-seq: # filter(source) => Filter # @@ -271,7 +337,40 @@ # date/time: # filter("src/files").into("target/classes").include("*.txt").using("build"=>Time.now).run def filter(source) Filter.new.from(source) end - + end + + +# Add a touch of colors (red) to warnings. +HighLine.use_color = PLATFORM !~ /win32/ +module Kernel #:nodoc: + + def warn_with_color(message) + warn_without_color $terminal.color(message.to_s, :red) + end + alias_method_chain :warn, :color + + # :call-seq: + # warn_deprecated(message) + # + # Use with deprecated methods and classes. This method automatically adds the file name and line number, + # and the text "Deprecated" before the message, and eliminated duplicate warnings. It only warns when + # running in verbose mode. + # + # For example: + # warn_deprecated "Please use new_foo instead of foo." + def warn_deprecated(message) #:nodoc: + return unless verbose + "#{caller[1]}: Deprecated: #{message}".tap do |message| + @deprecated ||= {} + unless @deprecated[message] + @deprecated[message] = true + warn message + end + end + end + +end +