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

- old
+ new

@@ -5,10 +5,27 @@ require "uri/open-sftp" class Hash + class << self + + # :call-seq: + # Hash.from_java_properties(string) + # + # Returns a hash from a string in the Java properties file format. For example: + # str = "foo=bar\nbaz=fab" + # Hash.from_properties(str) + # => { "foo"=>"bar", "baz"=>"fab" }.to_properties + def from_java_properties(string) + string.gsub(/\\\n/, "").split("\n").select { |line| line =~ /^[^#].*=.*/ }. + map { |line| line.gsub(/\\[trnf\\]/) { |escaped| {?t=>"\t", ?r=>"\r", ?n=>"\n", ?f=>"\f", ?\\=>"\\"}[escaped[1]] } }. + inject({}) { |hash, line| name, value = line.split("=") ; hash[name] = value ; hash } + end + + end + # :call-seq: # only(keys*) => hash # # Returns a new hash with only the specified keys. # @@ -30,10 +47,24 @@ # => { :a=>1, :c=>3 } def except(*keys) self.inject({}) { |hash, pair| hash[pair[0]] = pair[1] unless keys.include?(pair[0]) ; hash } end + # :call-seq: + # to_java_properties() => string + # + # Convert hash to string format used for Java properties file. For example: + # { "foo"=>"bar", "baz"=>"fab" }.to_properties + # => foo=bar + # baz=fab + def to_java_properties() + keys.sort.map { |key| + value = self[key].gsub(/[\t\r\n\f\\]/) { |escape| "\\" + {"\t"=>"t", "\r"=>"r", "\n"=>"n", "\f"=>"f", "\\"=>"\\"}[escape] } + "#{key}=#{value}" + }.join("\n") + end + end module Buildr @@ -42,21 +73,79 @@ # for example: # options.proxy.http = "http://proxy.acme.com:8080" # options.java_args = "-Xmx512M" class Options + # We use this to present environment variable as arrays. + class EnvArray < Array #:nodoc: + + def initialize(name) + @name = name.upcase + replace((ENV[@name] || ENV[@name.downcase] || "").split(/\s*,\s*/).reject(&:empty?)) + end + + (Array.instance_methods - Object.instance_methods - Enumerable.instance_methods).sort.each do |method| + class_eval %{def #{method}(*args, &block) ; result = super ; write ; result ; end} + end + + private + + def write() + ENV[@name.downcase] = nil + ENV[@name] = map(&:to_s).join(",") + end + + end + + # Wraps around the proxy environment variables: + # * :http -- HTTP_PROXY + # * :exclude -- NO_PROXY + class Proxies + + # Returns the HTTP_PROXY URL. + def http() + ENV["HTTP_PROXY"] || ENV["http_proxy"] + end + + # Sets the HTTP_PROXY URL. + def http=(url) + ENV["http_proxy"] = nil + ENV["HTTP_PROXY"] = url + end + + # Returns list of hosts to exclude from proxying (NO_PROXY). + def exclude() + @exclude ||= EnvArray.new("NO_PROXY") + end + + # Sets list of hosts to exclude from proxy (NO_PROXY). Accepts host name, array of names, + # or nil to clear the list. + def exclude=(url) + exclude.clear + exclude.concat [url].flatten if url + exclude + end + + end + # :call-seq: # proxy() => options # # Returns the proxy options. Currently supported options are: # * :http -- HTTP proxy for use when downloading. + # * :exclude -- Do not use proxy for these hosts/domains. # # For example: # options.proxy.http = "http://proxy.acme.com:8080" # You can also set it using the environment variable HTTP_PROXY. + # + # You can exclude individual hosts from being proxied, or entire domains, for example: + # options.proxy.exclude = "optimus" + # options.proxy.exclude = ["optimus", "prime"] + # options.proxy.exclude << "*.internal" def proxy() - @proxy ||= Struct.new(:http).new(ENV['HTTP_PROXY'] || ENV['http_proxy']) + @proxy ||= Proxies.new end end class << self @@ -189,30 +278,49 @@ # each source file, with the file name and content, returning the modified content. # # Without any mapping, the filter simply copies files from the source directory into the target # directory. # + # A filter has one target directory, but you can specify any number of source directories, + # either when creating the filter or calling #from. Include/exclude patterns are specified + # relative to the source directories, so: + # filter.include "*.png" + # will only include PNG files from any of the source directories. + # # See Buildr#filter. class Filter def initialize() #:nodoc: @include = [] @exclude = [] + @sources = [] end - # The source directory as a file task. - attr_accessor :source + # Returns the list of source directories (each being a file task). + attr_reader :sources + # *Deprecated* Use #sources instead. + def source() + warn_deprecated "Please use sources instead." + @sources.first + end + + # *Deprecated* Use #from instead. + def source=(dir) + warn_deprecated "Please use from instead." + from(dir) + end + # :call-seq: - # from(dir) => self + # from(*sources) => self # - # Sets the source directory from which files are copied and returns self. + # Adds additional directories from which to copy resources. # # For example: # filter.from("src").into("target").using("build"=>Time.now) - def from(dir) - @source = file(File.expand_path(dir.to_s)) + def from(*sources) + @sources |= sources.flatten.map { |dir| file(File.expand_path(dir.to_s)) } self end # The target directory as a file task. attr_reader :target @@ -275,39 +383,44 @@ # :call-seq: # run() => boolean # # Runs the filter. def run() - 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 source directory specified, where am I going to find the files to filter?" if sources.empty? + sources.each { |source| 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 } + copy_map = sources.flatten.map(&:to_s).inject({}) do |map, source| + base = Pathname.new(source) + files = FileList[File.join(source, "**/*")].reject { |file| File.directory?(file) }. + map { |file| Pathname.new(file).relative_path_from(base).to_s }. + select { |file| @include.empty? || @include.any? { |pattern| File.fnmatch(pattern, file) } }. + reject { |file| @exclude.any? { |pattern| File.fnmatch(pattern, file) } } + files.each do |file| + src, dest = File.expand_path(file, source), File.expand_path(file, target.to_s) + map[file] = src if !File.exist?(dest) || File.stat(src).mtime > File.stat(dest).mtime + end + map + end + return false if copy_map.empty? verbose(Rake.application.options.trace || false) do mkpath target.to_s - copy_map.each do |dest, src| + copy_map.each do |path, source| + dest = File.expand_path(path, target.to_s) 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 }) + mapped = mapping.call(path, File.open(source, "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 }. + mapped = File.open(source, "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 + cp source, 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 @@ -321,24 +434,24 @@ end end # :call-seq: - # filter(source) => Filter + # filter(*source) => Filter # - # Creates a filter that will copy files from the source directory into the target directory. + # Creates a filter that will copy files from the source directory(ies) into the target directory. # You can extend the filter to modify files by mapping <tt>${key}</tt> into values in each # of the copied files, and by including or excluding specific files. # # A filter is not a task, you must call the Filter#run method to execute it. # # For example, to copy all files from one directory to another: # filter("src/files").into("target/classes").run # To include only the text files, and replace each instance of <tt>${build}</tt> with the current # date/time: # filter("src/files").into("target/classes").include("*.txt").using("build"=>Time.now).run - def filter(source) - Filter.new.from(source) + def filter(*sources) + Filter.new.from(*sources) end end