| 
          #!/usr/bin/env ruby | 
        
        
           | 
          # encoding: utf-8 | 
        
        
           | 
          
 | 
        
        
           | 
          STDOUT.sync = true | 
        
        
           | 
          
 | 
        
        
           | 
          require 'time' | 
        
        
           | 
          require 'optparse' | 
        
        
           | 
          require 'ansi/mixin' | 
        
        
           | 
          require 'ansi/table' | 
        
        
           | 
          require 'json' | 
        
        
           | 
          
 | 
        
        
           | 
          class String | 
        
        
           | 
            include ANSI::Mixin | 
        
        
           | 
          end | 
        
        
           | 
          
 | 
        
        
           | 
          class Integer | 
        
        
           | 
            include ANSI::Mixin | 
        
        
           | 
            def ansi(*args) | 
        
        
           | 
              self.to_s.ansi(*args) | 
        
        
           | 
            end | 
        
        
           | 
          end | 
        
        
           | 
          
 | 
        
        
           | 
          $options = {} | 
        
        
           | 
          # OptionParser.new do |opts| | 
        
        
           | 
          #   opts.banner = "Usage: elasticat [options]" | 
        
        
           | 
          
 | 
        
        
           | 
          #   opts.on("-i", "--interval INTERVAL", "Set interval for date histogram facets") do |i| | 
        
        
           | 
          #     $options[:interval] = i | 
        
        
           | 
          #   end | 
        
        
           | 
          # end.parse! | 
        
        
           | 
          
 | 
        
        
           | 
          module Elasticat | 
        
        
           | 
            def decide(name, json, &block) | 
        
        
           | 
              puts "[!] ERROR: No handler for '#{name}' found".ansi(:red) unless respond_to?(name) | 
        
        
           | 
              self.send name, json if self.instance_eval(&block) | 
        
        
           | 
            end | 
        
        
           | 
          
 | 
        
        
           | 
            module Helpers | 
        
        
           | 
              def table(data, options={}, &format) | 
        
        
           | 
                ANSI::Table.new(data, options, &format) | 
        
        
           | 
              end | 
        
        
           | 
              def width | 
        
        
           | 
                Integer(ENV['COLUMNS'] || 80) | 
        
        
           | 
              end | 
        
        
           | 
              def humanize(string) | 
        
        
           | 
                string.to_s.gsub(/\_/, ' ').split.map { |s| s.capitalize}.join(' ') | 
        
        
           | 
              end | 
        
        
           | 
              def date(date, interval='day') | 
        
        
           | 
                case interval | 
        
        
           | 
                  when 'minute' | 
        
        
           | 
                    date.strftime('%a %m/%d %H:%M') + ' – ' + (date+60).strftime('%H:%M') | 
        
        
           | 
                  when 'hour' | 
        
        
           | 
                    date.strftime('%a %m/%d %H:%M') + ' – ' + (date+60*60).strftime('%H:%M') | 
        
        
           | 
                  when 'day' | 
        
        
           | 
                    date.strftime('%a %m/%d') | 
        
        
           | 
                  when 'week' | 
        
        
           | 
                    days_to_monday = date.wday!=0 ? date.wday-1 : 6 | 
        
        
           | 
                    days_to_sunday = date.wday!=0 ? 7-date.wday : 0 | 
        
        
           | 
                    start = (date - days_to_monday*24*60*60).strftime('%a %m/%d') | 
        
        
           | 
                    stop  = (date+(days_to_sunday*24*60*60)).strftime('%a %m/%d') | 
        
        
           | 
                    "#{start} – #{stop}" | 
        
        
           | 
                  when 'month' | 
        
        
           | 
                    date.strftime('%B %Y') | 
        
        
           | 
                  when 'year' | 
        
        
           | 
                    date.strftime('%Y') | 
        
        
           | 
                  else | 
        
        
           | 
                    date.strftime('%Y-%m-%d %H:%M') | 
        
        
           | 
                end | 
        
        
           | 
              end | 
        
        
           | 
              def ___ | 
        
        
           | 
                ('─'*Elasticat::Helpers.width).ansi(:faint) | 
        
        
           | 
              end | 
        
        
           | 
              extend self | 
        
        
           | 
            end | 
        
        
           | 
          
 | 
        
        
           | 
            module Actions | 
        
        
           | 
              include Helpers | 
        
        
           | 
          
 | 
        
        
           | 
              def display_allocation_on_nodes(json) | 
        
        
           | 
                json['routing_nodes']['nodes'].each do |id, shards| | 
        
        
           | 
                  puts (json['nodes'][id]['name'] || id).to_s.ansi(:bold) + " [#{id}]".ansi(:faint) | 
        
        
           | 
                  if shards.empty? | 
        
        
           | 
                    puts "No shards".ansi(:cyan) | 
        
        
           | 
                  else | 
        
        
           | 
                    puts table(shards.map do |shard| | 
        
        
           | 
                      [ | 
        
        
           | 
                        shard['index'], | 
        
        
           | 
                        shard['shard'].ansi( shard['primary'] ? :bold : :clear ), | 
        
        
           | 
                        shard['primary'] ? '◼'.ansi(:green) : '◻'.ansi(:yellow) | 
        
        
           | 
                      ] | 
        
        
           | 
                    end) | 
        
        
           | 
                  end | 
        
        
           | 
                end | 
        
        
           | 
          
 | 
        
        
           | 
                unless json['routing_nodes']['unassigned'].empty? | 
        
        
           | 
                  puts 'Unassigned: '.ansi(:faint, :yellow) + "#{json['routing_nodes']['unassigned'].size} shards" | 
        
        
           | 
                  puts table( json['routing_nodes']['unassigned'].map do |shard| | 
        
        
           | 
                    primary = shard['primary'] | 
        
        
           | 
          
 | 
        
        
           | 
                    [ | 
        
        
           | 
                      shard['index'], | 
        
        
           | 
                      shard['shard'].ansi( primary ? :bold : :clear ), | 
        
        
           | 
                      primary ? '◼'.ansi(:red) : '◻'.ansi(:yellow) | 
        
        
           | 
                    ] | 
        
        
           | 
                  end, border: false) | 
        
        
           | 
                end | 
        
        
           | 
              end | 
        
        
           | 
          
 | 
        
        
           | 
              def display_hits(json) | 
        
        
           | 
                hits = json['hits']['hits'] | 
        
        
           | 
                source = json['hits']['hits'].any? { |h| h['fields'] } ? 'fields' : '_source' | 
        
        
           | 
                properties = hits.map { |h| h[source] ? h[source].keys : nil  }.compact.flatten.uniq | 
        
        
           | 
                max_property_length = properties.map { |d| d.to_s.size }.compact.max.to_i + 1 | 
        
        
           | 
          
 | 
        
        
           | 
                hits.each_with_index do |hit, i| | 
        
        
           | 
                  title   = hit[source] && hit[source].select { |k, v| ['title', 'name'].include?(k) }.to_a.first | 
        
        
           | 
                  size_length = hits.size.to_s.size+2 | 
        
        
           | 
                  padding = size_length | 
        
        
           | 
          
 | 
        
        
           | 
                  puts "#{i+1}. ".rjust(size_length).ansi(:faint) + | 
        
        
           | 
                       " <#{hit['_id']}> " + | 
        
        
           | 
                       (title ? title.last.to_s.ansi(:bold) : ''), | 
        
        
           | 
                       ___ | 
        
        
           | 
          
 | 
        
        
           | 
                  ['_score', '_index', '_type'].each do |property| | 
        
        
           | 
                    puts ' '*padding + "#{property}: ".rjust(max_property_length+1).ansi(:faint) + hit[property].to_s if hit[property] | 
        
        
           | 
                  end | 
        
        
           | 
          
 | 
        
        
           | 
                  hit[source].each do |property, value| | 
        
        
           | 
                    puts ' '*padding + "#{property}: ".rjust(max_property_length+1).ansi(:faint) + value.to_s | 
        
        
           | 
                  end if hit[source] | 
        
        
           | 
          
 | 
        
        
           | 
                  # Highlight | 
        
        
           | 
                  if hit['highlight'] | 
        
        
           | 
                    puts "", ' '*(padding+max_property_length+1) + "Highlights".ansi(:faint), | 
        
        
           | 
                             ' '*(padding+max_property_length+1) + ('─'*10).ansi(:faint) | 
        
        
           | 
          
 | 
        
        
           | 
                    hit['highlight'].each do |property, matches| | 
        
        
           | 
                      print ' '*padding + "#{property}: ".rjust(max_property_length+1).ansi(:faint) | 
        
        
           | 
                      matches.each_with_index do |match, i| | 
        
        
           | 
                        print ' '*padding + ''.rjust(max_property_length+1)  if i > 0 | 
        
        
           | 
                        puts match.ansi(:faint).gsub(/\n/, ' ') | 
        
        
           | 
                                  .gsub(/<em>(.+)<\/em>/, '\1'.ansi(:clear, :bold)) | 
        
        
           | 
                                  .ansi(:faint) | 
        
        
           | 
                      end | 
        
        
           | 
                    end | 
        
        
           | 
                  end | 
        
        
           | 
          
 | 
        
        
           | 
                  puts "" | 
        
        
           | 
                end | 
        
        
           | 
                puts ___, "#{hits.size.to_s.ansi(:bold)} of #{json['hits']['total'].to_s.ansi(:bold)} results".ansi(:faint) | 
        
        
           | 
              end | 
        
        
           | 
          
 | 
        
        
           | 
              def display_terms_facets(json) | 
        
        
           | 
                facets =  json['facets'].select { |name, values| values['_type'] == 'terms' } | 
        
        
           | 
          
 | 
        
        
           | 
                facets.each do |name, values| | 
        
        
           | 
                  longest = values['terms'].map { |t| t['term'].size }.max | 
        
        
           | 
                  max     = values['terms'].map { |t| t['count'] }.max | 
        
        
           | 
                  padding = longest.to_i + max.to_s.size + 5 | 
        
        
           | 
                  ratio   = ((width)-padding)/max.to_f | 
        
        
           | 
          
 | 
        
        
           | 
                  puts "", "#{'Facet: '.ansi(:faint)}#{humanize(name)}", ___ | 
        
        
           | 
                  values['terms'].each_with_index do |value, i| | 
        
        
           | 
                    puts value['term'].ljust(longest).ansi(:bold) + | 
        
        
           | 
                         " [" + value['count'].to_s.rjust(max.to_s.size) + "] " + | 
        
        
           | 
                         " " + '█' * (value['count']*ratio).ceil | 
        
        
           | 
                  end | 
        
        
           | 
                end | 
        
        
           | 
              end | 
        
        
           | 
          
 | 
        
        
           | 
              def display_date_histogram_facets(json) | 
        
        
           | 
                facets = json['facets'].select { |name, values| values['_type'] == 'date_histogram' } | 
        
        
           | 
                facets.each do |name, values| | 
        
        
           | 
                  max     = values['entries'].map { |t| t['count'] }.max | 
        
        
           | 
                  padding = 27 | 
        
        
           | 
                  ratio   = ((width)-padding)/max.to_f | 
        
        
           | 
          
 | 
        
        
           | 
                  interval = $options[:interval] | 
        
        
           | 
                  puts "", "#{'Facet: '.ansi(:faint)}#{humanize(name)} #{interval ? ('(by ' + interval + ')').ansi(:faint) : ''}", ___ | 
        
        
           | 
                  values['entries'].each_with_index do |value, i| | 
        
        
           | 
                    puts date(Time.at(value['time'].to_i/1000).utc, interval).rjust(21).ansi(:bold) + | 
        
        
           | 
                         " [" + value['count'].to_s.rjust(max.to_s.size) + "] " + | 
        
        
           | 
                         " " + '█' * (value['count']*ratio).ceil | 
        
        
           | 
                  end | 
        
        
           | 
                end | 
        
        
           | 
              end | 
        
        
           | 
          
 | 
        
        
           | 
              def display_analyze_output(json) | 
        
        
           | 
                max_length = json['tokens'].map { |d| d['token'].to_s.size }.max | 
        
        
           | 
          
 | 
        
        
           | 
                puts table(json['tokens'].map do |t| | 
        
        
           | 
                             [ | 
        
        
           | 
                               t['position'], | 
        
        
           | 
                               t['token'].ljust(max_length+5).ansi(:bold), | 
        
        
           | 
                               "#{t['start_offset']}–#{t['end_offset']}", | 
        
        
           | 
                               t['type'] | 
        
        
           | 
                             ] | 
        
        
           | 
                           end) | 
        
        
           | 
              end | 
        
        
           | 
          
 | 
        
        
           | 
            end | 
        
        
           | 
          end | 
        
        
           | 
          
 | 
        
        
           | 
          unless ARGV.empty? | 
        
        
           | 
            # Running as `elasticurl` | 
        
        
           | 
            args    = ARGV.map { |d| d =~ /(^http)|(^{)/ ? d = "'#{d}'"  : d } | 
        
        
           | 
            command = "curl #{args.join(' ')}" | 
        
        
           | 
            input   = `#{command}` | 
        
        
           | 
          else | 
        
        
           | 
            # Running as piped `elasticat` | 
        
        
           | 
            input = STDIN.read | 
        
        
           | 
          end | 
        
        
           | 
          
 | 
        
        
           | 
          begin | 
        
        
           | 
            json  = JSON.load(input) | 
        
        
           | 
          rescue JSON::ParserError | 
        
        
           | 
            puts "[!] ERROR: Invalid json received".ansi(:red), | 
        
        
           | 
                 input.ansi(:faint) | 
        
        
           | 
            exit(1) | 
        
        
           | 
          end | 
        
        
           | 
          
 | 
        
        
           | 
          ___   = ('─'*Elasticat::Helpers.width).ansi(:faint) | 
        
        
           | 
          
 | 
        
        
           | 
          puts input.to_s.ansi(:faint), "" | 
        
        
           | 
          
 | 
        
        
           | 
          include Elasticat, Elasticat::Actions | 
        
        
           | 
          
 | 
        
        
           | 
          decide :display_allocation_on_nodes, json do | 
        
        
           | 
            json['routing_nodes'] && !json['routing_nodes'].empty? | 
        
        
           | 
          end | 
        
        
           | 
          
 | 
        
        
           | 
          decide :display_hits, json do | 
        
        
           | 
            json['hits'] && json['hits']['hits'] && !json['hits']['hits'].empty? | 
        
        
           | 
          end | 
        
        
           | 
          
 | 
        
        
           | 
          decide :display_terms_facets, json do | 
        
        
           | 
            json['facets'] && json['facets'].any? { |name, values| values['_type'] == 'terms' } | 
        
        
           | 
          end | 
        
        
           | 
          
 | 
        
        
           | 
          decide :display_date_histogram_facets, json do | 
        
        
           | 
            json['facets'] && json['facets'].any? { |name, values| values['_type'] == 'date_histogram' } | 
        
        
           | 
          end | 
        
        
           | 
          
 | 
        
        
           | 
          decide :display_analyze_output, json do | 
        
        
           | 
            json['tokens'].is_a?(Array) | 
        
        
           | 
          end | 
        
        
           | 
          
 | 
        
        
           | 
          puts ('▂'*Elasticat::Helpers.width).ansi(:faint) | 
        
  
@karmi I've added support for range and histogram facets: https://gist.github.com/felixbuenemann/6422175/revisions