jrf 0.1.11 → 0.1.13

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: edaeb729eecf63449c68fce92740acb8221885ff28a5ed26fe7bea32eb0a9bfa
4
- data.tar.gz: 30364ac5e6eee46506da8c4d3ff2d8a05f12cd91b2b5cd248a12e612ea333891
3
+ metadata.gz: 2862eaf6bd5f2486ea2c6aebf5caa4fbc2de56f419625bf8bb462392a3ea5dd9
4
+ data.tar.gz: 3f29e7024f4e33606d78ad01ce4c45f37c9cd652ba94ac490866cd877368037a
5
5
  SHA512:
6
- metadata.gz: 22e73015ffb398d376c3636f8a9e324c01bd56ae5dc1170b39a8ad5c600c10273fadec1de5385706798e3aff19449b446744e608f1437ca73113871c33aa9b1b
7
- data.tar.gz: 4c803ad64d65d6ae1d671426c2e81a4741ebcebd66ca4fb288574336e0a1088b8be2aa78f4235213ffc5794c92877410c88cbce7dae1f88bfb6369f6f37e3970
6
+ metadata.gz: 04f55e0ea8c24f70126964beffbe80bee1800e1e210da2f96186bb8ebdf5542e5dfbab9c06b48624da4ec35912d02456561ec6c0d2c66c094de001ecf7f4096f
7
+ data.tar.gz: '093821f35539be4561867b711664a31d3441052fe53e1c0f73489cd8b11fdf845bfb5573375f880ce07cd73ffc2f1d0514b55b760227c01ab515afd39d8ac08a'
data/jrf.gemspec CHANGED
@@ -16,6 +16,8 @@ Gem::Specification.new do |spec|
16
16
  spec.bindir = "exe"
17
17
  spec.executables = ["jrf"]
18
18
  spec.add_dependency "oj", ">= 3.16"
19
+ spec.add_development_dependency "minitest", ">= 5.0"
20
+ spec.add_development_dependency "rake", ">= 13.0"
19
21
 
20
22
  spec.files = Dir.glob("{exe,lib,test}/*") + Dir.glob("lib/**/*") + %w[DESIGN.txt jrf.gemspec Gemfile Rakefile].select { |path| File.file?(path) }
21
23
  end
@@ -28,12 +28,12 @@ module Jrf
28
28
  end
29
29
  end
30
30
 
31
- def initialize(inputs:, out: $stdout, err: $stderr, lax: false, pretty: false, atomic_write_bytes: DEFAULT_OUTPUT_BUFFER_LIMIT)
31
+ def initialize(inputs:, out: $stdout, err: $stderr, lax: false, output_format: :json, atomic_write_bytes: DEFAULT_OUTPUT_BUFFER_LIMIT)
32
32
  @inputs = inputs
33
33
  @out = out
34
34
  @err = err
35
35
  @lax = lax
36
- @pretty = pretty
36
+ @output_format = output_format
37
37
  @atomic_write_bytes = atomic_write_bytes
38
38
  @output_buffer = +""
39
39
  end
@@ -49,8 +49,13 @@ module Jrf
49
49
  pipeline = Pipeline.new(*blocks)
50
50
 
51
51
  input_enum = Enumerator.new { |y| each_input_value { |v| y << v } }
52
- pipeline.call(input_enum) do |value|
53
- emit_output(value)
52
+
53
+ if @output_format == :tsv
54
+ values = []
55
+ pipeline.call(input_enum) { |value| values << value }
56
+ emit_tsv(values)
57
+ else
58
+ pipeline.call(input_enum) { |value| emit_output(value) }
54
59
  end
55
60
  ensure
56
61
  write_output(@output_buffer)
@@ -109,7 +114,54 @@ module Jrf
109
114
  end
110
115
 
111
116
  def emit_output(value)
112
- record = (@pretty ? JSON.pretty_generate(value) : JSON.generate(value)) << "\n"
117
+ record = (@output_format == :pretty ? JSON.pretty_generate(value) : JSON.generate(value)) << "\n"
118
+ buffer_output(record)
119
+ end
120
+
121
+ def emit_tsv(values)
122
+ rows = values.flat_map { |value| value_to_rows(value) }
123
+ rows.each do |row|
124
+ buffer_output(row.join("\t") << "\n")
125
+ end
126
+ end
127
+
128
+ def value_to_rows(value)
129
+ case value
130
+ when Hash
131
+ value.map { |k, v|
132
+ case v
133
+ when Array
134
+ [format_cell(k)] + v.map { |e| format_cell(e) }
135
+ else
136
+ [format_cell(k), format_cell(v)]
137
+ end
138
+ }
139
+ when Array
140
+ value.map { |row|
141
+ case row
142
+ when Array
143
+ row.map { |e| format_cell(e) }
144
+ else
145
+ [format_cell(row)]
146
+ end
147
+ }
148
+ else
149
+ [[format_cell(value)]]
150
+ end
151
+ end
152
+
153
+ def format_cell(value)
154
+ case value
155
+ when nil
156
+ "null"
157
+ when Numeric, String, true, false
158
+ value.to_s
159
+ else
160
+ JSON.generate(value)
161
+ end
162
+ end
163
+
164
+ def buffer_output(record)
113
165
  if @output_buffer.bytesize + record.bytesize <= @atomic_write_bytes
114
166
  @output_buffer << record
115
167
  else
data/lib/jrf/cli.rb CHANGED
@@ -16,7 +16,8 @@ module Jrf
16
16
  Options:
17
17
  -v, --verbose print parsed stage expressions
18
18
  --lax allow multiline JSON texts; split inputs by whitespace (also detects JSON-SEQ RS 0x1e)
19
- -p, --pretty pretty-print JSON output instead of compact NDJSON
19
+ -o, --output FORMAT
20
+ output format: json (default), pretty, tsv
20
21
  -r, --require LIBRARY
21
22
  require LIBRARY before evaluating stages
22
23
  --no-jit do not enable YJIT, even when supported by the Ruby runtime
@@ -43,7 +44,7 @@ module Jrf
43
44
  def self.run(argv = ARGV, input: ARGF, out: $stdout, err: $stderr)
44
45
  verbose = false
45
46
  lax = false
46
- pretty = false
47
+ output_format = :json
47
48
  jit = true
48
49
  required_libraries = []
49
50
  atomic_write_bytes = Runner::DEFAULT_OUTPUT_BUFFER_LIMIT
@@ -52,7 +53,7 @@ module Jrf
52
53
  opts.banner = USAGE
53
54
  opts.on("-v", "--verbose", "print parsed stage expressions") { verbose = true }
54
55
  opts.on("--lax", "allow multiline JSON texts; split inputs by whitespace (also detects JSON-SEQ RS 0x1e)") { lax = true }
55
- opts.on("-p", "--pretty", "pretty-print JSON output instead of compact NDJSON") { pretty = true }
56
+ opts.on("-o", "--output FORMAT", %w[json pretty tsv], "output format: json, pretty, tsv") { |fmt| output_format = fmt.to_sym }
56
57
  opts.on("-r", "--require LIBRARY", "require LIBRARY before evaluating stages") { |library| required_libraries << library }
57
58
  opts.on("--no-jit", "do not enable YJIT, even when supported by the Ruby runtime") { jit = false }
58
59
  opts.on("--atomic-write-bytes N", Integer, "group short outputs into atomic writes of up to N bytes") do |value|
@@ -113,7 +114,7 @@ module Jrf
113
114
  out: out,
114
115
  err: err,
115
116
  lax: lax,
116
- pretty: pretty,
117
+ output_format: output_format,
117
118
  atomic_write_bytes: atomic_write_bytes
118
119
  ).run(expression, verbose: verbose)
119
120
  end
@@ -59,6 +59,10 @@ module Jrf
59
59
  end
60
60
  end
61
61
 
62
+ define_reducer(:count_if) do |_ctx, condition, block: nil|
63
+ { value: condition, initial: 0, step: ->(acc, v) { v ? (acc + 1) : acc } }
64
+ end
65
+
62
66
  define_reducer(:min) do |_ctx, value, block: nil|
63
67
  { value: value, initial: nil, step: ->(acc, v) { v.nil? ? acc : (acc.nil? || v < acc ? v : acc) } }
64
68
  end
@@ -170,16 +174,22 @@ module Jrf
170
174
  @__jrf_current_stage.step_reduce(current_input, initial: initial, &block)
171
175
  end
172
176
 
173
- def map(&block)
177
+ def map(collection = nil, &block)
174
178
  raise ArgumentError, "map requires a block" unless block
175
179
 
176
- @__jrf_current_stage.step_map(:map, current_input, &block)
180
+ @__jrf_current_stage.step_map(:map, collection || current_input, &block)
177
181
  end
178
182
 
179
- def map_values(&block)
183
+ def map_values(collection = nil, &block)
180
184
  raise ArgumentError, "map_values requires a block" unless block
181
185
 
182
- @__jrf_current_stage.step_map(:map_values, current_input, &block)
186
+ @__jrf_current_stage.step_map(:map_values, collection || current_input, &block)
187
+ end
188
+
189
+ def apply(collection = nil, &block)
190
+ raise ArgumentError, "apply requires a block" unless block
191
+
192
+ @__jrf_current_stage.step_apply(collection || current_input, &block)
183
193
  end
184
194
 
185
195
  def group_by(key, &block)
data/lib/jrf/stage.rb CHANGED
@@ -113,6 +113,35 @@ module Jrf
113
113
  ReducerToken.new(idx)
114
114
  end
115
115
 
116
+ def step_apply(collection, &block)
117
+ raise TypeError, "apply expects Array, got #{collection.class}" unless collection.is_a?(Array)
118
+
119
+ apply_reducers = []
120
+ template = nil
121
+ results = []
122
+
123
+ collection.each do |v|
124
+ with_scoped_reducers(apply_reducers) do
125
+ result = @ctx.send(:__jrf_with_current_input, v) { block.call(v) }
126
+ template ||= result
127
+ results << result
128
+ end
129
+ end
130
+
131
+ if apply_reducers.any?
132
+ self.class.resolve_template(template, apply_reducers)
133
+ else
134
+ results.each_with_object([]) do |mapped, arr|
135
+ next if mapped.equal?(Control::DROPPED)
136
+ if mapped.is_a?(Control::Flat)
137
+ arr.concat(Array(mapped.value))
138
+ else
139
+ arr << mapped
140
+ end
141
+ end
142
+ end
143
+ end
144
+
116
145
  def step_group_by(key, &block)
117
146
  idx = @cursor
118
147
  map_reducer = (@reducers[idx] ||= MapReducer.new(:group_by, false))
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.11"
4
+ VERSION = "0.1.13"
5
5
  end