jpt 0.0.4 → 0.1.0

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
2
  SHA256:
3
- metadata.gz: 8384c2b533862dd8680c91a673db060c59f950a5f6f7e56962bb5d18913d3f54
4
- data.tar.gz: f2700ad739f78beb7ed048998c7c6831c9c077a6810c723b51091b4c95176be6
3
+ metadata.gz: 11f0008d935a2ce891b13af68fe35da1984c733c6d36cc03a3490749ac5deaa6
4
+ data.tar.gz: fbe7f467057066b07c70fb6f37628f0b105b08e8da560af4f99f3a94aa3336d8
5
5
  SHA512:
6
- metadata.gz: dab70f4ce946f2716d403696d750ac97ddee416c9da1a04a2bc8b375c5c93ff9bf93aff1ccce23c0431820e7e6ef39fbb00f08a6cb4ff34c8e206dea4d97d33c
7
- data.tar.gz: b4c83b1a0c98652b00359a554a6f75f3fcf8ab57330425b139f6093aec70505a5061fa0036f13b5d32006486d3928035558b50a346a004cfaa3310f5f4c94bc5
6
+ metadata.gz: ecc6e9084048e7d9253fd14c175acc83e932caad33817790579d1f53000bca8efc89cf350fead4255b27fa9b72ec1fd67a6b67c9f0ccfbee4b1c9a23ee40b11e
7
+ data.tar.gz: 4a7a95f87230e35d9c5437aba5da7593f9a08bdbedabc09d56d4143dd72254e1c224c06f9fbc725d64a75a68c286db44ae601bb358af4926c8ac1c56c8708e23
data/bin/jpt CHANGED
@@ -10,6 +10,8 @@ Encoding.default_external = Encoding::UTF_8
10
10
  require 'optparse'
11
11
  require 'ostruct'
12
12
 
13
+ FUNCSIG_CHARS = {"l" => :logical, "n" => :nodes, "v" => :value}
14
+
13
15
  $options = OpenStruct.new
14
16
  begin
15
17
  op = OptionParser.new do |opts|
@@ -21,6 +23,14 @@ begin
21
23
  opts.on("-l", "--[no-]lines", "multi-line mode") do |v|
22
24
  $options.lines = v
23
25
  end
26
+ opts.on("-q", "--[no-]test", "test-file mode") do |v|
27
+ $options.test = v
28
+ end
29
+ opts.on("-fFUNCSIG", "--[no-]f=FUNCSIG", "add function signature name=rppp") do |v|
30
+ fail "funcsig format must be name=rppp" unless v =~ /\A([a-z][_a-z0-9]*)-([lnv]+)\z/
31
+
32
+ JPTType.add_funcsig($1, $2)
33
+ end
24
34
  opts.on("-tFMT", "--to=FMT", [:basic, :neat, :json, :yaml, :enum, :jp], "Target format") do |v|
25
35
  $options.target = v
26
36
  end
@@ -37,7 +47,30 @@ if ARGV == []
37
47
  end
38
48
  jp_file = ARGF.read
39
49
 
40
- if $options.lines
50
+ if $options.test
51
+ argument = query = output = nil
52
+ jp_file.scan(/((?:^(?:$|[^$=].*)\n)+)|([$].*)|=(.*)|#.*/) do |arg,qy,out|
53
+ begin
54
+ if arg
55
+ argument = JSON.parse(arg)
56
+ puts
57
+ puts JSON.dump(argument)
58
+ elsif qy
59
+ jpt = JPT.from_jp(qy)
60
+ output = jpt.apply(argument)
61
+ print jpt.tree.inspect << " "
62
+ puts "➔ #{JSON.dump(output)}"
63
+ elsif out
64
+ suggested = JSON.parse(out)
65
+ if output != suggested
66
+ p [:SUGGESTED, suggested]
67
+ end
68
+ end
69
+ rescue => e
70
+ warn "*** #{e.detailed_message} #{e.backtrace}"
71
+ end
72
+ end
73
+ elsif $options.lines
41
74
  lines = jp_file.lines(chomp: true)
42
75
  col = lines.map(&:length).max
43
76
  form = "%-#{col}s %s"
data/jpt.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "jpt"
3
- s.version = "0.0.4"
3
+ s.version = "0.1.0"
4
4
  s.summary = "JSONPath tools"
5
5
  s.description = %q{jpt implements converters and miscellaneous tools for JSONPath}
6
6
  s.author = "Carsten Bormann"
data/lib/jpt.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  module JPTType
2
2
  # :value, :nodes, :logical
3
3
 
4
+
5
+ FUNCSIG_CHARS = {"l" => :logical, "n" => :nodes, "v" => :value}
6
+
4
7
  FUNCTABLE = {
5
8
  "length" => [:value, :value],
6
9
  "count" => [:value, :nodes],
@@ -8,6 +11,10 @@ module JPTType
8
11
  "search" => [:logical, :value, :value],
9
12
  }
10
13
 
14
+ def self.add_funcsig(name, sig)
15
+ FUNCTABLE[name] = sig.chars.map {FUNCSIG_CHARS[_1]}
16
+ end
17
+
11
18
  def declared_type(ast)
12
19
  case ast
13
20
  in Numeric | String | false | true | nil
@@ -82,4 +89,249 @@ class JPT
82
89
  Marshal.load(Marshal.dump(self))
83
90
  end
84
91
 
92
+ def apply(arg)
93
+ nodes = [arg]
94
+ select_query(tree, nodes, nodes, :error)
95
+ end
96
+
97
+ def select_query(tree, nodes, root_node, curr_node)
98
+ case tree
99
+ in ["$", *segments]
100
+ nodes = root_node
101
+ segments.each do |seg|
102
+ nodes = select_segment(seg, nodes, root_node, curr_node)
103
+ end
104
+ in ["@", *segments]
105
+ nodes = curr_node
106
+ segments.each do |seg|
107
+ nodes = select_segment(seg, nodes, root_node, curr_node)
108
+ end
109
+ end
110
+ nodes
111
+ end
112
+
113
+ def select_segment(seg, nodes, root_node, curr_node)
114
+ case seg
115
+ in Integer => ix
116
+ nodes = nodes.flat_map do |n|
117
+ if Array === n
118
+ [n.fetch(ix, :nothing)]
119
+ else []
120
+ end
121
+ end
122
+ in String => ky
123
+ nodes = nodes.flat_map do |n|
124
+ if Hash === n
125
+ [n.fetch(ky, :nothing)]
126
+ else []
127
+ end
128
+ end
129
+ in ["u", *sel]
130
+ # nodes = sel.flat_map{ |sel1| select_segment(sel1, nodes, root_node, curr_node)}
131
+ nodes = nodes.flat_map do |n|
132
+ sel.flat_map{ |sel1| select_segment(sel1, [n], root_node, curr_node)}
133
+ end
134
+ in ["wild"]
135
+ nodes = nodes.flat_map do |n|
136
+ enumerate(n)
137
+ end
138
+ in ["desc", sel]
139
+ nodes = nodes.flat_map do |n|
140
+ containers(n)
141
+ end
142
+ nodes = select_segment(sel, nodes, root_node, curr_node)
143
+ in ["slice", st, en, sp]
144
+ nodes = nodes.flat_map do |n|
145
+ if (Array === n) && sp != 0
146
+ len = n.length
147
+ ret = []
148
+ sp ||= 1
149
+ if sp > 0 # weird formulae copied from spec
150
+ st ||= 0
151
+ en ||= len
152
+ else
153
+ # warn "ARR #{st} #{en} #{sp}"
154
+ st ||= len - 1
155
+ en ||= -len - 1
156
+ # warn "ARR2 #{st} #{en} #{sp}"
157
+ end
158
+ st, en = [st, en].map{|i| i >= 0 ? i : len + i}
159
+ if sp > 0
160
+ lo = [[st, 0].max, len].min
161
+ up = [[en, 0].max, len].min
162
+ while lo < up
163
+ ret << n[lo]; lo += sp
164
+ end
165
+ else
166
+ up = [[st, -1].max, len-1].min
167
+ lo = [[en, -1].max, len-1].min
168
+ # warn "ARR3 #{st} #{en} #{sp} #{lo} #{up}"
169
+ while lo < up
170
+ ret << n[up]; up += sp
171
+ end
172
+ end
173
+ ret
174
+ else
175
+ []
176
+ end
177
+ end
178
+ in ["filt", logexp]
179
+ nodes = nodes.flat_map do |n|
180
+ enumerate(n).flat_map do |cand|
181
+ a = filt_apply(logexp, root_node, [cand])
182
+ # warn "***A #{a.inspect}"
183
+ if filt_to_logical(a)
184
+ [cand]
185
+ else
186
+ []
187
+ end
188
+ end
189
+ end
190
+ end
191
+ nodes.delete(:nothing)
192
+ nodes
193
+ end
194
+
195
+ def enumerate(n)
196
+ case n
197
+ in Array
198
+ n
199
+ in Hash
200
+ n.map{|k, v| v}
201
+ else
202
+ []
203
+ end
204
+ end
205
+
206
+ def containers(n)
207
+ case n
208
+ in Array
209
+ [n, *n.flat_map{containers(_1)}]
210
+ in Hash
211
+ [n, *n.flat_map{|k, v| containers(v)}]
212
+ else
213
+ []
214
+ end
215
+ end
216
+
217
+ def filt_to_logical(val)
218
+ case val
219
+ in [:nodes, v]
220
+ v != []
221
+ in [:logical, v]
222
+ v
223
+ end
224
+ end
225
+
226
+ def filt_to_value(val)
227
+ case val
228
+ in [:nodes, v]
229
+ if v.length == 1
230
+ v[0]
231
+ else
232
+ :nothing
233
+ end
234
+ in [:value, v]
235
+ v
236
+ end
237
+ end
238
+
239
+ COMPARE_SWAP = {">" => "<", ">=" => "<="}
240
+
241
+ def filt_apply(logexp, root_node, curr_node)
242
+ # warn "***B #{logexp.inspect} #{root_node.inspect} #{curr_node.inspect}"
243
+ case logexp
244
+ in ["@", *]
245
+ [:nodes, select_query(logexp, curr_node, root_node, curr_node)]
246
+ in ["$", *]
247
+ [:nodes, select_query(logexp, root_node, root_node, curr_node)]
248
+ in [("==" | "!=" | "<" | ">" | "<=" | ">="), a, b]
249
+ lhs = filt_to_value(filt_apply(a, root_node, curr_node)) rescue :nothing
250
+ rhs = filt_to_value(filt_apply(b, root_node, curr_node)) rescue :nothing
251
+ op = logexp[0]
252
+ # warn "***C #{op} #{lhs.inspect}, #{rhs.inspect}"
253
+ if sop = COMPARE_SWAP[op]
254
+ lhs, rhs = rhs, lhs
255
+ op = sop
256
+ end
257
+ # warn "***C1 #{op} #{lhs.inspect}, #{rhs.inspect}"
258
+ res = if op != "<" && (lhs == rhs rescue false)
259
+ op == "!=" ? false : true
260
+ else
261
+ if op[0] == "<" # "<" or "<="
262
+ case [lhs, rhs]
263
+ in Numeric, Numeric
264
+ lhs < rhs
265
+ in String, String
266
+ lhs < rhs
267
+ else
268
+ false
269
+ end
270
+ else op == "!="
271
+ end
272
+ end
273
+ # warn "***C9 #{res}"
274
+ [:logical, res]
275
+ in ["and" | "or", a, b]
276
+ lhs = filt_to_logical(filt_apply(a, root_node, curr_node))
277
+ rhs = filt_to_logical(filt_apply(b, root_node, curr_node))
278
+ op = logexp[0]
279
+ # warn "***C #{op} #{lhs.inspect}, #{rhs.inspect}"
280
+ [:logical, case op
281
+ in "or"
282
+ lhs || rhs
283
+ in "and"
284
+ lhs && rhs
285
+ end]
286
+ in ["not", a]
287
+ lhs = filt_to_logical(filt_apply(a, root_node, curr_node))
288
+ [:logical, !lhs]
289
+ in ["func", "length", value]
290
+ value = filt_to_value(filt_apply(value, root_node, curr_node))
291
+ [:value,
292
+ case value
293
+ in Hash | Array | String
294
+ value.length
295
+ else
296
+ :nothing
297
+ end
298
+ ]
299
+ in ["func", "count", nodes]
300
+ ty, nodes = filt_apply(nodes, root_node, curr_node)
301
+ [:value,
302
+ if ty != :nodes
303
+ warn "*** func count ty #{ty.inspect}"
304
+ 0
305
+ else
306
+ nodes.length
307
+ end]
308
+ in ["func", "match", str, re]
309
+ str = filt_to_value(filt_apply(str, root_node, curr_node))
310
+ re = filt_to_value(filt_apply(re, root_node, curr_node))
311
+ [:logical,
312
+ begin
313
+ /\A(?:#{re})\z/ === str
314
+ rescue => e
315
+ warn "*** #{e.detailed_message} #{e.backtrace}"
316
+ false
317
+ end
318
+ ]
319
+ in ["func", "search", str, re]
320
+ str = filt_to_value(filt_apply(str, root_node, curr_node))
321
+ re = filt_to_value(filt_apply(re, root_node, curr_node))
322
+ [:logical,
323
+ begin
324
+ /#{re}/ === str
325
+ rescue => e
326
+ warn "*** #{e.detailed_message} #{e.backtrace}"
327
+ false
328
+ end
329
+ ]
330
+ in ["func", name, *args]
331
+ warn "*** Unknown function extension #{name} with args #{args.inspect}"
332
+ [:logical, false]
333
+ in String | Numeric | false | true | nil
334
+ [:value, logexp]
335
+ end
336
+ end
85
337
  end
@@ -2974,7 +2974,7 @@ module JPTGRAMMAR
2974
2974
 
2975
2975
  module AbsSingularPath1
2976
2976
  def ast
2977
- ["@", *singular_path_segments.ast]
2977
+ ["$", *singular_path_segments.ast]
2978
2978
  end
2979
2979
  end
2980
2980
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jpt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carsten Bormann
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-02-20 00:00:00.000000000 Z
11
+ date: 2023-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler