Module: JGrep

Defined in:
lib/jgrep.rb,
lib/parser/parser.rb,
lib/parser/scanner.rb

Defined Under Namespace

Classes: Parser, Scanner

Class Method Summary collapse

Class Method Details

.dig_path(json, path) ⇒ Object

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



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# 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) ⇒ Object

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



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# 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) ⇒ Object

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



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# 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_onObject



14
15
16
# File 'lib/jgrep.rb', line 14

def self.flatten_on
  @flatten = true
end

.format(kvalue, value) ⇒ Object

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



119
120
121
122
123
124
125
126
127
# 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) ⇒ Boolean

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

Returns:

  • (Boolean)


210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# 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) ⇒ Boolean

Check if key=value is present in document

Returns:

  • (Boolean)


157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# 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? && tmp.is_a?(String) && 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) ⇒ Boolean

Check if key=value is present in a sub array

Returns:

  • (Boolean)


200
201
202
203
204
205
206
# 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) ⇒ Object

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



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 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) ⇒ Boolean

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

Returns:

  • (Boolean)


130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# 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) ⇒ Object

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



65
66
67
68
69
70
# File 'lib/jgrep.rb', line 65

def self.validate_expression(expression)
  Parser.new(expression)
  true
rescue
  $!.message
end

.validate_filters(filters) ⇒ Object

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



104
105
106
107
108
109
110
111
112
113
114
115
116
# 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_onObject



10
11
12
# File 'lib/jgrep.rb', line 10

def self.verbose_on
  @verbose = true
end