module JGrep

require 'yajl/json_gem'

Public Class Methods

array_to_hash(documents, mark, key) click to toggle source

Convert a specific array inside a JSON document to a hash Mark is a string in the format foo.bar.baz that points to the hash in the document. Each element in the array will be turned into a hash in the format key => array

# File lib/jgrep.rb, line 114
def self.array_to_hash(documents, mark, key)

    begin
        documents = JSON.parse(documents)
    rescue JSON::ParserError => e
        STDERR.puts "Error. Invalid JSON given"
        exit 1
    end

    result = {}

    begin
        for i in 0..(documents.size - 1) do
            tmp = documents[i]
            unless mark == ""
                mark.split(".").each_with_index do |m,i|
                    tmp = tmp[m] unless i == mark.split(".").size - 1
                end
            end

            tmp[mark.split(".").last].each{|d| result[d[key]] = d}
            tmp[mark.split(".").last] = result

        end
    rescue Exception => e
        STDERR.puts "Error. Invalid position specified in JSON document"
        exit!
    end

    puts JSON.pretty_generate(documents)

end
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 380
def self.dig_path(json, path)
    index = nil
    path = path.gsub(/^\./, "")

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

    if path == ""
        return json
    end

    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
        if path == path_array.first
            return json
        else
            return dig_path(json, (path.include?('.') ? path_array.drop(1).join(".") : path))
        end

    elsif json.is_a? Array
        if path == path_array.first && (json.first.is_a?(Hash) && !(json.first.keys.include?(path)))
            return json
        else
            tmp = []
            json.each do |j|
                tmp_path = dig_path(j, (path.include?('.') ? path_array.drop(1).join(".") : path))
                unless tmp_path.nil?
                    tmp << tmp_path
                end
            end
            unless tmp.empty?
                (index) ? (return tmp.flatten[index.to_i]) : (return tmp)
            end
        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 349
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

    return eval(result.join(" "))
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 148
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

        result = result.flatten if (result.size == 1 && @flatten == true)
        return result

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

        result = result.flatten if (result.size == 1 && @flatten == true)
        return result
    end
end
flatten_on() click to toggle source
# File lib/jgrep.rb, line 17
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 196
def self.format(kvalue, value)
    if kvalue.to_s =~ /^\d+$/ && value.to_s =~ /^\d+$/
        return Integer(kvalue), Integer(value)
    elsif kvalue.to_s =~ /^\d+.\d+$/ && value.to_s =~ /^\d+.\d+$/
        return Float(kvalue), Float(value)
    else
        return 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 301
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, i|
        tmp = tmp[item]
        if tmp.nil?
            return false
        end
        if 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(" "))
                (fresult << "||") unless doc == tmp.last
            end
            return eval(fresult.join(" "))
        end
    end
end
has_object?(document, statement) click to toggle source

Check if key=value is present in document

# File lib/jgrep.rb, line 237
def self.has_object?(document, statement)

    key,value = statement.split(/<=|>=|=|<|>/)

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

    tmp = dig_path(document, key)

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

    tmp, value = format(tmp, (value.gsub(/"|'/, "") unless value.nil?))

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

    # Deal with booleans
    if tmp == true and value == 'true'
        return true
    elsif tmp == false and value == 'false'
        return true
    end

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

    #Deal with everything else
    case op
        when "="
            (tmp == value) ? (return true) : (return false)
        when "<="
            (tmp <= value) ? (return true) : (return false)
        when ">="
            (tmp >= value) ? (return true) : (return false)
        when ">"
            (tmp > value) ? (return true) : (return false)
        when "<"
            (tmp < value) ? (return true) : (return false)
    end
end
hash_to_array(documents, mark) click to toggle source

Convert a specific hash inside a JSON document to an array Mark is a string in the format foo.bar.baz that points to the array in the document.

# File lib/jgrep.rb, line 77
def self.hash_to_array(documents, mark)

    begin
        documents = JSON.parse(documents)
    rescue JSON::ParserError => e
        STDERR.puts "Error. Invalid JSON given"
        exit 1
    end

    result = []

    begin
        for i in 0..(documents.size - 1) do
            tmp = documents[i]
            unless mark == ""
                mark.split(".").each_with_index do |m,i|
                    tmp = tmp[m] unless i == mark.split(".").size - 1
                end
            end

            tmp[mark.split.last].each{|d| result << {"value" => d[1], "key" => d[0]}}
            tmp[mark.split.last] = result

        end
    rescue Exception => e
        STDERR.puts "Error. Invalid position specified in JSON document"
        exit!
    end

    puts JSON.pretty_generate(documents)

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 288
def self.is_object_in_array?(document, statement)

    document.each do |item|
        if has_object?(item,statement)
            return true
       end
    end

    return 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 25
def self.jgrep(json, expression, filters = nil, start = nil)
    errors = ""
    begin
        JSON.create_id = nil
        json = JSON.parse(json)
        if json.is_a? Hash
            json = [json]
        end

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

        result = []
        unless expression == ""
            call_stack = Parser.new(expression).execution_stack

            json.each do |document|
                begin
                    if eval_statement(document, call_stack)
                        result << document
                    end
                rescue Exception => e
                    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
        else
            result = json
        end

        unless errors == ""
            puts errors
        end

        unless filters
            return result
        else
            filter_json(result, filters)
        end

    rescue JSON::ParserError => e
        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 208
def self.present?(document, statement)
    statement.split(".").each do |key|
        if document.is_a? Hash
            if document.has_value? nil
                document.each do |k, v|
                    if document[k] == nil
                        document[k] = "null"
                    end
                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]
        if document.nil?
            return false
        end
    end
    return true
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 180
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
    else
        if filters =~ /=|<|>|^and$|^or$|^!$|^not$/
            raise "Invalid field for -s filter : '#{filters}'"
        end
    end
    return
end
verbose_on() click to toggle source
# File lib/jgrep.rb, line 13
def self.verbose_on
    @verbose = true
end