jgrep 1.1.3 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/jgrep +53 -33
- data/jgrep.gemspec +2 -2
- data/lib/jgrep.rb +160 -17
- data/lib/parser/parser.rb +20 -5
- data/lib/parser/scanner.rb +25 -0
- metadata +3 -3
data/bin/jgrep
CHANGED
@@ -3,67 +3,87 @@
|
|
3
3
|
require 'jgrep'
|
4
4
|
require 'optparse'
|
5
5
|
|
6
|
-
options = {:flat => false}
|
6
|
+
options = {:flat => false, :start => nil}
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
8
|
+
begin
|
9
|
+
OptionParser.new do |opts|
|
10
|
+
opts.banner = "Usage: jgrep [options] \"expression\""
|
11
|
+
opts.on("-s", "--simple FIELD[s]", "Display a single field from each of the resulting json documents") do |field|
|
12
|
+
if (field.split(" ")).size > 1
|
13
|
+
options[:field] = field.split(" ")
|
14
|
+
else
|
15
|
+
options[:field] = field
|
16
|
+
end
|
17
|
+
end
|
19
18
|
|
20
|
-
|
19
|
+
opts.on("-c", "--compact", "Display non pretty json") do
|
20
|
+
options[:flat] = true
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on("-f", "--flatten", "Makes output as flat as possible") do
|
24
|
+
JGrep::flatten_on
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on("-i", "--input [FILENAME]", "Specify input file to parse") do |filename|
|
28
|
+
options[:file] = filename
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on("-v", "--verbose", "Verbose output") do
|
32
|
+
JGrep::verbose_on
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("--start [FIELD]", "Where in the data to start from") do |field|
|
36
|
+
options[:start] = field
|
37
|
+
end
|
38
|
+
end.parse!
|
39
|
+
rescue OptionParser::InvalidOption => e
|
40
|
+
puts e.to_s.capitalize
|
41
|
+
exit 1
|
42
|
+
end
|
21
43
|
|
22
44
|
begin
|
23
45
|
expression = nil
|
24
46
|
|
25
47
|
#Identify the expression from command line arguments
|
26
48
|
ARGV.each do |argument|
|
27
|
-
if argument =~
|
49
|
+
if argument =~ /<|>|=|\+|-/
|
28
50
|
expression = argument
|
29
51
|
ARGV.delete(argument)
|
30
52
|
end
|
31
53
|
end
|
32
54
|
|
33
|
-
|
55
|
+
expression = "" if expression.nil?
|
56
|
+
|
57
|
+
#Load json from standard input if tty is false
|
34
58
|
#else find and load file from command line arugments
|
35
59
|
unless STDIN.tty?
|
36
60
|
json = STDIN.read
|
37
61
|
else
|
38
|
-
if
|
39
|
-
json = File.read(
|
62
|
+
if options[:file]
|
63
|
+
json = File.read(options[:file])
|
40
64
|
else
|
41
65
|
raise "No json input specified"
|
42
66
|
end
|
43
67
|
end
|
44
68
|
|
45
69
|
unless options[:field]
|
46
|
-
result = JGrep::jgrep((json), expression)
|
70
|
+
result = JGrep::jgrep((json), expression, nil, options[:start])
|
47
71
|
unless result == []
|
48
72
|
(options[:flat] == false) ? puts(JSON.pretty_generate(result)) : puts(result.to_json)
|
49
73
|
end
|
50
|
-
else
|
51
|
-
if options[:field] =~ /=|<|>/
|
52
|
-
raise "'-s' flag requires a field to display"
|
53
|
-
end
|
54
74
|
|
55
|
-
|
75
|
+
else
|
76
|
+
if options[:field].is_a? Array
|
77
|
+
JGrep::validate_filters(options[:field])
|
78
|
+
puts(JSON.pretty_generate(JGrep::jgrep((json), expression, options[:field], options[:start])))
|
56
79
|
|
57
|
-
|
58
|
-
options[:field]
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
65
|
-
unless r.nil?
|
66
|
-
(!r.is_a?(Array) && !r.is_a?(Hash)) ? puts(r) : puts(JSON.pretty_generate(r))
|
80
|
+
else
|
81
|
+
JGrep::validate_filters(options[:field])
|
82
|
+
result = JGrep::jgrep((json), expression, options[:field], options[:start])
|
83
|
+
unless result.first.is_a?(Hash) || result.flatten.first.is_a?(Hash)
|
84
|
+
result.map{|x| puts x unless x.nil?}
|
85
|
+
else
|
86
|
+
puts(JSON.pretty_generate(result))
|
67
87
|
end
|
68
88
|
end
|
69
89
|
end
|
data/jgrep.gemspec
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "jgrep"
|
3
|
-
s.version = "1.1
|
3
|
+
s.version = "1.2.1"
|
4
4
|
|
5
5
|
s.authors = ["P Loubser"]
|
6
|
-
s.date = %q{2011-
|
6
|
+
s.date = %q{2011-08-01}
|
7
7
|
s.default_executable = "jgrep"
|
8
8
|
s.description = "Compare a list of json documents to a simple logical language and returns matches as output"
|
9
9
|
s.email = ["ploubser@gmail.com"]
|
data/lib/jgrep.rb
CHANGED
@@ -7,38 +7,60 @@ require 'json'
|
|
7
7
|
|
8
8
|
module JGrep
|
9
9
|
@verbose = false
|
10
|
+
@flatten = false
|
10
11
|
|
11
12
|
def self.verbose_on
|
12
13
|
@verbose = true
|
13
14
|
end
|
14
15
|
|
16
|
+
def self.flatten_on
|
17
|
+
@flatten = true
|
18
|
+
end
|
19
|
+
|
15
20
|
#Method parses json and returns documents that match the logical expression
|
16
|
-
def self.jgrep(json, expression)
|
21
|
+
def self.jgrep(json, expression, filters = nil, start = nil)
|
17
22
|
errors = ""
|
18
23
|
begin
|
19
|
-
|
20
|
-
result = []
|
24
|
+
JSON.create_id = nil
|
21
25
|
json = JSON.parse(json)
|
22
|
-
json.
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
if json.is_a? Hash
|
27
|
+
json = [json]
|
28
|
+
end
|
29
|
+
|
30
|
+
json = filter_json(json, start).flatten if start
|
31
|
+
|
32
|
+
result = []
|
33
|
+
unless expression == ""
|
34
|
+
call_stack = Parser.new(expression).execution_stack
|
35
|
+
|
36
|
+
json.each do |document|
|
37
|
+
begin
|
38
|
+
if eval_statement(document, call_stack)
|
39
|
+
result << document
|
40
|
+
end
|
41
|
+
rescue Exception => e
|
42
|
+
if @verbose
|
43
|
+
require 'pp'
|
44
|
+
pp document
|
45
|
+
STDERR.puts "Error - #{e} \n\n"
|
46
|
+
else
|
47
|
+
errors = "One or more the json documents could not be parsed. Run jgrep -v for to display documents"
|
48
|
+
end
|
33
49
|
end
|
34
50
|
end
|
51
|
+
else
|
52
|
+
result = json
|
35
53
|
end
|
36
54
|
|
37
55
|
unless errors == ""
|
38
56
|
puts errors
|
39
57
|
end
|
40
58
|
|
41
|
-
|
59
|
+
unless filters
|
60
|
+
return result
|
61
|
+
else
|
62
|
+
filter_json(result, filters)
|
63
|
+
end
|
42
64
|
|
43
65
|
rescue JSON::ParserError => e
|
44
66
|
STDERR.puts "Error. Invalid JSON given"
|
@@ -47,6 +69,54 @@ module JGrep
|
|
47
69
|
|
48
70
|
end
|
49
71
|
|
72
|
+
#Strips filters from json documents and returns those values as a less bloated json document
|
73
|
+
def self.filter_json(documents, filters)
|
74
|
+
result = []
|
75
|
+
|
76
|
+
if filters.is_a? Array
|
77
|
+
documents.each do |doc|
|
78
|
+
tmp_json = {}
|
79
|
+
filters.each do |filter|
|
80
|
+
filtered_result = dig_path(doc, filter)
|
81
|
+
unless (filtered_result == doc) || filtered_result.nil?
|
82
|
+
tmp_json[filter] = filtered_result
|
83
|
+
end
|
84
|
+
end
|
85
|
+
result << tmp_json
|
86
|
+
end
|
87
|
+
|
88
|
+
result = result.flatten if (result.size == 1 && @flatten == true)
|
89
|
+
return result
|
90
|
+
|
91
|
+
else
|
92
|
+
documents.each do |r|
|
93
|
+
filtered_result = dig_path(r, filters)
|
94
|
+
unless (filtered_result == r) || filtered_result.nil?
|
95
|
+
result << filtered_result
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
result = result.flatten if (result.size == 1 && @flatten == true)
|
100
|
+
return result
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
#Validates if filters do not match any of the parser's logical tokens
|
105
|
+
def self.validate_filters(filters)
|
106
|
+
if filters.is_a? Array
|
107
|
+
filters.each do |filter|
|
108
|
+
if filter =~ /=|<|>|^and$|^or$|^!$|^not$/
|
109
|
+
raise "Invalid field for -s filter : '#{filter}'"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
else
|
113
|
+
if filters =~ /=|<|>|^and$|^or$|^!$|^not$/
|
114
|
+
raise "Invalid field for -s filter : '#{filters}'"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
return
|
118
|
+
end
|
119
|
+
|
50
120
|
#Correctly format values so we can do the correct type of comparison
|
51
121
|
def self.format(kvalue, value)
|
52
122
|
if kvalue.to_s =~ /^\d+$/ && value.to_s =~ /^\d+$/
|
@@ -58,6 +128,34 @@ module JGrep
|
|
58
128
|
end
|
59
129
|
end
|
60
130
|
|
131
|
+
def self.present?(document, statement)
|
132
|
+
statement.split(".").each do |key|
|
133
|
+
if document.is_a? Hash
|
134
|
+
if document.has_value? nil
|
135
|
+
document.each do |k, v|
|
136
|
+
if document[k] == nil
|
137
|
+
document[k] = "null"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
if document.is_a? Array
|
144
|
+
rval = false
|
145
|
+
document.each do |doc|
|
146
|
+
rval ||= present?(doc, key)
|
147
|
+
end
|
148
|
+
return rval
|
149
|
+
end
|
150
|
+
|
151
|
+
document = document[key]
|
152
|
+
if document.nil?
|
153
|
+
return false
|
154
|
+
end
|
155
|
+
end
|
156
|
+
return true
|
157
|
+
end
|
158
|
+
|
61
159
|
#Check if key=value is present in document
|
62
160
|
def self.has_object?(document, statement)
|
63
161
|
|
@@ -74,7 +172,11 @@ module JGrep
|
|
74
172
|
key.split(".").each_with_index do |item,i|
|
75
173
|
tmp = tmp[item]
|
76
174
|
if tmp.nil?
|
77
|
-
|
175
|
+
if item == key.split(".").last
|
176
|
+
tmp = "null"
|
177
|
+
else
|
178
|
+
return false
|
179
|
+
end
|
78
180
|
end
|
79
181
|
result = false
|
80
182
|
if tmp.is_a? Array
|
@@ -82,7 +184,7 @@ module JGrep
|
|
82
184
|
end
|
83
185
|
end
|
84
186
|
|
85
|
-
tmp, value = format(tmp, value.gsub(/"|'/, ""))
|
187
|
+
tmp, value = format(tmp, (value.gsub(/"|'/, "") unless value.nil?))
|
86
188
|
|
87
189
|
case op
|
88
190
|
when "="
|
@@ -170,6 +272,10 @@ module JGrep
|
|
170
272
|
else
|
171
273
|
result << has_object?(document, expression.values.first)
|
172
274
|
end
|
275
|
+
when "+"
|
276
|
+
result << present?(document, expression.values.first)
|
277
|
+
when "-"
|
278
|
+
result << !(present?(document, expression.values.first))
|
173
279
|
when "and"
|
174
280
|
result << "&&"
|
175
281
|
when "or"
|
@@ -185,4 +291,41 @@ module JGrep
|
|
185
291
|
|
186
292
|
return eval(result.join(" "))
|
187
293
|
end
|
294
|
+
|
295
|
+
def self.dig_path(json, path)
|
296
|
+
|
297
|
+
json = json[path.split(".").first] if json.is_a? Hash
|
298
|
+
|
299
|
+
if json.is_a?(Hash)
|
300
|
+
if path == path.split(".").first
|
301
|
+
return json
|
302
|
+
else
|
303
|
+
return dig_path(json, (path.match(/\./) ? path.split(".").drop(1).join(".") : path))
|
304
|
+
end
|
305
|
+
|
306
|
+
elsif json.is_a? Array
|
307
|
+
if path == path.split(".").first && (json.first.is_a?(Hash) && !(json.first.keys.include?(path)))
|
308
|
+
return json
|
309
|
+
else
|
310
|
+
tmp = []
|
311
|
+
json.each do |j|
|
312
|
+
tmp_path = dig_path(j, (path.match(/\./) ? path.split(".").drop(1).join(".") : path))
|
313
|
+
unless tmp_path.nil?
|
314
|
+
tmp << tmp_path
|
315
|
+
end
|
316
|
+
end
|
317
|
+
unless tmp.empty?
|
318
|
+
return tmp
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
elsif json.nil?
|
323
|
+
return nil
|
324
|
+
|
325
|
+
else
|
326
|
+
return json
|
327
|
+
|
328
|
+
end
|
329
|
+
|
330
|
+
end
|
188
331
|
end
|
data/lib/parser/parser.rb
CHANGED
@@ -33,7 +33,7 @@ module JGrep
|
|
33
33
|
case c_token
|
34
34
|
|
35
35
|
when "and"
|
36
|
-
unless (n_token =~ /not|statement|\(
|
36
|
+
unless (n_token =~ /not|statement|\(|\+|-/) || (scanner.token_index == scanner.arguments.size)
|
37
37
|
raise "Error at column #{scanner.token_index}. \nExpected 'not', 'statement' or '('. Found '#{n_token_value}'"
|
38
38
|
end
|
39
39
|
|
@@ -44,7 +44,7 @@ module JGrep
|
|
44
44
|
end
|
45
45
|
|
46
46
|
when "or"
|
47
|
-
unless (n_token =~ /not|statement|\(
|
47
|
+
unless (n_token =~ /not|statement|\(|\+|-/) || (scanner.token_index == scanner.arguments.size)
|
48
48
|
raise "Error at column #{scanner.token_index}. \nExpected 'not', 'statement', '('. Found '#{n_token_value}'"
|
49
49
|
end
|
50
50
|
|
@@ -55,7 +55,7 @@ module JGrep
|
|
55
55
|
end
|
56
56
|
|
57
57
|
when "not"
|
58
|
-
unless n_token =~ /statement|\(|not
|
58
|
+
unless n_token =~ /statement|\(|not|\+|-/
|
59
59
|
raise "Error at column #{scanner.token_index}. \nExpected 'statement' or '('. Found '#{n_token_value}'"
|
60
60
|
end
|
61
61
|
|
@@ -69,7 +69,7 @@ module JGrep
|
|
69
69
|
|
70
70
|
if c_token_value =~ /!=/
|
71
71
|
c_token_value = c_token_value.gsub("!=", "=")
|
72
|
-
@execution_stack << {"not"
|
72
|
+
@execution_stack << {"not" => "not"}
|
73
73
|
end
|
74
74
|
|
75
75
|
unless n_token =~ /and|or|\)/
|
@@ -78,6 +78,21 @@ module JGrep
|
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
|
+
when "+"
|
82
|
+
unless n_token =~ /and|or|\)/
|
83
|
+
unless n_token.nil?
|
84
|
+
raise "Error at column #{scanner.token_index}. \nExpected 'and', 'or', ')'. Found '#{n_token_value}'"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
when "-"
|
89
|
+
unless n_token =~ /and|or|\)/
|
90
|
+
unless n_token.nil?
|
91
|
+
raise "Error at column #{scanner.token_index}. \nExpected 'and', 'or', ')'. Found '#{n_token_value}'"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
81
96
|
when ")"
|
82
97
|
unless (n_token =~ /|and|or|not|\(/)
|
83
98
|
unless n_token.nil?
|
@@ -87,7 +102,7 @@ module JGrep
|
|
87
102
|
parenth += 1
|
88
103
|
|
89
104
|
when "("
|
90
|
-
unless n_token =~ /statement|not|\(
|
105
|
+
unless n_token =~ /statement|not|\(|\+|-/
|
91
106
|
raise "Error at column #{scanner.token_index}. \nExpected 'statement', '(', not. Found '#{n_token_value}'"
|
92
107
|
end
|
93
108
|
parenth -= 1
|
data/lib/parser/scanner.rb
CHANGED
@@ -54,6 +54,31 @@ module JGrep
|
|
54
54
|
gen_statement
|
55
55
|
end
|
56
56
|
|
57
|
+
when "+"
|
58
|
+
value = ""
|
59
|
+
i = @token_index + 1
|
60
|
+
|
61
|
+
begin
|
62
|
+
value += @arguments.split("")[i]
|
63
|
+
i += 1
|
64
|
+
end until (i >= @arguments.size) || (@arguments.split("")[i] =~ /\s|\)/)
|
65
|
+
|
66
|
+
@token_index = i - 1
|
67
|
+
return "+", value
|
68
|
+
|
69
|
+
when "-"
|
70
|
+
value = ""
|
71
|
+
i = @token_index + 1
|
72
|
+
|
73
|
+
begin
|
74
|
+
value += @arguments.split("")[i]
|
75
|
+
i += 1
|
76
|
+
end until (i >= @arguments.size) || (@arguments.split("")[i] =~ /\s|\)/)
|
77
|
+
|
78
|
+
@token_index = i - 1
|
79
|
+
return "-", value
|
80
|
+
|
81
|
+
|
57
82
|
when " "
|
58
83
|
return " ", " "
|
59
84
|
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 1
|
7
|
+
- 2
|
7
8
|
- 1
|
8
|
-
|
9
|
-
version: 1.1.3
|
9
|
+
version: 1.2.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- P Loubser
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-
|
17
|
+
date: 2011-08-01 00:00:00 +01:00
|
18
18
|
default_executable: jgrep
|
19
19
|
dependencies: []
|
20
20
|
|