require 'singleton' require 'optparse' require 'hpricot' module XPGrep def self.grep(expr, io) h = Hpricot::XML(io) h.search(expr) end module CLI Options = Struct.new(:print_filename, :files_with_matches, :files_without_match, :attribute) class Options include Singleton def files_with_matches=(bool) self[:files_with_matches] = !!bool self[:files_without_match] = !bool end def files_with_matches! self.files_with_matches = true end def files_without_match=(bool) self[:files_without_match] = !!bool self[:files_with_matches] = !bool end def files_without_match! self.files_without_match = true end end class UI def initialize(file_count, out = STDOUT, err = STDERR) @file_count = file_count @out, @err = out, err end def puts(*args) @out.puts *args end # Prints a single matched node, possibly prefixed with the filename in # which it occurs. If more than one file was given on the command line, # or -H was passed, print it. # # @param fn the filename in which the match occurs # @param match an Hpricot node from a set returned by #search # @return nil def print_match(fn, match) print_filename = Options.instance.print_filename if (@file_count > 1 and print_filename.nil?) or print_filename @out.print "#{fn}: " end if (attribute = Options.instance.attribute) @out.puts match[attribute] else @out.puts match end end # Prints the error message from an exception, prefixed with the program # name. # # @param exception the exception object # @param prefix optional prefix to print. To disable, pass # something falsey. Defaults to $0. def perror(exception, prefix = $0) @err.print "#{prefix}: " if prefix @err.puts exception.message end end def self.parse_opts!(args = ARGV) @options = Options.instance OptionParser.new do |o| o.banner = "Usage: #{$0} [options] " o.on('-H', '--with-filename', "print the filename for each match") { @options.print_filename = true } o.on('-h', '--no-filename', "suppress printing filename for matches") { @options.print_filename = false } o.on('-l', '--files-with-matches', "only print filenames containing matches") { @options.files_with_matches! } o.on('-L', '--files-without-match', "only print filenames containing no match") { @options.files_without_match! } o.on('-e', '--extract ATTR') {|attr| @options[:attribute] = attr } o.on_tail('--help', "print this message and exit") { puts o; exit } end.parse!(args) end def self.run(args = ARGV) parse_opts! expr = args.shift @ui = UI.new(args.size) args.each do |fn| begin File.open(fn) do |file| nodes = XPGrep.grep(expr, file) if @options.files_with_matches @ui.puts fn unless nodes.empty? elsif @options.files_without_match @ui.puts fn if nodes.empty? else nodes.each {|node| @ui.print_match(fn, node) } end end rescue Errno::ENOENT, Errno::EISDIR => ex @ui.perror ex next end end end end end