module JGrep
Public Class Methods
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
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
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
# File lib/jgrep.rb, line 14 def self.flatten_on @flatten = true end
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
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
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
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
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
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
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
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
# File lib/jgrep.rb, line 10 def self.verbose_on @verbose = true end