# = multiglob.rb
#
# CREDIT Tom Sawyer

# Like +glob+ but can take multiple patterns.
#
#   Dir.multiglob( '*.rb', '*.py' )
#
# Rather then constants for options multiglob accepts a trailing options
# hash of symbol keys.
#
#   :noescape    File::FNM_NOESCAPE
#   :casefold    File::FNM_CASEFOLD
#   :pathname    File::FNM_PATHNAME
#   :dotmatch    File::FNM_DOTMATCH
#   :strict      File::FNM_PATHNAME && File::FNM_DOTMATCH
#
# It also has an option for recurse.
#
#   :recurse     Recurively include contents of directories.
#
# For example
#
#   Dir.multiglob( '*', :recurse => true )
#
# would have the same result as
#
#   Dir.multiglob('**/*')
#
# Multiglob also accepts '+' and '-' prefixes. Any entry that begins with a '-'
# is treated as an exclusion glob and will be removed from the final result.
# For example, to collect all files in the current directory, less ruby scripts:
#
#   Dir.multiglob( '*', '-*.rb' )
#
# This is very useful in collecting files as specificed by a configuration
# parameter.

def Dir.multiglob( *patterns )
  options  = (Hash === patterns.last ? patterns.pop : {})

  bitflags = 0
  bitflags |= File::FNM_NOESCAPE if options[:noescape]
  bitflags |= File::FNM_CASEFOLD if options[:casefold]
  bitflags |= File::FNM_PATHNAME if options[:pathname] or options[:strict]
  bitflags |= File::FNM_DOTMATCH if options[:dotmatch] or options[:strict]

  patterns = [patterns].flatten.compact

  patterns_include = patterns.select{ |f| f !~ /^[-]/ }
  patterns_exclude = patterns.select{ |f| f =~ /^[-]/ }

  patterns_include.collect!{ |f| f =~ /^[+]/ ? f[1..-1] : f }
  patterns_exclude.collect!{ |f| f =~ /^[-]/ ? f[1..-1] : f }

  if options[:recurse]
    patterns_include += patterns_include.collect{ |f| File.join(f, '**', '*') }
    patterns_exclude += patterns_exclude.collect{ |f| File.join(f, '**', '*') }
  end

  files = []
  files += patterns_include.collect{ |pattern| Dir.glob(pattern, bitflags) }.flatten.uniq
  files -= patterns_exclude.collect{ |pattern| Dir.glob(pattern, bitflags) }.flatten.uniq

  return files
end

# The same as +multiglob+, but recusively includes directories.
#
#   Dir.multiglob_r( 'folder' )
#
# is equivalent to
#
#   Dir.multiglob( 'folder', :recurse=>true )
#
# The effect of which is
#
#   Dir.multiglob( 'folder', 'folder/**/*' )

def Dir.multiglob_r( *patterns )
  options = (Hash === patterns.last ? patterns.pop : {})
  options[:recurse] = true
  patterns << options
  multiglob(*patterns)
end

# This is just like multiglob but handles a base pattern such that
# if the +patterns+ list starts with a '+' or '-' entry, then the base
# will be included in the result, otherwise it will be omitted.
#
#   Dir.multiglob_with_default('*.yaml', '-*.rb')  #=> [ 'foo.yaml' ]
#   Dir.multiglob_with_default('*.yaml', '+*.rb')  #=> [ 'foo.yaml', 'foo.rb' ]
#   Dir.multiglob_with_default('*.yaml', '*.rb')   #=> [ 'foo.rb' ]
#
# This is useful when a configuration option needs to supply a file list
# that may include files, exclude files or append files to a default list.

def Dir.multiglob_with_default(default, *patterns)
  default = [default].flatten.compact
  patterns = patterns.flatten.compact

  if patterns.empty?
    patterns = default
  elsif patterns.first =~ /^[+-]/
    patterns = default + patterns
  end

  multiglob(*patterns)
end

