pru 0.2.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d7ab83319d415eed24a2650913be76d27c0652a1
4
- data.tar.gz: 69d39153bc5388450fb4125e6d66513740e81273
2
+ SHA256:
3
+ metadata.gz: f72e74d5784d176b7582189d4a7b805e62c12e574b6d778549c664ecb02fe0b1
4
+ data.tar.gz: f5135f6bee632dfa47a596e5236ad503c71ce9d505f6daee242338ce05db3620
5
5
  SHA512:
6
- metadata.gz: f21fbb6b7a9925e65176555fe73b732f9708c32dbc202d83555542b710f14ef92bbc6543edb1f0d0b1f21fc38b4f2dbea05c1189a85353e3a49fc0bd74b70d00
7
- data.tar.gz: 8c10b8fdd4a47e024c603729b6d5b6b260f0402c4c71e1e0c7fa2f0182c68ca50f4adfee06f389d4bdcfc4b3da2f9d937897b00bf67831ec83bc0b581c528133
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,41 +36,83 @@ 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|
62
- output_lines ? output_lines << line : puts(line)
83
+ line = output_format.call(line) if output_format
84
+ if output_lines
85
+ output_lines << line
86
+ else
87
+ begin
88
+ puts(line)
89
+ rescue Errno::EPIPE
90
+ exit 0
91
+ end
92
+ end
93
+ end
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 }
63
105
  end
64
106
 
65
107
  if reduce
66
108
  results = []
67
- Pru.map(input, map) { |out| results << out }
109
+ Pru.map(items, map) { |out| results << out }
68
110
  collector.call Pru.reduce(results, reduce)
69
111
  else
70
- Pru.map(input, map) { |out| collector.call out }
112
+ Pru.map(items, map) { |out| collector.call out }
71
113
  end
72
114
 
115
+ # write back to file if requested
73
116
  if options[:file]
74
117
  content = output_lines.join(newline)
75
118
  content << newline if trailing_newline
@@ -11,8 +11,8 @@ class Array
11
11
  end
12
12
  end unless method_defined?(:sum)
13
13
 
14
- def mean(method = nil, &block)
15
- sum(method, &block) / size.to_f
14
+ def mean(*args, &block)
15
+ sum(*args, &block) / size.to_f
16
16
  end unless method_defined?(:mean)
17
17
 
18
18
  def grouped
data/lib/pru/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pru
2
- VERSION = "0.2.0"
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.0
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: 2016-03-24 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,16 +30,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
33
30
  requirements:
34
31
  - - ">="
35
32
  - !ruby/object:Gem::Version
36
- version: 2.0.0
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
- rubyforge_project:
44
- rubygems_version: 2.5.1
45
- signing_key:
40
+ rubygems_version: 4.0.10
46
41
  specification_version: 4
47
42
  summary: Pipeable Ruby - forget about grep / sed / awk / wc ... use pure, readable
48
43
  Ruby!