jgrep 1.4.1 → 1.5.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6f1b59d081b76f0692d89d7d99d40cb361215ed5
4
- data.tar.gz: e173dbd8230dedcfb2a77c9bdb355d7293a67f66
2
+ SHA256:
3
+ metadata.gz: da7a8523b3fb626bd1c859fce413fdf621016029691177e52a70c581b55634bf
4
+ data.tar.gz: ae188a8718ae6d0a5f32e6732c9b10d9019133b31bfec5cb826b2a1b6d47475b
5
5
  SHA512:
6
- metadata.gz: bac482d335720345795310014895a0188b1458e7c87fd2ec062ca2cdf0a6aa5a1cecc1eded73680e4632635acb9e2eca5b75f71363e701be015b514eb20906eb
7
- data.tar.gz: 8624259c860b573792437681645991f3fd86a9fdc27530cd838c5988a8d1573c033eba23c632872ab1779b0226bd3eec7caa47488a187a96de14fd09b26baaa2
6
+ metadata.gz: a020b6c87f1e9de3256ac0a91dfe594c26240b2bbd5b32d268d08ce003edd0e0a0c45a18cc50bd6530db37a4c7477942d8b7be0a74f6a2e3137b46ec8b13a9ae
7
+ data.tar.gz: 631e11e62c4f3da937368fc8766b624fd8d923ba13738e180a9bcd9b2e79eac4725734229caf4cef143373d4b8d86fbd10e0246f70ae368ea41cee665c8e246d
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.5.4
4
+ * Include missing fixes for Ruby 2.7.0 deprecations
5
+
6
+ ## 1.5.3
7
+ * Fix Ruby 2.7.0 deprecation warnings
8
+ * Bump Rspec version in Gemfile
9
+
10
+ ## 1.5.2
11
+ * Fixed an issue where strings like 2012R2 would get parsed as floats
12
+
13
+ ## 1.5.1
14
+ * Now handles escaped parens when tokenising statements
15
+
16
+ ## 1.5.0
17
+ * Dropped support for Ruby 1.8.3
18
+ * Added support for modern Ruby versions (Tested up to 2.4.0)
19
+ * Added utility method to validate expressions
20
+
3
21
  ## 1.4.1
4
22
  * Fix binary exit code to be 1 when no matches are found (Mickaël Canévet)
5
23
 
@@ -1,12 +1,12 @@
1
1
  JGrep is a command line tool and API for parsing JSON documents based on logical expressions.
2
2
 
3
- ###Installation:###
3
+ ### Installation:
4
4
 
5
5
  jgrep is available as a gem:
6
6
 
7
7
  gem install jgrep
8
8
 
9
- ###JGrep binary usage:###
9
+ ### JGrep binary usage:
10
10
 
11
11
  jgrep [expression] -i foo.json
12
12
 
@@ -14,7 +14,7 @@ or
14
14
 
15
15
  cat "foo.json" | jgrep [expression]
16
16
 
17
- ###Flags:###
17
+ ### Flags:
18
18
 
19
19
  -s, --simple [FIELDS] : Greps the JSON and only returns the value of the field(s) specified
20
20
  -c, --compat : Returns the JSON in its non-pretty flat form
@@ -26,7 +26,7 @@ or
26
26
  --start FIELD : Starts the grep at a specific key in the document
27
27
  --slice [RANGE] : A range of the form 'n' or 'n..m', indicating which documents to extract from the final output
28
28
 
29
- ###Expressions###
29
+ ### Expressions:
30
30
 
31
31
  JGrep uses the following logical symbols to define expressions.
32
32
 
@@ -62,7 +62,7 @@ JGrep uses the following logical symbols to define expressions.
62
62
 
63
63
  Performs the operations inside the perentheses first.
64
64
 
65
- ###Statements:###
65
+ ### Statements:
66
66
 
67
67
  A statement is defined as some value in a json document compared to another value.
68
68
  Available comparison operators are '=', '<', '>', '<=', '>='
@@ -73,7 +73,7 @@ Examples:
73
73
  foo.bar>0
74
74
  foo.bar<=1.3
75
75
 
76
- ###Complex expressions:###
76
+ ### Complex expressions:
77
77
 
78
78
  Given a json document, {"foo":1, "bar":null}, the following are examples of valid expressions
79
79
 
@@ -95,12 +95,12 @@ Examples:
95
95
 
96
96
  ... returns true
97
97
 
98
- ###CLI missing an expression###
98
+ ### CLI missing an expression:
99
99
 
100
100
  If JGrep is executed without a set expression, it will return an unmodified JSON document. The
101
101
  -s flag can still be applied to the result.
102
102
 
103
- ###In document comparison:###
103
+ ### In document comparison:
104
104
 
105
105
  If a document contains an array, the '[' and ']' operators can be used to define a comparison where
106
106
  statements are checked for truth on a per element basis which will then be combined.
@@ -152,7 +152,7 @@ will return
152
152
 
153
153
  **Note**: In document comparison cannot be nested.
154
154
 
155
- ###The -s flag###
155
+ ### The -s flag:
156
156
 
157
157
  The s flag simplifies the output returned by JGrep. Given a JSON document
158
158
 
@@ -167,7 +167,7 @@ will output
167
167
  1
168
168
 
169
169
  The s flag can also be used with multiple field, which will return JSON as output which only contain the specified fields.
170
- **Note**: Separate fields by a space and enclose all fields in quotes (see example below)
170
+ **Note**: Separate fields by a space and enclose all fields in quotes (see example below)
171
171
 
172
172
  Given:
173
173
 
@@ -190,7 +190,7 @@ will output
190
190
  }
191
191
  ]
192
192
 
193
- ###The --start flag###
193
+ ### The --start flag:
194
194
 
195
195
  Some documents do not comply to our expected format, they might have an array embedded deep in a field. The --start
196
196
  flag lets you pick a starting point for the grep.
@@ -230,7 +230,7 @@ With the --stream or -n flag, jgrep will process multiple JSON inputs (newline
230
230
  separated) until standard input is closed. Each JSON input will be processed
231
231
  as usual, but the output immediately printed.
232
232
 
233
- ###JGrep Gem usage:###
233
+ ### JGrep Gem usage:
234
234
 
235
235
  require 'jgrep'
236
236
 
@@ -242,3 +242,4 @@ as usual, but the output immediately printed.
242
242
  sflags = "foo"
243
243
 
244
244
  JGrep::jgrep(json, expression, sflags)
245
+
data/Rakefile CHANGED
@@ -2,4 +2,9 @@ require 'rspec/core/rake_task'
2
2
 
3
3
  RSpec::Core::RakeTask.new(:spec)
4
4
 
5
- task :default => :spec
5
+ desc "Run rubycop style checks"
6
+ task :rubocop do
7
+ sh("rubocop -f progress -f offenses lib spec bin")
8
+ end
9
+
10
+ task :default => [:rubocop, :spec]
data/bin/jgrep CHANGED
@@ -1,153 +1,139 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'jgrep'
4
- require 'optparse'
3
+ require "jgrep"
4
+ require "optparse"
5
5
 
6
- @options = {:flat => false, :start => nil, :field => [], :slice => nil}
6
+ @options = {flat: false, start: nil, field: [], slice: nil}
7
7
 
8
8
  def print_json(result)
9
- unless @options[:flat]
10
- result = result.first if @options[:stream]
11
- puts(JSON.pretty_generate(result))
12
- else
13
- puts(result.first.to_json)
14
- end
9
+ if @options[:flat]
10
+ puts(result.first.to_json)
11
+ else
12
+ result = result.first if @options[:stream]
13
+ puts(JSON.pretty_generate(result))
14
+ end
15
15
  end
16
16
 
17
17
  def do_grep(json, expression)
18
- if @options[:field].empty?
19
- result = JGrep::jgrep((json), expression, nil, @options[:start])
20
- result = result.slice(@options[:slice]) if @options[:slice]
21
- exit 1 if result == []
22
- unless @options[:quiet] == true
23
- print_json(result)
24
- end
18
+ if @options[:field].empty?
19
+ result = JGrep.jgrep(json, expression, nil, @options[:start])
20
+ result = result.slice(@options[:slice]) if @options[:slice]
21
+
22
+ exit 1 if result == []
23
+
24
+ print_json(result) unless @options[:quiet] == true
25
+ elsif @options[:field].size > 1
26
+ JGrep.validate_filters(@options[:field])
27
+ result = JGrep.jgrep(json, expression, @options[:field], @options[:start])
28
+ result = result.slice(@options[:slice]) if @options[:slice]
29
+
30
+ exit 1 if result == []
31
+
32
+ print_json(result) unless @options[:quiet] == true
33
+
34
+ else
35
+ JGrep.validate_filters(@options[:field][0])
36
+ result = JGrep.jgrep(json, expression, @options[:field][0], @options[:start])
37
+ result = result.slice(@options[:slice]) if @options[:slice]
38
+ exit 1 if result == []
39
+ if result.is_a?(Array) && !(result.first.is_a?(Hash) || result.flatten.first.is_a?(Hash))
40
+ unless @options[:quiet] == true
41
+ result.map {|x| puts x unless x.nil?}
42
+ end
25
43
  else
26
- if @options[:field].size > 1
27
- JGrep::validate_filters(@options[:field])
28
- result = JGrep::jgrep((json), expression, @options[:field], @options[:start])
29
- result = result.slice(@options[:slice]) if @options[:slice]
30
- exit 1 if result == []
31
- unless @options[:quiet] == true
32
- print_json(result)
33
- end
34
-
35
- else
36
- JGrep::validate_filters(@options[:field][0])
37
- result = JGrep::jgrep((json), expression, @options[:field][0], @options[:start])
38
- result = result.slice(@options[:slice]) if @options[:slice]
39
- exit 1 if result == []
40
- if result.is_a?(Array) && !(result.first.is_a?(Hash) || result.flatten.first.is_a?(Hash))
41
- unless @options[:quiet] == true
42
- result.map{|x| puts x unless x.nil?}
43
- end
44
- else
45
- unless @options[:quiet] == true
46
- print_json(result)
47
- end
48
- end
49
- end
44
+ print_json(result) unless @options[:quiet] == true
50
45
  end
46
+ end
51
47
  end
52
48
 
53
49
  begin
54
- OptionParser.new do |opts|
55
- opts.banner = "Usage: jgrep [options] \"expression\""
56
- opts.on("-s", "--simple [FIELDS]", "Display only one or more fields from each of the resulting json documents") do |field|
57
- unless field.nil?
58
- @options[:field].concat(field.split(" "))
59
- else
60
- raise "-s flag requires a field value"
61
- end
62
- end
63
-
64
- opts.on("-c", "--compact", "Display non pretty json") do
65
- @options[:flat] = true
66
- end
67
-
68
- opts.on("-n", "--stream", "Display continuous output from continuous input") do
69
- @options[:stream] = true
70
- end
71
-
72
- opts.on("-f", "--flatten", "Makes output as flat as possible") do
73
- JGrep::flatten_on
74
- end
75
-
76
- opts.on("-i", "--input [FILENAME]", "Specify input file to parse") do |filename|
77
- @options[:file] = filename
78
- end
79
-
80
- opts.on("-q", "--quiet", "Quiet; don't write to stdout. Exit with zero status if match found.") do
81
- @options[:quiet] = true
82
- end
83
-
84
- opts.on("-v", "--verbose", "Verbose output") do
85
- JGrep::verbose_on
86
- end
87
-
88
- opts.on("--start [FIELD]", "Where in the data to start from") do |field|
89
- @options[:start] = field
90
- end
91
-
92
- opts.on("--slice [RANGE]", "A range of the form 'n' or 'n..m', indicating which documents to extract from the final output") do |field|
93
- range_nums = field.split('..').map{ |x| x.to_i }
94
- @options[:slice] = range_nums.length == 1 ? range_nums[0] : Range.new(*range_nums)
95
- end
96
- end.parse!
97
- rescue OptionParser::InvalidOption => e
98
- puts e.to_s.capitalize
99
- exit 1
50
+ OptionParser.new do |opts|
51
+ opts.banner = "Usage: jgrep [options] \"expression\""
52
+ opts.on("-s", "--simple [FIELDS]", "Display only one or more fields from each of the resulting json documents") do |field|
53
+ raise "-s flag requires a field value" if field.nil?
54
+
55
+ @options[:field].concat(field.split(" "))
56
+ end
57
+
58
+ opts.on("-c", "--compact", "Display non pretty json") do
59
+ @options[:flat] = true
60
+ end
100
61
 
101
- rescue Exception => e
102
- puts e
103
- exit 1
62
+ opts.on("-n", "--stream", "Display continuous output from continuous input") do
63
+ @options[:stream] = true
64
+ end
65
+
66
+ opts.on("-f", "--flatten", "Makes output as flat as possible") do
67
+ JGrep.flatten_on
68
+ end
69
+
70
+ opts.on("-i", "--input [FILENAME]", "Specify input file to parse") do |filename|
71
+ @options[:file] = filename
72
+ end
73
+
74
+ opts.on("-q", "--quiet", "Quiet; don't write to stdout. Exit with zero status if match found.") do
75
+ @options[:quiet] = true
76
+ end
77
+
78
+ opts.on("-v", "--verbose", "Verbose output") do
79
+ JGrep.verbose_on
80
+ end
81
+
82
+ opts.on("--start [FIELD]", "Where in the data to start from") do |field|
83
+ @options[:start] = field
84
+ end
85
+
86
+ opts.on("--slice [RANGE]", "A range of the form 'n' or 'n..m', indicating which documents to extract from the final output") do |field|
87
+ range_nums = field.split("..").map(&:to_i)
88
+ @options[:slice] = range_nums.length == 1 ? range_nums[0] : Range.new(*range_nums)
89
+ end
90
+ end.parse!
91
+ rescue OptionParser::InvalidOption => e
92
+ puts e.to_s.capitalize
93
+ exit 1
94
+ rescue Exception => e # rubocop:disable Lint/RescueException
95
+ puts e
96
+ exit 1
104
97
  end
105
98
 
106
99
  begin
107
- expression = nil
108
-
109
- #Identify the expression from command line arguments
110
- ARGV.each do |argument|
111
- if argument =~ /<|>|=|\+|-/
112
- expression = argument
113
- ARGV.delete(argument)
114
- end
100
+ expression = nil
101
+
102
+ # Identify the expression from command line arguments
103
+ ARGV.each do |argument|
104
+ if argument =~ /<|>|=|\+|-/
105
+ expression = argument
106
+ ARGV.delete(argument)
115
107
  end
108
+ end
116
109
 
117
- expression = "" if expression.nil?
110
+ expression = "" if expression.nil?
118
111
 
119
- #Continuously gets if inputstream in constant
120
- #Load json from standard input if tty is false
121
- #else find and load file from command line arugments
112
+ # Continuously gets if inputstream in constant
113
+ # Load json from standard input if tty is false
114
+ # else find and load file from command line arugments
122
115
 
123
- if @options[:stream]
124
- unless STDIN.tty?
125
- while json = gets
126
- do_grep(json, expression)
127
- end
128
- else
129
- raise "No json input specified"
130
- end
131
- else
132
- if @options[:file]
133
- json = File.read(@options[:file])
134
- do_grep(json, expression)
135
- elsif ! STDIN.tty?
136
- json = STDIN.read
137
- do_grep(json, expression)
138
- else
139
- raise "No json input specified"
140
- end
141
- end
116
+ if @options[:stream]
117
+ raise "No json input specified" if STDIN.tty?
142
118
 
143
- rescue Interrupt
144
- STDERR.puts "Exiting..."
145
- exit 1
146
- rescue Exception => e
147
- if e.is_a?(SystemExit)
148
- exit e.status
149
- else
150
- STDERR.puts "Error - #{e}"
151
- exit 1
119
+ while json = gets
120
+ do_grep(json, expression)
152
121
  end
122
+ elsif @options[:file]
123
+ json = File.read(@options[:file])
124
+ do_grep(json, expression)
125
+ elsif !STDIN.tty?
126
+ json = STDIN.read
127
+ do_grep(json, expression)
128
+ else
129
+ raise "No json input specified"
130
+ end
131
+ rescue Interrupt
132
+ STDERR.puts "Exiting..."
133
+ exit 1
134
+ rescue SystemExit
135
+ exit e.status
136
+ rescue Exception => e # rubocop:disable Lint/RescueException
137
+ STDERR.puts "Error - #{e}"
138
+ exit 1
153
139
  end
@@ -1,446 +1,355 @@
1
- #! /usr/lib/env ruby
2
-
3
- require 'parser/parser.rb'
4
- require 'parser/scanner.rb'
5
- require 'rubygems'
6
- require 'json'
7
- #require 'yajl/json_gem'
1
+ require "parser/parser.rb"
2
+ require "parser/scanner.rb"
3
+ require "rubygems"
4
+ require "json"
8
5
 
9
6
  module JGrep
10
- @verbose = false
11
- @flatten = false
12
-
13
- def self.verbose_on
14
- @verbose = true
15
- end
16
-
17
- def self.flatten_on
18
- @flatten = true
19
- end
20
-
21
- #Parse json and return documents that match the logical expression
22
- #Filters define output by limiting it to only returning a the listed keys.
23
- #Start allows you to move the pointer indicating where parsing starts.
24
- #Default is the first key in the document heirarchy
25
- def self.jgrep(json, expression, filters = nil, start = nil)
26
- errors = ""
27
- begin
28
- JSON.create_id = nil
29
- json = JSON.parse(json)
30
- if json.is_a? Hash
31
- json = [json]
32
- end
33
-
34
- json = filter_json(json, start).flatten if start
35
-
36
- result = []
37
- unless expression == ""
38
- call_stack = Parser.new(expression).execution_stack
39
-
40
- json.each do |document|
41
- begin
42
- if eval_statement(document, call_stack)
43
- result << document
44
- end
45
- rescue Exception => e
46
- if @verbose
47
- require 'pp'
48
- pp document
49
- STDERR.puts "Error - #{e} \n\n"
50
- else
51
- errors = "One or more the json documents could not be parsed. Run jgrep -v for to display documents"
52
- end
53
- end
54
- end
7
+ @verbose = false
8
+ @flatten = false
9
+
10
+ def self.verbose_on
11
+ @verbose = true
12
+ end
13
+
14
+ def self.flatten_on
15
+ @flatten = true
16
+ end
17
+
18
+ # Parse json and return documents that match the logical expression
19
+ # Filters define output by limiting it to only returning a the listed keys.
20
+ # Start allows you to move the pointer indicating where parsing starts.
21
+ # Default is the first key in the document heirarchy
22
+ def self.jgrep(json, expression, filters = nil, start = nil)
23
+ errors = ""
24
+
25
+ begin
26
+ JSON.create_id = nil
27
+ json = JSON.parse(json)
28
+ json = [json] if json.is_a?(Hash)
29
+
30
+ json = filter_json(json, start).flatten if start
31
+
32
+ result = []
33
+
34
+ if expression == ""
35
+ result = json
36
+ else
37
+ call_stack = Parser.new(expression).execution_stack
38
+
39
+ json.each do |document|
40
+ begin
41
+ result << document if eval_statement(document, call_stack)
42
+ rescue Exception => e # rubocop:disable Lint/RescueException
43
+ if @verbose
44
+ require "pp"
45
+ pp document
46
+ STDERR.puts "Error - #{e} \n\n"
55
47
  else
56
- result = json
48
+ errors = "One or more the json documents could not be parsed. Run jgrep -v for to display documents"
57
49
  end
50
+ end
51
+ end
52
+ end
58
53
 
59
- unless errors == ""
60
- puts errors
61
- end
54
+ puts errors unless errors == ""
62
55
 
63
- unless filters
64
- return result
65
- else
66
- filter_json(result, filters)
67
- end
56
+ return result unless filters
68
57
 
69
- rescue JSON::ParserError => e
70
- STDERR.puts "Error. Invalid JSON given"
71
- end
58
+ filter_json(result, filters)
59
+ rescue JSON::ParserError
60
+ STDERR.puts "Error. Invalid JSON given"
72
61
  end
73
-
74
- #Convert a specific hash inside a JSON document to an array
75
- #Mark is a string in the format foo.bar.baz that points to
76
- #the array in the document.
77
- def self.hash_to_array(documents, mark)
78
-
79
- begin
80
- documents = JSON.parse(documents)
81
- rescue JSON::ParserError => e
82
- STDERR.puts "Error. Invalid JSON given"
83
- exit 1
62
+ end
63
+
64
+ # Validates an expression, true when no errors are found else a string representing the issues
65
+ def self.validate_expression(expression)
66
+ Parser.new(expression)
67
+ true
68
+ rescue
69
+ $!.message
70
+ end
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
+
80
+ filters.each do |filter|
81
+ filtered_result = dig_path(doc, filter)
82
+ unless (filtered_result == doc) || filtered_result.nil?
83
+ tmp_json[filter] = filtered_result
84
+ end
84
85
  end
86
+ result << tmp_json
87
+ end
88
+ else
89
+ documents.each do |r|
90
+ filtered_result = dig_path(r, filters)
91
+
92
+ unless (filtered_result == r) || filtered_result.nil?
93
+ result << filtered_result
94
+ end
95
+ end
96
+ end
85
97
 
86
- result = []
87
-
88
- begin
89
- for i in 0..(documents.size - 1) do
90
- tmp = documents[i]
91
- unless mark == ""
92
- mark.split(".").each_with_index do |m,i|
93
- tmp = tmp[m] unless i == mark.split(".").size - 1
94
- end
95
- end
98
+ result.flatten if @flatten == true && result.size == 1
96
99
 
97
- tmp[mark.split.last].each{|d| result << {"value" => d[1], "key" => d[0]}}
98
- tmp[mark.split.last] = result
100
+ result
101
+ end
99
102
 
100
- end
101
- rescue Exception => e
102
- STDERR.puts "Error. Invalid position specified in JSON document"
103
- exit!
103
+ # Validates if filters do not match any of the parser's logical tokens
104
+ def self.validate_filters(filters)
105
+ if filters.is_a? Array
106
+ filters.each do |filter|
107
+ if filter =~ /=|<|>|^and$|^or$|^!$|^not$/
108
+ raise "Invalid field for -s filter : '#{filter}'"
104
109
  end
105
-
106
- puts JSON.pretty_generate(documents)
107
-
110
+ end
111
+ elsif filters =~ /=|<|>|^and$|^or$|^!$|^not$/
112
+ raise "Invalid field for -s filter : '#{filters}'"
108
113
  end
109
114
 
110
- #Convert a specific array inside a JSON document to a hash
111
- #Mark is a string in the format foo.bar.baz that points to
112
- #the hash in the document. Each element in the array will
113
- #be turned into a hash in the format key => array[x]
114
- def self.array_to_hash(documents, mark, key)
115
-
116
- begin
117
- documents = JSON.parse(documents)
118
- rescue JSON::ParserError => e
119
- STDERR.puts "Error. Invalid JSON given"
120
- exit 1
115
+ nil
116
+ end
117
+
118
+ # Correctly format values so we can do the correct type of comparison
119
+ def self.format(kvalue, value)
120
+ if kvalue.to_s =~ /^\d+$/ && value.to_s =~ /^\d+$/
121
+ [Integer(kvalue), Integer(value)]
122
+ elsif kvalue.to_s =~ /^\d+\.\d+$/ && value.to_s =~ /^\d+\.\d+$/
123
+ [Float(kvalue), Float(value)]
124
+ else
125
+ [kvalue, value]
126
+ end
127
+ end
128
+
129
+ # Check if the json key that is defined by statement is defined in the json document
130
+ def self.present?(document, statement)
131
+ statement.split(".").each do |key|
132
+ if document.is_a? Hash
133
+ if document.value?(nil)
134
+ document.each do |k, _|
135
+ document[k] = "null" if document[k].nil?
136
+ end
121
137
  end
138
+ end
122
139
 
123
- result = {}
140
+ if document.is_a? Array
141
+ rval = false
142
+ document.each do |doc|
143
+ rval ||= present?(doc, key)
144
+ end
145
+ return rval
146
+ end
124
147
 
125
- begin
126
- for i in 0..(documents.size - 1) do
127
- tmp = documents[i]
128
- unless mark == ""
129
- mark.split(".").each_with_index do |m,i|
130
- tmp = tmp[m] unless i == mark.split(".").size - 1
131
- end
132
- end
148
+ document = document[key]
133
149
 
134
- tmp[mark.split(".").last].each{|d| result[d[key]] = d}
135
- tmp[mark.split(".").last] = result
150
+ return false if document.nil?
151
+ end
136
152
 
137
- end
138
- rescue Exception => e
139
- STDERR.puts "Error. Invalid position specified in JSON document"
140
- exit!
141
- end
153
+ true
154
+ end
142
155
 
143
- puts JSON.pretty_generate(documents)
156
+ # Check if key=value is present in document
157
+ def self.has_object?(document, statement)
158
+ key, value = statement.split(/<=|>=|=|<|>/)
144
159
 
160
+ if statement =~ /(<=|>=|<|>|=)/
161
+ op = $1
162
+ else
163
+ op = statement
145
164
  end
146
165
 
147
- #Strips filters from json documents and returns those values as a less bloated json document
148
- def self.filter_json(documents, filters)
149
- result = []
166
+ tmp = dig_path(document, key)
150
167
 
151
- if filters.is_a? Array
152
- documents.each do |doc|
153
- tmp_json = {}
154
- filters.each do |filter|
155
- filtered_result = dig_path(doc, filter)
156
- unless (filtered_result == doc) || filtered_result.nil?
157
- tmp_json[filter] = filtered_result
158
- end
159
- end
160
- result << tmp_json
161
- end
168
+ tmp = tmp.first if tmp.is_a?(Array) && tmp.size == 1
162
169
 
163
- result = result.flatten if (result.size == 1 && @flatten == true)
164
- return result
170
+ tmp, value = format(tmp, (value.gsub(/"|'/, "") unless value.nil?)) # rubocop:disable Style/FormatString
165
171
 
166
- else
167
- documents.each do |r|
168
- filtered_result = dig_path(r, filters)
169
- unless (filtered_result == r) || filtered_result.nil?
170
- result << filtered_result
171
- end
172
- end
172
+ # Deal with null comparison
173
+ return true if tmp.nil? && value == "null"
173
174
 
174
- result = result.flatten if (result.size == 1 && @flatten == true)
175
- return result
176
- end
177
- end
175
+ # Deal with booleans
176
+ return true if tmp == true && value == "true"
177
+ return true if tmp == false && value == "false"
178
178
 
179
- #Validates if filters do not match any of the parser's logical tokens
180
- def self.validate_filters(filters)
181
- if filters.is_a? Array
182
- filters.each do |filter|
183
- if filter =~ /=|<|>|^and$|^or$|^!$|^not$/
184
- raise "Invalid field for -s filter : '#{filter}'"
185
- end
186
- end
187
- else
188
- if filters =~ /=|<|>|^and$|^or$|^!$|^not$/
189
- raise "Invalid field for -s filter : '#{filters}'"
190
- end
191
- end
192
- return
179
+ # Deal with regex matching
180
+ if !tmp.nil? && tmp.is_a?(String) && value =~ /^\/.*\/$/
181
+ tmp.match(Regexp.new(value.delete("/"))) ? (return true) : (return false)
193
182
  end
194
183
 
195
- #Correctly format values so we can do the correct type of comparison
196
- def self.format(kvalue, value)
197
- if kvalue.to_s =~ /^\d+$/ && value.to_s =~ /^\d+$/
198
- return Integer(kvalue), Integer(value)
199
- elsif kvalue.to_s =~ /^\d+.\d+$/ && value.to_s =~ /^\d+.\d+$/
200
- return Float(kvalue), Float(value)
201
- else
202
- return kvalue, value
203
- end
184
+ # Deal with everything else
185
+ case op
186
+ when "="
187
+ return tmp == value
188
+ when "<="
189
+ return tmp <= value
190
+ when ">="
191
+ return tmp >= value
192
+ when ">"
193
+ return tmp > value
194
+ when "<"
195
+ return tmp < value
204
196
  end
197
+ end
205
198
 
199
+ # Check if key=value is present in a sub array
200
+ def self.is_object_in_array?(document, statement)
201
+ document.each do |item|
202
+ return true if has_object?(item, statement)
203
+ end
206
204
 
207
- #Check if the json key that is defined by statement is defined in the json document
208
- def self.present?(document, statement)
209
- statement.split(".").each do |key|
210
- if document.is_a? Hash
211
- if document.has_value? nil
212
- document.each do |k, v|
213
- if document[k] == nil
214
- document[k] = "null"
215
- end
216
- end
217
- end
218
- end
219
-
220
- if document.is_a? Array
221
- rval = false
222
- document.each do |doc|
223
- rval ||= present?(doc, key)
224
- end
225
- return rval
226
- end
227
-
228
- document = document[key]
229
- if document.nil?
230
- return false
231
- end
232
- end
233
- return true
205
+ false
206
+ end
207
+
208
+ # Check if complex statement (defined as [key=value...]) is
209
+ # present over an array of key value pairs
210
+ def self.has_complex?(document, compound)
211
+ field = ""
212
+ tmp = document
213
+ result = []
214
+ fresult = []
215
+
216
+ compound.each do |token|
217
+ if token[0] == "statement"
218
+ field = token
219
+ break
220
+ end
234
221
  end
235
222
 
236
- #Check if key=value is present in document
237
- def self.has_object?(document, statement)
223
+ field = field[1].split(/=|<|>/).first
238
224
 
239
- key,value = statement.split(/<=|>=|=|<|>/)
225
+ field.split(".").each_with_index do |item, _|
226
+ tmp = tmp[item]
240
227
 
241
- if statement =~ /(<=|>=|<|>|=)/
242
- op = $1
243
- else
244
- op = statement
245
- end
228
+ return false if tmp.nil?
246
229
 
247
- tmp = dig_path(document, key)
230
+ next unless tmp.is_a?(Array)
248
231
 
249
- if tmp.is_a?(Array) and tmp.size == 1
250
- tmp = tmp.first
251
- end
252
-
253
- tmp, value = format(tmp, (value.gsub(/"|'/, "") unless value.nil?))
232
+ tmp.each do |doc|
233
+ result = []
254
234
 
255
- #Deal with null comparison
256
- if tmp.nil? and value == "null"
257
- return true
235
+ compound.each do |token|
236
+ case token[0]
237
+ when "and"
238
+ result << "&&"
239
+ when "or"
240
+ result << "||"
241
+ when /not|\!/
242
+ result << "!"
243
+ when "statement"
244
+ op = token[1].match(/.*<=|>=|=|<|>/)
245
+ left = token[1].split(op[0]).first.split(".").last
246
+ right = token[1].split(op[0]).last
247
+ new_statement = left + op[0] + right
248
+ result << has_object?(doc, new_statement)
249
+ end
258
250
  end
259
251
 
260
- # Deal with booleans
261
- if tmp == true and value == 'true'
262
- return true
263
- elsif tmp == false and value == 'false'
264
- return true
265
- end
252
+ fresult << eval(result.join(" ")) # rubocop:disable Security/Eval
253
+ (fresult << "||") unless doc == tmp.last
254
+ end
266
255
 
267
- #Deal with regex matching
268
- if ((value =~ /^\/.*\/$/) && tmp != nil)
269
- (tmp.match(Regexp.new(value.gsub("/", "")))) ? (return true) : (return false)
270
- end
271
-
272
- #Deal with everything else
273
- case op
274
- when "="
275
- (tmp == value) ? (return true) : (return false)
276
- when "<="
277
- (tmp <= value) ? (return true) : (return false)
278
- when ">="
279
- (tmp >= value) ? (return true) : (return false)
280
- when ">"
281
- (tmp > value) ? (return true) : (return false)
282
- when "<"
283
- (tmp < value) ? (return true) : (return false)
284
- end
256
+ return eval(fresult.join(" ")) # rubocop:disable Security/Eval
285
257
  end
286
-
287
- #Check if key=value is present in a sub array
288
- def self.is_object_in_array?(document, statement)
289
-
290
- document.each do |item|
291
- if has_object?(item,statement)
292
- return true
293
- end
258
+ end
259
+
260
+ # Evaluates the call stack en returns true of selected document
261
+ # matches logical expression
262
+ def self.eval_statement(document, callstack)
263
+ result = []
264
+
265
+ callstack.each do |expression|
266
+ case expression.keys.first
267
+ when "statement"
268
+ if expression.values.first.is_a?(Array)
269
+ result << has_complex?(document, expression.values.first)
270
+ else
271
+ result << has_object?(document, expression.values.first)
294
272
  end
295
-
296
- return false
273
+ when "+"
274
+ result << present?(document, expression.values.first)
275
+ when "-"
276
+ result << !present?(document, expression.values.first)
277
+ when "and"
278
+ result << "&&"
279
+ when "or"
280
+ result << "||"
281
+ when "("
282
+ result << "("
283
+ when ")"
284
+ result << ")"
285
+ when "not"
286
+ result << "!"
287
+ end
297
288
  end
298
289
 
299
- #Check if complex statement (defined as [key=value...]) is
300
- #present over an array of key value pairs
301
- def self.has_complex?(document, compound)
302
- field = ""
303
- tmp = document
304
- result = []
305
- fresult = []
290
+ eval(result.join(" ")) # rubocop:disable Security/Eval
291
+ end
306
292
 
307
- compound.each do |token|
308
- if token[0] == "statement"
309
- field = token
310
- break
311
- end
312
- end
313
- field = field[1].split(/=|<|>/).first
293
+ # Digs to a specific path in the json document and returns the value
294
+ def self.dig_path(json, path)
295
+ index = nil
296
+ path = path.gsub(/^\./, "")
314
297
 
315
- field.split(".").each_with_index do |item, i|
316
- tmp = tmp[item]
317
- if tmp.nil?
318
- return false
319
- end
320
- if tmp.is_a? Array
321
- tmp.each do |doc|
322
- result = []
323
- compound.each do |token|
324
- case token[0]
325
- when "and"
326
- result << "&&"
327
- when "or"
328
- result << "||"
329
- when /not|\!/
330
- result << "!"
331
- when "statement"
332
- op = token[1].match(/.*<=|>=|=|<|>/)
333
- left = token[1].split(op[0]).first.split(".").last
334
- right = token[1].split(op[0]).last
335
- new_statement = left + op[0] + right
336
- result << has_object?(doc, new_statement)
337
- end
338
- end
339
- fresult << eval(result.join(" "))
340
- (fresult << "||") unless doc == tmp.last
341
- end
342
- return eval(fresult.join(" "))
343
- end
344
- end
298
+ if path =~ /(.*)\[(.*)\]/
299
+ path = $1
300
+ index = $2
345
301
  end
346
302
 
347
- #Evaluates the call stack en returns true of selected document
348
- #matches logical expression
349
- def self.eval_statement(document, callstack)
350
- result = []
351
- callstack.each do |expression|
352
- case expression.keys.first
353
- when "statement"
354
- if expression.values.first.is_a? Array
355
- result << has_complex?(document, expression.values.first)
356
- else
357
- result << has_object?(document, expression.values.first)
358
- end
359
- when "+"
360
- result << present?(document, expression.values.first)
361
- when "-"
362
- result << !(present?(document, expression.values.first))
363
- when "and"
364
- result << "&&"
365
- when "or"
366
- result << "||"
367
- when "("
368
- result << "("
369
- when ")"
370
- result << ")"
371
- when "not"
372
- result << "!"
373
- end
374
- end
303
+ return json if path == ""
375
304
 
376
- return eval(result.join(" "))
305
+ if json.is_a? Hash
306
+ json.keys.each do |k|
307
+ if path.start_with?(k) && k.include?(".")
308
+ return dig_path(json[k], path.gsub(k, ""))
309
+ end
310
+ end
377
311
  end
378
312
 
379
- #Digs to a specific path in the json document and returns the value
380
- def self.dig_path(json, path)
381
- index = nil
382
- path = path.gsub(/^\./, "")
313
+ path_array = path.split(".")
383
314
 
384
- if path =~ /(.*)\[(.*)\]/
385
- path = $1
386
- index = $2
387
- end
315
+ if path_array.first == "*"
316
+ tmp = []
388
317
 
389
- if path == ""
390
- return json
391
- end
318
+ json.each do |j|
319
+ tmp << dig_path(j[1], path_array.drop(1).join("."))
320
+ end
392
321
 
393
- if json.is_a? Hash
394
- json.keys.each do |k|
395
- if path.start_with?(k) && k.include?('.')
396
- return dig_path(json[k], path.gsub(k, ""))
397
- end
398
- end
399
- end
322
+ return tmp
323
+ end
400
324
 
401
- path_array=path.split(".")
325
+ json = json[path_array.first] if json.is_a? Hash
402
326
 
403
- if path_array.first == "*"
404
- tmp = []
405
- json.each do |j|
406
- tmp << dig_path(j[1], path_array.drop(1).join("."))
407
- end
408
- return tmp
327
+ if json.is_a? Hash
328
+ return json if path == path_array.first
329
+ return dig_path(json, path.include?(".") ? path_array.drop(1).join(".") : path)
409
330
 
410
- end
331
+ elsif json.is_a? Array
332
+ if path == path_array.first && (json.first.is_a?(Hash) && !json.first.keys.include?(path))
333
+ return json
334
+ end
411
335
 
412
- json = json[path_array.first] if json.is_a? Hash
413
-
414
- if json.is_a? Hash
415
- if path == path_array.first
416
- return json
417
- else
418
- return dig_path(json, (path.include?('.') ? path_array.drop(1).join(".") : path))
419
- end
336
+ tmp = []
420
337
 
421
- elsif json.is_a? Array
422
- if path == path_array.first && (json.first.is_a?(Hash) && !(json.first.keys.include?(path)))
423
- return json
424
- else
425
- tmp = []
426
- json.each do |j|
427
- tmp_path = dig_path(j, (path.include?('.') ? path_array.drop(1).join(".") : path))
428
- unless tmp_path.nil?
429
- tmp << tmp_path
430
- end
431
- end
432
- unless tmp.empty?
433
- (index) ? (return tmp.flatten[index.to_i]) : (return tmp)
434
- end
435
- end
338
+ json.each do |j|
339
+ tmp_path = dig_path(j, (path.include?(".") ? path_array.drop(1).join(".") : path))
340
+ tmp << tmp_path unless tmp_path.nil?
341
+ end
436
342
 
437
- elsif json.nil?
438
- return nil
343
+ unless tmp.empty?
344
+ return index ? tmp.flatten[index.to_i] : tmp
345
+ end
439
346
 
440
- else
441
- return json
347
+ elsif json.nil?
348
+ return nil
442
349
 
443
- end
350
+ else
351
+ return json
444
352
 
445
353
  end
354
+ end
446
355
  end