module JGrep

Public Class Methods

dig_path(json, path) click to toggle source

Digs to a specific path in the json document and returns the value

# File lib/jgrep.rb, line 294
def self.dig_path(json, path)
  index = nil
  path = path.gsub(/^\./, "")

  if path =~ /(.*)\[(.*)\]/
    path = $1
    index = $2
  end

  return json if path == ""

  if json.is_a? Hash
    json.keys.each do |k|
      if path.start_with?(k) && k.include?(".")
        return dig_path(json[k], path.gsub(k, ""))
      end
    end
  end

  path_array = path.split(".")

  if path_array.first == "*"
    tmp = []

    json.each do |j|
      tmp << dig_path(j[1], path_array.drop(1).join("."))
    end

    return tmp
  end

  json = json[path_array.first] if json.is_a? Hash

  if json.is_a? Hash
    return json if path == path_array.first
    return dig_path(json, path.include?(".") ? path_array.drop(1).join(".") : path)

  elsif json.is_a? Array
    if path == path_array.first && (json.first.is_a?(Hash) && !json.first.keys.include?(path))
      return json
    end

    tmp = []

    json.each do |j|
      tmp_path = dig_path(j, (path.include?(".") ? path_array.drop(1).join(".") : path))
      tmp << tmp_path unless tmp_path.nil?
    end

    unless tmp.empty?
      return index ? tmp.flatten[index.to_i] : tmp
    end

  elsif json.nil?
    return nil

  else
    return json

  end
end
eval_statement(document, callstack) click to toggle source

Evaluates the call stack en returns true of selected document matches logical expression

# File lib/jgrep.rb, line 262
def self.eval_statement(document, callstack)
  result = []

  callstack.each do |expression|
    case expression.keys.first
    when "statement"
      if expression.values.first.is_a?(Array)
        result << has_complex?(document, expression.values.first)
      else
        result << has_object?(document, expression.values.first)
      end
    when "+"
      result << present?(document, expression.values.first)
    when "-"
      result << !present?(document, expression.values.first)
    when "and"
      result << "&&"
    when "or"
      result << "||"
    when "("
      result << "("
    when ")"
      result << ")"
    when "not"
      result << "!"
    end
  end

  eval(result.join(" ")) # rubocop:disable Security/Eval
end
filter_json(documents, filters) click to toggle source

Strips filters from json documents and returns those values as a less bloated json document

# File lib/jgrep.rb, line 73
def self.filter_json(documents, filters)
  result = []

  if filters.is_a? Array
    documents.each do |doc|
      tmp_json = {}

      filters.each do |filter|
        filtered_result = dig_path(doc, filter)
        unless (filtered_result == doc) || filtered_result.nil?
          tmp_json[filter] = filtered_result
        end
      end
      result << tmp_json
    end
  else
    documents.each do |r|
      filtered_result = dig_path(r, filters)

      unless (filtered_result == r) || filtered_result.nil?
        result << filtered_result
      end
    end
  end

  result.flatten if @flatten == true && result.size == 1

  result
end
flatten_on() click to toggle source
# File lib/jgrep.rb, line 14
def self.flatten_on
  @flatten = true
end
format(kvalue, value) click to toggle source

Correctly format values so we can do the correct type of comparison

# File lib/jgrep.rb, line 119
def self.format(kvalue, value)
  if kvalue.to_s =~ /^\d+$/ && value.to_s =~ /^\d+$/
    [Integer(kvalue), Integer(value)]
  elsif kvalue.to_s =~ /^\d+.\d+$/ && value.to_s =~ /^\d+.\d+$/
    [Float(kvalue), Float(value)]
  else
    [kvalue, value]
  end
end
has_complex?(document, compound) click to toggle source

Check if complex statement (defined as [key=value…]) is present over an array of key value pairs

# File lib/jgrep.rb, line 210
def self.has_complex?(document, compound)
  field = ""
  tmp = document
  result = []
  fresult = []

  compound.each do |token|
    if token[0] == "statement"
      field = token
      break
    end
  end

  field = field[1].split(/=|<|>/).first

  field.split(".").each_with_index do |item, _|
    tmp = tmp[item]

    return false if tmp.nil?

    next unless tmp.is_a?(Array)

    tmp.each do |doc|
      result = []

      compound.each do |token|
        case token[0]
        when "and"
          result << "&&"
        when "or"
          result << "||"
        when /not|\!/
          result << "!"
        when "statement"
          op = token[1].match(/.*<=|>=|=|<|>/)
          left = token[1].split(op[0]).first.split(".").last
          right = token[1].split(op[0]).last
          new_statement = left + op[0] + right
          result << has_object?(doc, new_statement)
        end
      end

      fresult << eval(result.join(" ")) # rubocop:disable Security/Eval
      (fresult << "||") unless doc == tmp.last
    end

    return eval(fresult.join(" ")) # rubocop:disable Security/Eval
  end
end
has_object?(document, statement) click to toggle source

Check if key=value is present in document

# File lib/jgrep.rb, line 157
def self.has_object?(document, statement)
  key, value = statement.split(/<=|>=|=|<|>/)

  if statement =~ /(<=|>=|<|>|=)/
    op = $1
  else
    op = statement
  end

  tmp = dig_path(document, key)

  tmp = tmp.first if tmp.is_a?(Array) && tmp.size == 1

  tmp, value = format(tmp, (value.gsub(/"|'/, "") unless value.nil?)) # rubocop:disable Style/FormatString

  # Deal with null comparison
  return true if tmp.nil? && value == "null"

  # Deal with booleans
  return true if tmp == true && value == "true"
  return true if tmp == false && value == "false"

  # Deal with regex matching
  if !tmp.nil? && value =~ /^\/.*\/$/
    tmp.match(Regexp.new(value.delete("/"))) ? (return true) : (return false)
  end

  # Deal with everything else
  case op
  when "="
    return tmp == value
  when "<="
    return tmp <= value
  when ">="
    return tmp >= value
  when ">"
    return tmp > value
  when "<"
    return tmp < value
  end
end
is_object_in_array?(document, statement) click to toggle source

Check if key=value is present in a sub array

# File lib/jgrep.rb, line 200
def self.is_object_in_array?(document, statement)
  document.each do |item|
    return true if has_object?(item, statement)
  end

  false
end
jgrep(json, expression, filters = nil, start = nil) click to toggle source

Parse json and return documents that match the logical expression Filters define output by limiting it to only returning a the listed keys. Start allows you to move the pointer indicating where parsing starts. Default is the first key in the document heirarchy

# File lib/jgrep.rb, line 22
def self.jgrep(json, expression, filters = nil, start = nil)
  errors = ""

  begin
    JSON.create_id = nil
    json = JSON.parse(json)
    json = [json] if json.is_a?(Hash)

    json = filter_json(json, start).flatten if start

    result = []

    if expression == ""
      result = json
    else
      call_stack = Parser.new(expression).execution_stack

      json.each do |document|
        begin
          result << document if eval_statement(document, call_stack)
        rescue Exception => e # rubocop:disable Lint/RescueException
          if @verbose
            require "pp"
            pp document
            STDERR.puts "Error - #{e} \n\n"
          else
            errors = "One or more the json documents could not be parsed. Run jgrep -v for to display documents"
          end
        end
      end
    end

    puts errors unless errors == ""

    return result unless filters

    filter_json(result, filters)
  rescue JSON::ParserError
    STDERR.puts "Error. Invalid JSON given"
  end
end
present?(document, statement) click to toggle source

Check if the json key that is defined by statement is defined in the json document

# File lib/jgrep.rb, line 130
def self.present?(document, statement)
  statement.split(".").each do |key|
    if document.is_a? Hash
      if document.value?(nil)
        document.each do |k, _|
          document[k] = "null" if document[k].nil?
        end
      end
    end

    if document.is_a? Array
      rval = false
      document.each do |doc|
        rval ||= present?(doc, key)
      end
      return rval
    end

    document = document[key]

    return false if document.nil?
  end

  true
end
validate_expression(expression) click to toggle source

Validates an expression, true when no errors are found else a string representing the issues

# File lib/jgrep.rb, line 65
def self.validate_expression(expression)
  Parser.new(expression)
  true
rescue
  $!.message
end
validate_filters(filters) click to toggle source

Validates if filters do not match any of the parser's logical tokens

# File lib/jgrep.rb, line 104
def self.validate_filters(filters)
  if filters.is_a? Array
    filters.each do |filter|
      if filter =~ /=|<|>|^and$|^or$|^!$|^not$/
        raise "Invalid field for -s filter : '#{filter}'"
      end
    end
  elsif filters =~ /=|<|>|^and$|^or$|^!$|^not$/
    raise "Invalid field for -s filter : '#{filters}'"
  end

  nil
end
verbose_on() click to toggle source
# File lib/jgrep.rb, line 10
def self.verbose_on
  @verbose = true
end