jrf 0.1.6 → 0.1.8

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e9bb2a3a16d2bbe8cfb463267ff74d7d582511d4b4891e56ad3dfa6eee75fceb
4
- data.tar.gz: a13b2e9c8517c3da997452166556505b24fc4d5f898765ad33495eafd57c3081
3
+ metadata.gz: 0a1ab5bb43a602346df298f2827aaa9de7d418fcc7f090517ccb5c47f66081c6
4
+ data.tar.gz: 79181c2bb2689146e5b48d13bb1cdf3c740e86f61067862112f3fb2e81821b28
5
5
  SHA512:
6
- metadata.gz: 54b400cdaba584896f2511acfe9a41ef10af25033bf88cfc6e0386eaa840df9395fb0d008c320b3193d55a9c3fad444a7f54bd29f52c34f69bc9a9cf392a7809
7
- data.tar.gz: 80c72675e179da483316bfeaee7114da6edb49dc66ae179aa072d48907c4c9caf74113c6681b2f4a83f4b97da6faac436f5d6af5bd31e82605b122d85892cede
6
+ metadata.gz: 0b57e5b2f895420eb7c6a3a59d40a3f3213768eba4bef8125f3e8c6b77a4d6fa818c803a174ea37fa6dc12bca594f52949a9ba623205e089acb5cc1993d9d7ac
7
+ data.tar.gz: 6c2a48e7db8074eac45a24bdcdcc41f304c2997e03e8ba43f1a28bf152195ef2ce3515ca9a3609cd17cd840849e91ef3eb4b5b9115282ae2c7187f0186a9475e
data/exe/jrf CHANGED
@@ -10,4 +10,4 @@ end
10
10
  $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
11
11
  require "jrf"
12
12
 
13
- exit Jrf::CLI.run(ARGV)
13
+ Jrf::CLI.run(ARGV)
data/lib/jrf/cli.rb CHANGED
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "optparse"
4
+
3
5
  require_relative "cli/runner"
6
+ require_relative "version"
4
7
 
5
8
  module Jrf
6
9
  class CLI
7
10
  USAGE = "usage: jrf [options] 'STAGE >> STAGE >> ...'"
8
-
9
11
  HELP_TEXT = <<~'TEXT'
10
12
  usage: jrf [options] 'STAGE >> STAGE >> ...'
11
13
 
@@ -15,8 +17,10 @@ module Jrf
15
17
  -v, --verbose print parsed stage expressions
16
18
  --lax allow multiline JSON texts; split inputs by whitespace (also detects JSON-SEQ RS 0x1e)
17
19
  -p, --pretty pretty-print JSON output instead of compact NDJSON
20
+ --no-jit do not enable YJIT, even when supported by the Ruby runtime
18
21
  --atomic-write-bytes N
19
22
  group short outputs into atomic writes of up to N bytes
23
+ -V, --version show version and exit
20
24
  -h, --help show this help and exit
21
25
 
22
26
  Pipeline:
@@ -38,43 +42,47 @@ module Jrf
38
42
  verbose = false
39
43
  lax = false
40
44
  pretty = false
45
+ jit = true
41
46
  atomic_write_bytes = Runner::DEFAULT_OUTPUT_BUFFER_LIMIT
42
-
43
- while argv.first&.start_with?("-")
44
- case argv.first
45
- when "-v", "--verbose"
46
- verbose = true
47
- argv.shift
48
- when "--lax"
49
- lax = true
50
- argv.shift
51
- when "-p", "--pretty"
52
- pretty = true
53
- argv.shift
54
- when /\A--atomic-write-bytes=(.+)\z/
55
- atomic_write_bytes = parse_atomic_write_bytes(Regexp.last_match(1), err)
56
- return 1 unless atomic_write_bytes
57
- argv.shift
58
- when "--atomic-write-bytes"
59
- argv.shift
60
- atomic_write_bytes = parse_atomic_write_bytes(argv.shift, err)
61
- return 1 unless atomic_write_bytes
62
- when "-h", "--help"
63
- out.puts HELP_TEXT
64
- return 0
65
- else
66
- err.puts "unknown option: #{argv.first}"
67
- err.puts USAGE
68
- return 1
47
+ begin
48
+ parser = OptionParser.new do |opts|
49
+ opts.banner = USAGE
50
+ opts.on("-v", "--verbose", "print parsed stage expressions") { verbose = true }
51
+ opts.on("--lax", "allow multiline JSON texts; split inputs by whitespace (also detects JSON-SEQ RS 0x1e)") { lax = true }
52
+ opts.on("-p", "--pretty", "pretty-print JSON output instead of compact NDJSON") { pretty = true }
53
+ opts.on("--no-jit", "do not enable YJIT, even when supported by the Ruby runtime") { jit = false }
54
+ opts.on("--atomic-write-bytes N", Integer, "group short outputs into atomic writes of up to N bytes") do |value|
55
+ if value.positive?
56
+ atomic_write_bytes = value
57
+ else
58
+ raise OptionParser::InvalidArgument, "--atomic-write-bytes requires a positive integer"
59
+ end
60
+ end
61
+ opts.on("-V", "--version", "show version and exit") do
62
+ out.puts Jrf::VERSION
63
+ exit
64
+ end
65
+ opts.on("-h", "--help", "show this help and exit") do
66
+ out.puts HELP_TEXT
67
+ exit
68
+ end
69
69
  end
70
+
71
+ parser.order!(argv)
72
+ rescue OptionParser::ParseError => e
73
+ err.puts e.message
74
+ err.puts USAGE
75
+ exit 1
70
76
  end
71
77
 
72
78
  if argv.empty?
73
79
  err.puts USAGE
74
- return 1
80
+ exit 1
75
81
  end
76
82
 
77
83
  expression = argv.shift
84
+ enable_yjit if jit
85
+
78
86
  inputs = Enumerator.new do |y|
79
87
  if argv.empty?
80
88
  y << input
@@ -103,15 +111,12 @@ module Jrf
103
111
  pretty: pretty,
104
112
  atomic_write_bytes: atomic_write_bytes
105
113
  ).run(expression, verbose: verbose)
106
- 0
107
114
  end
108
115
 
109
- def self.parse_atomic_write_bytes(value, err)
110
- bytes = Integer(value, exception: false)
111
- return bytes if bytes && bytes.positive?
116
+ def self.enable_yjit
117
+ return unless defined?(RubyVM::YJIT) && RubyVM::YJIT.respond_to?(:enable)
112
118
 
113
- err.puts "--atomic-write-bytes requires a positive integer"
114
- nil
119
+ RubyVM::YJIT.enable
115
120
  end
116
121
  end
117
122
  end
data/lib/jrf/pipeline.rb CHANGED
@@ -22,54 +22,43 @@ module Jrf
22
22
  # @yieldparam value output value
23
23
  # @return [Array, nil] output values (without block), or nil (with block)
24
24
  def call(input, &on_output)
25
- if on_output
26
- call_streaming(input, &on_output)
27
- else
25
+ if on_output.nil?
28
26
  results = []
29
- call_streaming(input) { |v| results << v }
30
- results
27
+ on_output = proc { |value| results << value }
31
28
  end
32
- end
33
-
34
- private
35
29
 
36
- def call_streaming(input, &on_output)
37
- error = nil
38
30
  begin
39
31
  input.each { |value| process_value(value, @stages, &on_output) }
40
- rescue StandardError => e
41
- error = e
42
32
  ensure
43
33
  flush_reducers(@stages, &on_output)
44
34
  end
45
- raise error if error
35
+
36
+ results unless results.nil?
46
37
  end
47
38
 
48
- def process_value(input, stages, &on_output)
49
- current_values = [input]
39
+ private
50
40
 
51
- stages.each do |stage|
52
- next_values = []
41
+ def process_value(value, stages, idx = 0, &on_output)
42
+ while idx < stages.length
43
+ value = stages[idx].call(value)
53
44
 
54
- current_values.each do |value|
55
- out = stage.call(value)
56
- if out.equal?(Control::DROPPED)
57
- next
58
- elsif out.is_a?(Control::Flat)
59
- unless out.value.is_a?(Array)
60
- raise TypeError, "flat expects Array, got #{out.value.class}"
61
- end
62
- next_values.concat(out.value)
63
- else
64
- next_values << out
45
+ if value.equal?(Control::DROPPED)
46
+ return
47
+ elsif value.is_a?(Control::Flat)
48
+ value = value.value
49
+ unless value.is_a?(Array)
50
+ raise TypeError, "flat expects Array, got #{value.class}"
51
+ end
52
+ value.each do |child|
53
+ process_value(child, stages, idx + 1, &on_output)
65
54
  end
55
+ return
66
56
  end
67
57
 
68
- return if next_values.empty?
69
- current_values = next_values
58
+ idx += 1
70
59
  end
71
60
 
72
- current_values.each(&on_output)
61
+ on_output.call(value)
73
62
  end
74
63
 
75
64
  def flush_reducers(stages, &on_output)
@@ -13,7 +13,7 @@ module Jrf
13
13
  def define_reducer(name, &definition)
14
14
  define_method(name) do |*args, **kwargs, &block|
15
15
  spec = definition.call(self, *args, **kwargs, block: block)
16
- @__jrf_current_stage.allocate_reducer(
16
+ @__jrf_current_stage.step_reduce(
17
17
  spec.fetch(:value),
18
18
  initial: reducer_initial_value(spec.fetch(:initial)),
19
19
  finish: spec[:finish],
@@ -136,9 +136,9 @@ module Jrf
136
136
  end
137
137
 
138
138
  define_reducer(:percentile) do |ctx, value, percentage, block: nil|
139
- percentages = percentage.is_a?(Array) ? percentage : [percentage]
139
+ scalar = !percentage.is_a?(Enumerable)
140
+ percentages = scalar ? [percentage] : percentage.to_a
140
141
  percentages.each { |p| ctx.send(:validate_percentile!, p) }
141
- scalar = !percentage.is_a?(Array)
142
142
 
143
143
  finish =
144
144
  if scalar
@@ -161,24 +161,24 @@ module Jrf
161
161
  def reduce(initial, &block)
162
162
  raise ArgumentError, "reduce requires a block" unless block
163
163
 
164
- @__jrf_current_stage.allocate_reducer(current_input, initial: initial, &block)
164
+ @__jrf_current_stage.step_reduce(current_input, initial: initial, &block)
165
165
  end
166
166
 
167
167
  def map(&block)
168
168
  raise ArgumentError, "map requires a block" unless block
169
169
 
170
- @__jrf_current_stage.allocate_map(:array, @obj, &block)
170
+ @__jrf_current_stage.step_map(:map, @obj, &block)
171
171
  end
172
172
 
173
173
  def map_values(&block)
174
174
  raise ArgumentError, "map_values requires a block" unless block
175
175
 
176
- @__jrf_current_stage.allocate_map(:hash, @obj, &block)
176
+ @__jrf_current_stage.step_map(:map_values, @obj, &block)
177
177
  end
178
178
 
179
179
  def group_by(key, &block)
180
180
  block ||= proc { group }
181
- @__jrf_current_stage.allocate_group_by(key, &block)
181
+ @__jrf_current_stage.step_group_by(key, &block)
182
182
  end
183
183
 
184
184
  private
data/lib/jrf/stage.rb CHANGED
@@ -39,39 +39,52 @@ module Jrf
39
39
  @ctx.__jrf_current_stage = self
40
40
  result = @ctx.instance_eval(&@block)
41
41
 
42
- if @mode.nil? && @reducers.any?
43
- @mode = :reducer
44
- @template = result
45
- elsif @mode.nil?
46
- @mode = :passthrough
42
+ if @mode.nil?
43
+ if @reducers.any?
44
+ @mode = :reducer
45
+ @template = result
46
+ else
47
+ @mode = :passthrough
48
+ end
47
49
  end
48
50
 
49
51
  (@mode == :reducer) ? Control::DROPPED : result
50
52
  end
51
53
 
52
- def allocate_reducer(value, initial:, finish: nil, &step_fn)
54
+ def step_reduce(value, initial:, finish: nil, &step_fn)
53
55
  idx = @cursor
54
- finish_rows = finish || ->(acc) { [acc] }
55
- @reducers[idx] ||= Reducers.reduce(initial, finish: finish_rows, &step_fn)
56
+
57
+ if @reducers[idx].nil?
58
+ finish_rows = finish || ->(acc) { [acc] }
59
+ @reducers[idx] = Reducers.reduce(initial, finish: finish_rows, &step_fn)
60
+ result = ReducerToken.new(idx)
61
+ else
62
+ result = Control::DROPPED
63
+ end
64
+
56
65
  @reducers[idx].step(value)
57
- @cursor += 1
58
- ReducerToken.new(idx)
66
+ @cursor = idx + 1
67
+ result
59
68
  end
60
69
 
61
- def allocate_map(type, collection, &block)
70
+ def step_map(builtin, collection, &block)
62
71
  idx = @cursor
63
72
  @cursor += 1
64
73
 
74
+ if collection.is_a?(Array)
75
+ raise TypeError, "map_values expects Hash, got Array" if builtin == :map_values
76
+ elsif !collection.is_a?(Hash)
77
+ raise TypeError, "#{builtin} expects #{builtin == :map_values ? "Hash" : "Array or Hash"}, got #{collection.class}"
78
+ end
79
+
65
80
  # Transformation mode (detected on first call)
66
81
  if @map_transforms[idx]
67
- return transform_collection(type, collection, &block)
82
+ return transform_collection(builtin, collection, &block)
68
83
  end
69
84
 
70
- map_reducer = (@reducers[idx] ||= MapReducer.new(type))
85
+ map_reducer = (@reducers[idx] ||= MapReducer.new(builtin, collection.is_a?(Array)))
71
86
 
72
- case type
73
- when :array
74
- raise TypeError, "map expects Array, got #{collection.class}" unless collection.is_a?(Array)
87
+ if collection.is_a?(Array)
75
88
  collection.each_with_index do |v, i|
76
89
  slot = map_reducer.slot(i)
77
90
  with_scoped_reducers(slot.reducers) do
@@ -79,12 +92,11 @@ module Jrf
79
92
  slot.template ||= result
80
93
  end
81
94
  end
82
- when :hash
83
- raise TypeError, "map_values expects Hash, got #{collection.class}" unless collection.is_a?(Hash)
95
+ else
84
96
  collection.each do |k, v|
85
97
  slot = map_reducer.slot(k)
86
98
  with_scoped_reducers(slot.reducers) do
87
- result = @ctx.send(:__jrf_with_current_input, v) { block.call(v) }
99
+ result = @ctx.send(:__jrf_with_current_input, v) { invoke_block(builtin, block, k, v) }
88
100
  slot.template ||= result
89
101
  end
90
102
  end
@@ -94,15 +106,15 @@ module Jrf
94
106
  if @mode.nil? && map_reducer.slots.values.all? { |s| s.reducers.empty? }
95
107
  @map_transforms[idx] = true
96
108
  @reducers[idx] = nil
97
- return transformed_slots(type, map_reducer)
109
+ return transformed_slots(builtin, map_reducer)
98
110
  end
99
111
 
100
112
  ReducerToken.new(idx)
101
113
  end
102
114
 
103
- def allocate_group_by(key, &block)
115
+ def step_group_by(key, &block)
104
116
  idx = @cursor
105
- map_reducer = (@reducers[idx] ||= MapReducer.new(:hash))
117
+ map_reducer = (@reducers[idx] ||= MapReducer.new(:group_by, false))
106
118
 
107
119
  row = @ctx._
108
120
  slot = map_reducer.slot(key)
@@ -138,55 +150,82 @@ module Jrf
138
150
  @cursor = saved_cursor
139
151
  end
140
152
 
141
- def transform_collection(type, collection, &block)
142
- case type
143
- when :array
144
- raise TypeError, "map expects Array, got #{collection.class}" unless collection.is_a?(Array)
153
+ def invoke_block(builtin, block, key, value)
154
+ case builtin
155
+ when :map then block.call([key, value])
156
+ when :map_values then block.call(value)
157
+ else raise ArgumentError, "unexpected builtin: #{builtin}"
158
+ end
159
+ end
145
160
 
161
+ def transform_collection(builtin, collection, &block)
162
+ if collection.is_a?(Array)
146
163
  collection.each_with_object([]) do |value, result|
147
164
  mapped = @ctx.send(:__jrf_with_current_input, value) { block.call(value) }
148
- append_map_result(result, mapped)
165
+ append_result(result, mapped, builtin)
149
166
  end
150
- when :hash
151
- raise TypeError, "map_values expects Hash, got #{collection.class}" unless collection.is_a?(Hash)
152
-
153
- collection.each_with_object({}) do |(key, value), result|
154
- mapped = @ctx.send(:__jrf_with_current_input, value) { block.call(value) }
155
- next if mapped.equal?(Control::DROPPED)
156
- raise TypeError, "flat is not supported inside map_values" if mapped.is_a?(Control::Flat)
167
+ else
168
+ case builtin
169
+ when :map
170
+ collection.each_with_object([]) do |(key, value), result|
171
+ mapped = @ctx.send(:__jrf_with_current_input, value) { invoke_block(builtin, block, key, value) }
172
+ append_result(result, mapped, builtin)
173
+ end
174
+ when :map_values
175
+ collection.each_with_object({}) do |(key, value), result|
176
+ mapped = @ctx.send(:__jrf_with_current_input, value) { invoke_block(builtin, block, key, value) }
177
+ next if mapped.equal?(Control::DROPPED)
178
+ raise TypeError, "flat is not supported inside map_values" if mapped.is_a?(Control::Flat)
157
179
 
158
- result[key] = mapped
180
+ result[key] = mapped
181
+ end
182
+ else
183
+ raise ArgumentError, "unexpected builtin: #{builtin}"
159
184
  end
160
185
  end
161
186
  end
162
187
 
163
- def transformed_slots(type, map_reducer)
164
- case type
165
- when :array
188
+ def transformed_slots(builtin, map_reducer)
189
+ if map_reducer.array_input?
166
190
  map_reducer.slots
167
191
  .sort_by { |k, _| k }
168
192
  .each_with_object([]) do |(_, slot), result|
169
- append_map_result(result, slot.template)
193
+ append_result(result, slot.template, builtin)
194
+ end
195
+ else
196
+ case builtin
197
+ when :map
198
+ map_reducer.slots.each_with_object([]) do |(_key, slot), result|
199
+ append_result(result, slot.template, builtin)
170
200
  end
171
- when :hash
172
- map_reducer.slots.each_with_object({}) do |(key, slot), result|
173
- next if slot.template.equal?(Control::DROPPED)
174
- raise TypeError, "flat is not supported inside map_values" if slot.template.is_a?(Control::Flat)
201
+ when :map_values
202
+ map_reducer.slots.each_with_object({}) do |(key, slot), result|
203
+ next if slot.template.equal?(Control::DROPPED)
204
+ raise TypeError, "flat is not supported inside map_values" if slot.template.is_a?(Control::Flat)
175
205
 
176
- result[key] = slot.template
206
+ result[key] = slot.template
207
+ end
208
+ else
209
+ raise ArgumentError, "unexpected builtin: #{builtin}"
177
210
  end
178
211
  end
179
212
  end
180
213
 
181
- def append_map_result(result, mapped)
214
+ def append_result(result, mapped, builtin)
182
215
  return if mapped.equal?(Control::DROPPED)
183
216
 
184
217
  if mapped.is_a?(Control::Flat)
185
- unless mapped.value.is_a?(Array)
186
- raise TypeError, "flat expects Array, got #{mapped.value.class}"
218
+ case builtin
219
+ when :map
220
+ unless mapped.value.is_a?(Array)
221
+ raise TypeError, "flat expects Array, got #{mapped.value.class}"
222
+ end
223
+ result.concat(mapped.value)
224
+ when :map_values
225
+ raise TypeError, "flat is not supported inside map_values"
226
+ else
227
+ raise ArgumentError, "unexpected builtin: #{builtin}"
187
228
  end
188
-
189
- result.concat(mapped.value)
190
229
  else
191
230
  result << mapped
192
231
  end
@@ -195,24 +234,35 @@ module Jrf
195
234
  class MapReducer
196
235
  attr_reader :slots
197
236
 
198
- def initialize(type)
199
- @type = type
237
+ def initialize(builtin, array_input)
238
+ @builtin = builtin
239
+ @array_input = array_input
200
240
  @slots = {}
201
241
  end
202
242
 
243
+ def array_input?
244
+ @array_input
245
+ end
246
+
203
247
  def slot(key)
204
248
  @slots[key] ||= SlotState.new
205
249
  end
206
250
 
207
251
  def finish
208
- case @type
209
- when :array
252
+ if @array_input
210
253
  keys = @slots.keys.sort
211
254
  [keys.map { |k| Stage.resolve_template(@slots[k].template, @slots[k].reducers) }]
212
- when :hash
213
- result = {}
214
- @slots.each { |k, s| result[k] = Stage.resolve_template(s.template, s.reducers) }
215
- [result]
255
+ else
256
+ case @builtin
257
+ when :map
258
+ [@slots.map { |_k, s| Stage.resolve_template(s.template, s.reducers) }]
259
+ when :map_values, :group_by
260
+ result = {}
261
+ @slots.each { |k, s| result[k] = Stage.resolve_template(s.template, s.reducers) }
262
+ [result]
263
+ else
264
+ raise ArgumentError, "unexpected builtin: #{@builtin}"
265
+ end
216
266
  end
217
267
  end
218
268
 
data/lib/jrf/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Jrf
4
- VERSION = "0.1.6"
4
+ VERSION = "0.1.8"
5
5
  end
data/test/jrf_test.rb CHANGED
@@ -145,6 +145,9 @@ assert_includes(stdout, "usage: jrf [options] 'STAGE >> STAGE >> ...'")
145
145
  assert_includes(stdout, "JSON filter with the power and speed of Ruby.")
146
146
  assert_includes(stdout, "--lax")
147
147
  assert_includes(stdout, "--pretty")
148
+ assert_includes(stdout, "--no-jit")
149
+ assert_includes(stdout, "-V")
150
+ assert_includes(stdout, "--version")
148
151
  assert_includes(stdout, "--atomic-write-bytes N")
149
152
  assert_includes(stdout, "Pipeline:")
150
153
  assert_includes(stdout, "Connect stages with top-level >>.")
@@ -153,6 +156,16 @@ assert_includes(stdout, "See Also:")
153
156
  assert_includes(stdout, "https://github.com/kazuho/jrf#readme")
154
157
  assert_equal([], lines(stderr), "help stderr output")
155
158
 
159
+ stdout, stderr, status = Open3.capture3("./exe/jrf", "--version")
160
+ assert_success(status, stderr, "version long option")
161
+ assert_equal([Jrf::VERSION], lines(stdout), "version long option output")
162
+ assert_equal([], lines(stderr), "version long option stderr")
163
+
164
+ stdout, stderr, status = Open3.capture3("./exe/jrf", "-V")
165
+ assert_success(status, stderr, "version short option")
166
+ assert_equal([Jrf::VERSION], lines(stdout), "version short option output")
167
+ assert_equal([], lines(stderr), "version short option stderr")
168
+
156
169
  threshold_input = StringIO.new((1..4).map { |i| "{\"foo\":\"#{'x' * 1020}\",\"i\":#{i}}\n" }.join)
157
170
  buffered_runner = RecordingRunner.new(inputs: [threshold_input], out: StringIO.new, err: StringIO.new)
158
171
  buffered_runner.run('_')
@@ -190,6 +203,18 @@ stdout, stderr, status = Open3.capture3("./exe/jrf", "--atomic-write-bytes", "0"
190
203
  assert_failure(status, "atomic write bytes rejects zero")
191
204
  assert_includes(stderr, "--atomic-write-bytes requires a positive integer")
192
205
 
206
+ if defined?(RubyVM::YJIT) && RubyVM::YJIT.respond_to?(:enabled?)
207
+ yjit_probe = "{\"probe\":1}\n"
208
+
209
+ stdout, stderr, status = run_jrf('RubyVM::YJIT.enabled?', yjit_probe)
210
+ assert_success(status, stderr, "default jit enablement")
211
+ assert_equal(%w[true], lines(stdout), "default jit enablement output")
212
+
213
+ stdout, stderr, status = run_jrf('RubyVM::YJIT.enabled?', yjit_probe, "--no-jit")
214
+ assert_success(status, stderr, "no-jit option")
215
+ assert_equal(%w[false], lines(stdout), "no-jit option output")
216
+ end
217
+
193
218
  Dir.mktmpdir do |dir|
194
219
  gz_path = File.join(dir, "input.ndjson.gz")
195
220
  Zlib::GzipWriter.open(gz_path) do |io|
@@ -444,6 +469,14 @@ assert_equal(
444
469
  "array percentile output"
445
470
  )
446
471
 
472
+ stdout, stderr, status = run_jrf('percentile(_["foo"], 0.25.step(1.0, 0.25))', input_sum)
473
+ assert_success(status, stderr, "enumerable percentile")
474
+ assert_equal(
475
+ ['[1,2,3,4]'],
476
+ lines(stdout),
477
+ "enumerable percentile output"
478
+ )
479
+
447
480
  input_with_nil = <<~NDJSON
448
481
  {"foo":1}
449
482
  {"foo":null}
@@ -747,6 +780,26 @@ stdout, stderr, status = run_jrf('map_values { |v| reduce(0) { |acc, x| acc + x
747
780
  assert_success(status, stderr, "map_values with reduce")
748
781
  assert_equal(['{"a":6,"b":60}'], lines(stdout), "map_values with reduce output")
749
782
 
783
+ stdout, stderr, status = run_jrf('map { |k, v| "#{k}:#{v}" }', input_map_values)
784
+ assert_success(status, stderr, "map over hash transform")
785
+ assert_equal(['["a:1","b:10"]', '["a:2","b:20"]', '["a:3","b:30"]'], lines(stdout), "map over hash transform output")
786
+
787
+ stdout, stderr, status = run_jrf('map { |pair| pair }', input_map_values)
788
+ assert_success(status, stderr, "map over hash single block arg")
789
+ assert_equal(['[["a",1],["b",10]]', '[["a",2],["b",20]]', '[["a",3],["b",30]]'], lines(stdout), "map over hash single block arg output")
790
+
791
+ stdout, stderr, status = run_jrf('map { |k, v| select(v >= 10 && k != "a") }', input_map_values)
792
+ assert_success(status, stderr, "map over hash transform with select")
793
+ assert_equal(['[10]', '[20]', '[30]'], lines(stdout), "map over hash transform with select output")
794
+
795
+ stdout, stderr, status = run_jrf('map { |k, v| sum(v + k.length) }', input_map_values)
796
+ assert_success(status, stderr, "map over hash with sum")
797
+ assert_equal(['[9,63]'], lines(stdout), "map over hash with sum output")
798
+
799
+ stdout, stderr, status = run_jrf('map { |k, v| sum(_["a"] + v + k.length) }', input_map_values)
800
+ assert_success(status, stderr, "map over hash keeps ambient _")
801
+ assert_equal(['[15,69]'], lines(stdout), "map over hash ambient _ output")
802
+
750
803
  stdout, stderr, status = run_jrf('select(false) >> map { |x| sum(x) }', input_map)
751
804
  assert_success(status, stderr, "map no matches")
752
805
  assert_equal([], lines(stdout), "map no matches output")
@@ -881,6 +934,18 @@ assert_equal([[4, 6]], j.call([[1, 2], [3, 4]]), "library map reduce")
881
934
  j = Jrf.new(proc { map_values { |v| v * 10 } })
882
935
  assert_equal([{"a" => 10, "b" => 20}], j.call([{"a" => 1, "b" => 2}]), "library map_values transform")
883
936
 
937
+ # map hash transform
938
+ j = Jrf.new(proc { map { |k, v| "#{k}=#{v}" } })
939
+ assert_equal([["a=1", "b=2"]], j.call([{"a" => 1, "b" => 2}]), "library map hash transform")
940
+
941
+ # map hash single block arg
942
+ j = Jrf.new(proc { map { |pair| pair } })
943
+ assert_equal([[["a", 1], ["b", 2]]], j.call([{"a" => 1, "b" => 2}]), "library map hash single block arg")
944
+
945
+ # map hash reduce
946
+ j = Jrf.new(proc { map { |k, v| sum(v + k.length) } })
947
+ assert_equal([[5, 7]], j.call([{"a" => 1, "b" => 2}, {"a" => 2, "b" => 3}]), "library map hash reduce")
948
+
884
949
  # group_by
885
950
  j = Jrf.new(proc { group_by(_["k"]) { count() } })
886
951
  assert_equal([{"x" => 2, "y" => 1}], j.call([{"k" => "x"}, {"k" => "x"}, {"k" => "y"}]), "library group_by")
@@ -901,4 +966,13 @@ assert_equal([{"a" => 3}], j.call([{"a" => 1}, {"a" => 2}, {"a" => 3}]), "librar
901
966
  j = Jrf.new(proc { sum(_) })
902
967
  assert_equal([], j.call([]), "library empty input")
903
968
 
969
+ ctx = Jrf::RowContext.new
970
+ stage = Jrf::Stage.new(ctx, proc { })
971
+ first_token = stage.step_reduce(1, initial: 0) { |acc, v| acc + v }
972
+ assert_equal(0, first_token.index, "step_reduce returns token while classifying reducer stage")
973
+ stage.instance_variable_set(:@mode, :reducer)
974
+ stage.instance_variable_set(:@cursor, 0)
975
+ second_token = stage.step_reduce(2, initial: 0) { |acc, v| acc + v }
976
+ raise "expected DROPPED for established reducer slot" unless second_token.equal?(Jrf::Control::DROPPED)
977
+
904
978
  puts "ok"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jrf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - kazuho