pru 0.2.1 → 0.3.0

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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/bin/pru +47 -12
  3. data/lib/pru/version.rb +1 -1
  4. data/lib/pru.rb +33 -17
  5. metadata +4 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e3aadfba1442b50864edd1817ede514cf9592cd5cda84df67969958160d4053
4
- data.tar.gz: 56047bf61575504fc0c3c489b084c3537ca715d20498c2571d01df92585fe684
3
+ metadata.gz: f72e74d5784d176b7582189d4a7b805e62c12e574b6d778549c664ecb02fe0b1
4
+ data.tar.gz: f5135f6bee632dfa47a596e5236ad503c71ce9d505f6daee242338ce05db3620
5
5
  SHA512:
6
- metadata.gz: '0995c519eaee3cf6b9fd41fa21578b3b4138214d2a5cbc6848fa03d37f9ad0328f16bef8dfbee6c274744408708d7ac47eeb545b5c228f89cf41cc2c42336de0'
7
- data.tar.gz: fd7de3b48e9291ab01ad4478b65bc76a02f97841f4d84aa93ae624a600e2c188f921f32e5f2fc90e10cd76579f7fb2434771a9fb586fdebf9fe96dab29c28513
6
+ metadata.gz: f7f1f60fa40cc36551551f42d1c98c469aa33c4dbd1cc3e6c03a27d008e5ae323bb17e10bf1e5a91f42a3e8f9911ef6faec8508044ae403ac44f51c96574b489
7
+ data.tar.gz: 456555cd258f16f86dcdc86392c1be6be9c536fa5f60792c6e3d9cd2fb98ecd28d3ecdc3657e333f2ed36b718a2097713059ffdda077afeee74985a09093e8ec
data/bin/pru CHANGED
@@ -7,10 +7,9 @@ $LOAD_PATH << "#{root}/lib" if File.exist?("#{root}/Gemfile")
7
7
 
8
8
  require 'pru'
9
9
 
10
- usage = nil
11
10
  options = {}
12
11
 
13
- OptionParser.new do |opts|
12
+ parser = OptionParser.new do |opts|
14
13
  opts.banner = <<-BANNER.gsub(/^ /, "")
15
14
  Pipeable Ruby
16
15
 
@@ -28,6 +27,8 @@ OptionParser.new do |opts|
28
27
  Options:
29
28
  BANNER
30
29
  opts.on("-r", "--reduce CODE","reduce via CODE") {|code| options[:reduce] = code }
30
+ opts.on("-j", "--json","Parse input as a JSON stream, each value is an item") { options[:json] = true }
31
+ opts.on("-k", "--k8s","Like --json, but iterate the \"items\" array if present (kubectl -o json)") { options[:json] = true; options[:k8s] = true }
31
32
  opts.separator ''
32
33
  opts.on('-I', '--libdir DIR', 'Add DIR to load path') { |dir| $LOAD_PATH << dir }
33
34
  opts.on('--require LIB', 'Require LIB (also comma-separated)') { |lib| lib.split(',').each{|l| require l } }
@@ -35,30 +36,51 @@ OptionParser.new do |opts|
35
36
  opts.separator ''
36
37
  opts.on("-h", "--help","Show this.") { puts opts; exit }
37
38
  opts.on('-v', '--version','Show Version'){ require 'pru/version'; puts Pru::VERSION; exit}
38
- usage = opts
39
- end.parse!
40
-
41
- if ARGV.empty? && options.empty? # no arguments -> show usage
42
- puts usage
43
- exit
44
39
  end
40
+ parser.parse!
45
41
 
46
- abort "Too many arguments, see --help" if ARGV.size > 2
42
+ # bad arguments -> fail
43
+ if ARGV.empty? && options.empty?
44
+ abort parser.to_s
45
+ end
46
+ if ARGV.size > 2
47
+ abort "Too many arguments, see --help"
48
+ end
49
+ if ARGV.size == 2 && options[:reduce]
50
+ abort "Cannot combine a reduce argument with --reduce, see --help"
51
+ end
52
+ if options[:file] && ARGV.empty? && !options[:reduce]
53
+ abort "No code given for --inplace-edit, see --help"
54
+ end
47
55
 
48
56
  map, reduce = ARGV
49
57
  reduce ||= options[:reduce]
50
58
  map = 'true' if !map || map.empty?
51
59
 
60
+ # reading from file or stdin ?
52
61
  if options[:file]
53
62
  output_lines = []
54
63
  input = File.read(options[:file])
55
64
  newline = input[/\r\n|\r|\n/]
56
- trailing_newline = (input =~ /#{newline}\Z/)
65
+ trailing_newline = (newline && input.end_with?(newline))
57
66
  else
58
67
  input = $stdin
59
68
  end
60
69
 
70
+ # output as JSON ?
71
+ if options[:json]
72
+ require 'json' # also used by --k8s, which sets :json
73
+ output_format = lambda do |out|
74
+ case out
75
+ when Hash, Array then JSON.pretty_generate(out)
76
+ else out
77
+ end
78
+ end
79
+ end
80
+
81
+ # collect results into list or print them
61
82
  collector = lambda do |line|
83
+ line = output_format.call(line) if output_format
62
84
  if output_lines
63
85
  output_lines << line
64
86
  else
@@ -70,14 +92,27 @@ collector = lambda do |line|
70
92
  end
71
93
  end
72
94
 
95
+ # find all items (ideally lazy so we can stream)
96
+ items = if options[:k8s]
97
+ # single document (e.g. `kubectl get ... -o json`) -> parse once and iterate its items
98
+ JSON.parse(input.read).fetch("items")
99
+ elsif options[:json]
100
+ # lazy stream of JSON values
101
+ Pru.each_json(input)
102
+ else
103
+ # plain lines, chomped, lazily so streaming/head still work
104
+ input.each_line.lazy.map { |line| line.chomp }
105
+ end
106
+
73
107
  if reduce
74
108
  results = []
75
- Pru.map(input, map) { |out| results << out }
109
+ Pru.map(items, map) { |out| results << out }
76
110
  collector.call Pru.reduce(results, reduce)
77
111
  else
78
- Pru.map(input, map) { |out| collector.call out }
112
+ Pru.map(items, map) { |out| collector.call out }
79
113
  end
80
114
 
115
+ # write back to file if requested
81
116
  if options[:file]
82
117
  content = output_lines.join(newline)
83
118
  content << newline if trailing_newline
data/lib/pru/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pru
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/pru.rb CHANGED
@@ -2,34 +2,50 @@ require 'pru/core_ext/array'
2
2
 
3
3
  module Pru
4
4
  class << self
5
- def map(io, code)
6
- String.class_eval <<-RUBY, __FILE__, __LINE__ + 1
7
- def _pru(i)
8
- #{code}
9
- end
10
- RUBY
11
-
5
+ def map(items, code)
6
+ block = compile(code)
12
7
  i = 0
13
- io.each_line do |line|
8
+ items.each do |item|
14
9
  i += 1
15
- line.chomp!
16
- result = line._pru(i) or next
10
+ result = item.instance_exec(i, &block) || next
17
11
 
18
12
  case result
19
- when true then yield line
20
- when Regexp then yield line if line =~ result
13
+ when true then yield item
14
+ when Regexp then yield item if item =~ result
21
15
  else yield result
22
16
  end
23
17
  end
24
18
  end
25
19
 
26
20
  def reduce(array, code)
27
- Array.class_eval <<-RUBY, __FILE__, __LINE__ + 1
28
- def _pru
29
- #{code}
21
+ array.instance_exec(&compile(code))
22
+ end
23
+
24
+ # An enumerable of JSON values parsed from a stream (newline-delimited or multiline),
25
+ # parsed lazily so we process as input arrives, by accumulating lines until the buffer
26
+ # forms a complete value.
27
+ # TODO: this is not very efficient, but keeping track of opening/closing braces might be ugly too
28
+ def each_json(io)
29
+ Enumerator.new do |yielder|
30
+ buffer = +""
31
+ io.each_line do |line|
32
+ buffer << line
33
+ begin
34
+ item = JSON.parse(buffer)
35
+ rescue JSON::ParserError
36
+ next
37
+ end
38
+ yielder << item
39
+ buffer = +""
30
40
  end
31
- RUBY
32
- array._pru
41
+ raise JSON::ParserError, "unexpected trailing input: #{buffer.strip}" unless buffer.strip.empty?
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def compile(code)
48
+ eval("proc { |i| #{code} }", TOPLEVEL_BINDING, __FILE__, __LINE__)
33
49
  end
34
50
  end
35
51
  end
metadata CHANGED
@@ -1,16 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pru
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2021-11-18 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
- description:
14
12
  email: michael@grosser.it
15
13
  executables:
16
14
  - pru
@@ -25,7 +23,6 @@ homepage: https://github.com/grosser/pru
25
23
  licenses:
26
24
  - MIT
27
25
  metadata: {}
28
- post_install_message:
29
26
  rdoc_options: []
30
27
  require_paths:
31
28
  - lib
@@ -33,15 +30,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
33
30
  requirements:
34
31
  - - ">="
35
32
  - !ruby/object:Gem::Version
36
- version: '2.5'
33
+ version: '2.6'
37
34
  required_rubygems_version: !ruby/object:Gem::Requirement
38
35
  requirements:
39
36
  - - ">="
40
37
  - !ruby/object:Gem::Version
41
38
  version: '0'
42
39
  requirements: []
43
- rubygems_version: 3.2.16
44
- signing_key:
40
+ rubygems_version: 4.0.10
45
41
  specification_version: 4
46
42
  summary: Pipeable Ruby - forget about grep / sed / awk / wc ... use pure, readable
47
43
  Ruby!