ru2 2.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/lib/ru/array.rb ADDED
@@ -0,0 +1,88 @@
1
+ module Ru
2
+ class Array
3
+ def initialize(array)
4
+ @data = array.to_a
5
+ end
6
+
7
+ def each_line
8
+ Ru::Iterator.new(self)
9
+ end
10
+
11
+ def files
12
+ @data.map! do |line|
13
+ Ru::File.new(line)
14
+ end
15
+ self
16
+ end
17
+
18
+ def format(format='l')
19
+ @data.map! do |item|
20
+ item.format(format)
21
+ end
22
+ self
23
+ end
24
+
25
+ def grep(pattern)
26
+ if pattern.kind_of?(String)
27
+ pattern = Regexp.new(pattern)
28
+ end
29
+ select! do |item|
30
+ item.to_s =~ pattern
31
+ end
32
+ self
33
+ end
34
+
35
+ def map(method=nil, *args, &block)
36
+ if method.nil? && !block_given?
37
+ to_a.map
38
+ elsif method.nil?
39
+ array = to_a.map(&block)
40
+ self.class.new(array)
41
+ else
42
+ array = to_a.map { |item| item.send(method, *args) }
43
+ self.class.new(array)
44
+ end
45
+ end
46
+
47
+ def select(*args, &block)
48
+ delegate_to_array(:select, *args, &block)
49
+ end
50
+
51
+ def to_a
52
+ @data
53
+ end
54
+
55
+ def to_ary
56
+ to_a
57
+ end
58
+
59
+ def to_s
60
+ to_a.join("\n")
61
+ end
62
+
63
+ def to_self
64
+ self
65
+ end
66
+
67
+ def ==(other)
68
+ self.to_a == other.to_a
69
+ end
70
+
71
+ private
72
+
73
+ def method_missing(method, *args, &block)
74
+ delegate_to_array(method, *args, &block)
75
+ end
76
+
77
+ def delegate_to_array(method, *args, &block)
78
+ result = @data.send(method, *args, &block)
79
+ if result.kind_of? Enumerator::Lazy
80
+ Ru::Stream.new(result)
81
+ elsif result.kind_of? ::Array
82
+ self.class.new(result)
83
+ else
84
+ result
85
+ end
86
+ end
87
+ end
88
+ end
data/lib/ru/file.rb ADDED
@@ -0,0 +1,94 @@
1
+ require 'etc'
2
+
3
+ module Ru
4
+ class File
5
+ def initialize(path)
6
+ @path = path
7
+ end
8
+
9
+ def basename
10
+ delegate_to_file(:basename)
11
+ end
12
+
13
+ def ctime
14
+ delegate_to_file(:ctime)
15
+ end
16
+
17
+ def extname
18
+ delegate_to_file(:extname)
19
+ end
20
+
21
+ def format(format='l')
22
+ separator = "\t"
23
+ case format
24
+ when 'l'
25
+ datetime = ctime.strftime(%w{%Y-%m-%d %H:%M}.join(separator))
26
+ return [omode, owner, group, size, datetime, name].join(separator)
27
+ else
28
+ raise 'format not supported'
29
+ end
30
+ end
31
+
32
+ def ftype
33
+ delegate_to_file(:ftype)
34
+ end
35
+
36
+ def gid
37
+ delegate_to_stat(:gid)
38
+ end
39
+
40
+ def group
41
+ Etc.getgrgid(gid).name
42
+ end
43
+
44
+ def mode
45
+ delegate_to_stat(:mode)
46
+ end
47
+
48
+ def mtime
49
+ delegate_to_file(:mtime)
50
+ end
51
+
52
+ def omode
53
+ '%o' % world_readable?
54
+ end
55
+
56
+ def owner
57
+ Etc.getpwuid(uid).name
58
+ end
59
+
60
+ def size
61
+ delegate_to_file(:size)
62
+ end
63
+
64
+ def to_s
65
+ name
66
+ end
67
+
68
+ def uid
69
+ delegate_to_stat(:uid)
70
+ end
71
+
72
+ def world_readable?
73
+ delegate_to_stat(:world_readable?)
74
+ end
75
+
76
+ def <=>(other)
77
+ name <=> other.name
78
+ end
79
+
80
+ alias_method :name, :basename
81
+ alias_method :created_at, :ctime
82
+ alias_method :updated_at, :mtime
83
+
84
+ private
85
+
86
+ def delegate_to_file(method)
87
+ ::File.send(method, @path)
88
+ end
89
+
90
+ def delegate_to_stat(method)
91
+ ::File.stat(@path).send(method)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,56 @@
1
+ module Ru
2
+ class Iterator
3
+ class << self
4
+ def redefined_methods
5
+ @redefined_methods ||= begin
6
+ preserved_methods = %{initialize method_missing respond_to? to_a}
7
+ [].public_methods.select do |method|
8
+ method = method.to_s
9
+ method =~ /^[a-z]/ && !preserved_methods.include?(method)
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ def initialize(enum)
16
+ @enum = enum
17
+ end
18
+
19
+ def to_a
20
+ case @enum
21
+ when ::Array
22
+ Ru::Array.new(@enum)
23
+ when Ru::Array
24
+ @enum
25
+ when Ru::Stream
26
+ @enum.to_a
27
+ end
28
+ end
29
+
30
+ def to_stdout
31
+ case @enum
32
+ when ::Array
33
+ @enum.join("\n")
34
+ else
35
+ @enum.to_s
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def method_missing(method, *args, &block)
42
+ map_method(method, *args, &block)
43
+ end
44
+
45
+ def map_method(method, *args, &block)
46
+ @enum = @enum.map { |item| item.send(method, *args, &block) }
47
+ self
48
+ end
49
+
50
+ redefined_methods.each do |method|
51
+ define_method(method) do |*args|
52
+ map_method(method, *args)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,33 @@
1
+ require 'erb'
2
+
3
+ module Ru
4
+ class OptionPrinter
5
+ def exists?(option_key)
6
+ options[option_key].present?
7
+ end
8
+
9
+ def run(option_key, option_value=nil)
10
+ send(options[option_key], *option_value)
11
+ end
12
+
13
+ private
14
+
15
+ def options
16
+ {
17
+ help: :get_help,
18
+ version: :get_version
19
+ }
20
+ end
21
+
22
+ def get_help
23
+ namespace = OpenStruct.new(version: Ru::VERSION)
24
+ template_path = ::File.expand_path("../../../doc/help.erb", __FILE__)
25
+ template = ::File.open(template_path).read
26
+ ERB.new(template).result(namespace.instance_eval { binding })
27
+ end
28
+
29
+ def get_version
30
+ Ru::VERSION
31
+ end
32
+ end
33
+ end
data/lib/ru/process.rb ADDED
@@ -0,0 +1,121 @@
1
+ require 'optparse'
2
+
3
+ module Ru
4
+ class Process
5
+
6
+ def initialize
7
+ @option_printer = OptionPrinter.new
8
+ end
9
+
10
+ def run
11
+ output = process_options
12
+ return output if output
13
+
14
+ args = ARGV.dup
15
+ @code = args.shift
16
+
17
+ if @code.blank?
18
+ $stderr.puts @option_printer.run(:help)
19
+ return
20
+ end
21
+
22
+ @stdin = get_stdin(args, @options[:stream]) unless @code.start_with?('! ')
23
+ @code = prepare_code(@code)
24
+
25
+ context =
26
+ if @stdin.nil?
27
+ Ru::Array.new([])
28
+ else
29
+ if @options[:stream]
30
+ if @options[:binary]
31
+ Ru::Stream.new(@stdin.each_byte.lazy)
32
+ else
33
+ Ru::Stream.new(@stdin.each_line.lazy.map { |line| line.chomp("\n".freeze) })
34
+ end
35
+ else
36
+ if @options[:binary]
37
+ Ru::Array.new(@stdin.bytes)
38
+ else
39
+ # Prevent 'invalid byte sequence in UTF-8'
40
+ @stdin.encode!('UTF-8', 'UTF-8', invalid: :replace)
41
+ Ru::Array.new(@stdin.split("\n"))
42
+ end
43
+ end
44
+ end
45
+ output = context.instance_eval(@code) || @stdin
46
+
47
+ prepare_output(output)
48
+ end
49
+
50
+ private
51
+
52
+ def prepare_code(code)
53
+ if code.kind_of?(String)
54
+ if code.start_with?('[')
55
+ code = 'to_self' + code
56
+ elsif code.start_with?('! ')
57
+ code = code[2..-1]
58
+ end
59
+ end
60
+ code
61
+ end
62
+
63
+ def prepare_output(output)
64
+ if output.respond_to?(:to_stdout)
65
+ output = output.to_stdout
66
+ end
67
+ if output.kind_of?(::Array)
68
+ output = output.join("\n")
69
+ end
70
+ output.to_s
71
+ end
72
+
73
+ def process_options
74
+ @options = get_options
75
+ @options.each do |option, value|
76
+ if @option_printer.exists?(option)
77
+ return @option_printer.run(option)
78
+ end
79
+ end
80
+ nil
81
+ end
82
+
83
+ def get_options
84
+ options = {}
85
+ OptionParser.new do |opts|
86
+ opts.on("-h", "--help", "Print help") do
87
+ options[:help] = true
88
+ end
89
+
90
+ opts.on("-v", "--version", "Print version") do
91
+ options[:version] = true
92
+ end
93
+
94
+ opts.on('-s', '--stream', 'Stream mode') do
95
+ options[:stream] = true
96
+ end
97
+
98
+ opts.on('-b', '--binary', 'Binary mode') do
99
+ options[:binary] = true
100
+ end
101
+ end.parse!
102
+ options
103
+ end
104
+
105
+ def get_stdin(paths, stream)
106
+ if paths.present?
107
+ if stream
108
+ ::File.open(paths[0])
109
+ else
110
+ paths.map { |path| ::File.read(path) }.join("\n")
111
+ end
112
+ else
113
+ if stream
114
+ $stdin
115
+ else
116
+ $stdin.read
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
data/lib/ru/stream.rb ADDED
@@ -0,0 +1,126 @@
1
+ class Enumerator::Lazy
2
+ def [](number_or_range, length=1)
3
+ if number_or_range.kind_of? Range
4
+ range = number_or_range
5
+ if range.begin < 0 || range.end < -1 || (range.exclude_end? && range.end == -1)
6
+ raise ArgumentError, "Stream doesn't support negative indexes, except x..-1"
7
+ end
8
+ if range.end == -1
9
+ drop(range.begin)
10
+ else
11
+ each_with_index.select { |_, index|
12
+ range.cover?(index)
13
+ }
14
+ end
15
+ else
16
+ number = number_or_range
17
+ if number >= 0
18
+ result = drop(number)
19
+ if length >= 0
20
+ result.take(length)
21
+ else
22
+ nil
23
+ end
24
+ else
25
+ raise ArgumentError, "Stream doesn't support negative indexes"
26
+ end
27
+ end
28
+ end
29
+
30
+ def length
31
+ count
32
+ end
33
+
34
+ def uniq
35
+ seen = Set.new
36
+ select { |item|
37
+ seen.add?(item)
38
+ }
39
+ end
40
+ end
41
+
42
+ module Ru
43
+ class Stream
44
+
45
+ attr :stream
46
+
47
+ # @param [Enumerator::Lazy] stream
48
+ def initialize(stream)
49
+ raise ArgumentError unless stream.kind_of? Enumerator::Lazy
50
+ @stream = stream
51
+ end
52
+
53
+ def each_line
54
+ Ru::Iterator.new(self)
55
+ end
56
+
57
+ def files
58
+ self.class.new @stream.map { |line| Ru::File.new(line) }
59
+ end
60
+
61
+ def format(format='l')
62
+ self.class.new @stream.map { |item| item.format(format) }
63
+ end
64
+
65
+ def grep(pattern)
66
+ if pattern.kind_of? String
67
+ pattern = Regexp.new(pattern)
68
+ end
69
+ self.class.new @stream.select { |item| item.to_s =~ pattern }
70
+ end
71
+
72
+ def map(method=nil, *args, &block)
73
+ if method.nil? && !block_given?
74
+ each_line
75
+ elsif method.nil?
76
+ self.class.new @stream.map(&block)
77
+ else
78
+ self.class.new @stream.map { |item| item.send(method, *args) }
79
+ end
80
+ end
81
+
82
+ def select(*args, &block)
83
+ self.class.new @stream.select(*args, &block)
84
+ end
85
+
86
+ def to_a
87
+ Ru::Array.new(@stream.to_a)
88
+ end
89
+
90
+ alias_method :to_ary, :to_a
91
+
92
+ def to_s
93
+ result = ''
94
+ result.concat @stream.next.to_s
95
+ loop do
96
+ item = @stream.next.to_s
97
+ result.concat "\n".freeze
98
+ result.concat item
99
+ end
100
+ result
101
+ rescue StopIteration
102
+ result
103
+ end
104
+
105
+ def to_self
106
+ self
107
+ end
108
+
109
+ def ==(other)
110
+ other.is_a?(self.class) && self.stream == other.stream
111
+ end
112
+
113
+ private
114
+
115
+ def method_missing(method, *args, &block)
116
+ result = @stream.send(method, *args, &block)
117
+ if result.kind_of? Enumerator::Lazy
118
+ self.class.new(result)
119
+ elsif result.kind_of? ::Array
120
+ Ru::Array.new(result)
121
+ else
122
+ result
123
+ end
124
+ end
125
+ end
126
+ end
data/lib/ru/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Ru
2
+ VERSION = '2.1.4'
3
+ end
data/lib/ru.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'active_support/all'
2
+
3
+ directory = File.dirname(File.absolute_path(__FILE__))
4
+ Dir.glob("#{directory}/ru/*.rb") { |file| require file }
5
+
6
+ module Ru
7
+ end
data/ru.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ require File.expand_path('../lib/ru/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.authors = ['Tom Benner', 'Alexander Pavlenko']
5
+ s.email = ['tombenner@gmail.com', 'alerticus@gmail.com']
6
+ s.description = s.summary = %q{Ruby in your shell!}
7
+ s.homepage = 'https://github.com/AlexanderPavlenko/ru'
8
+
9
+ s.files = `git ls-files`.split($\)
10
+ s.name = 'ru2'
11
+ s.executables = ['ru']
12
+ s.require_paths = ['lib']
13
+ s.version = Ru::VERSION
14
+ s.license = 'MIT'
15
+
16
+ s.add_dependency 'activesupport', '>= 3.2.0'
17
+
18
+ s.add_development_dependency 'appraisal', '~> 1.0'
19
+ s.add_development_dependency 'rspec', '~> 3.1'
20
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'misc examples' do
4
+ include FixturesHelper
5
+ include ProcessHelper
6
+
7
+ # http://stackoverflow.com/questions/450799/shell-command-to-sum-integers-one-per-line
8
+ context "summing integers" do
9
+ it "sums" do
10
+ lines = (1..10).to_a.map(&:to_s)
11
+ out = run('map(:to_i).sum', lines)
12
+ expect(out).to eq('55')
13
+ end
14
+ end
15
+
16
+ # http://stackoverflow.com/questions/6022384/bash-tool-to-get-nth-line-from-a-file
17
+ context "printing the nth line" do
18
+ it "prints" do
19
+ lines = (1..10).to_a.map(&:to_s)
20
+ out = run('[4]', lines)
21
+ expect(out).to eq('5')
22
+ end
23
+ end
24
+
25
+ # https://coderwall.com/p/ueazhw
26
+ context "sorting an Apache access log by response time" do
27
+ it "sorts" do
28
+ file = fixture_path('files', 'access.log')
29
+ out = run(['map { |line| [line[/(\d+)( ".+"){2}$/, 1].to_i, line] }.sort.reverse.map(:join, " ")', file])
30
+ expect(out).to eq(<<-EOF.strip
31
+ 584912 66.249.64.14 - - [18/Sep/2004:11:07:48 +1000] "GET /file.txt HTTP/1.0" 200 584912 "-" "Googlebot/2.1"
32
+ 6433 66.249.64.14 - - [18/Sep/2004:11:07:48 +1000] "GET / HTTP/1.0" 200 6433 "-" "Googlebot/2.1"
33
+ 6433 66.249.64.13 - - [18/Sep/2004:11:07:48 +1000] "GET / HTTP/1.0" 200 6433 "-" "Googlebot/2.1"
34
+ 468 66.249.64.14 - - [18/Sep/2004:11:07:48 +1000] "GET /robots.txt HTTP/1.0" 200 468 "-" "Googlebot/2.1"
35
+ 468 66.249.64.13 - - [18/Sep/2004:11:07:48 +1000] "GET /robots.txt HTTP/1.0" 200 468 "-" "Googlebot/2.1"
36
+ EOF
37
+ )
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+
3
+ # https://www.gnu.org/software/sed/manual/sed.html#Examples
4
+ describe 'sed examples' do
5
+ include FixturesHelper
6
+ include ProcessHelper
7
+
8
+ context "centering lines" do
9
+ it "centers" do
10
+ lines = %w{john paul george} + [' ringo ']
11
+ out = run('each_line.strip.center(10)', lines)
12
+ expect(out).to eq(" john \n paul \n george \n ringo ")
13
+ end
14
+ end
15
+
16
+ context "increment a number" do
17
+ it "increments" do
18
+ lines = ('5'..'10').to_a
19
+ out = run('(each_line.to_i+1)', lines)
20
+ expect(out).to eq(('6'..'11').to_a.join("\n"))
21
+ end
22
+ end
23
+
24
+ context "reverse characters of lines" do
25
+ it "reverses" do
26
+ lines = %w{john paul george ringo}
27
+ out = run('each_line.reverse', lines)
28
+ expect(out).to eq("nhoj\nluap\negroeg\nognir")
29
+ end
30
+ end
31
+
32
+ context "numbering lines" do
33
+ it "numbers" do
34
+ lines = %w{john paul george ringo}
35
+ out = run('map.with_index { |line, index| "#{(index+1).to_s.rjust(6)} #{line}" }', lines)
36
+ expect(out).to eq(" 1 john\n 2 paul\n 3 george\n 4 ringo")
37
+ end
38
+ end
39
+
40
+ context "counting lines" do
41
+ it "counts" do
42
+ lines = %w{john paul george ringo}
43
+ out = run('length', lines)
44
+ expect(out).to eq("4")
45
+ end
46
+ end
47
+
48
+ context "printing the first lines" do
49
+ it "prints" do
50
+ lines = %w{john paul george ringo}
51
+ out = run('[0,2]', lines)
52
+ expect(out).to eq("john\npaul")
53
+ end
54
+ end
55
+
56
+ context "printing the last lines" do
57
+ it "prints" do
58
+ lines = %w{john paul george ringo}
59
+ out = run('[2..-1]', lines)
60
+ expect(out).to eq("george\nringo")
61
+ end
62
+ end
63
+
64
+ context "make duplicate lines unique" do
65
+ it "dedupes" do
66
+ lines = %w{john john paul george george george ringo}
67
+ out = run('uniq', lines)
68
+ expect(out).to eq("john\npaul\ngeorge\nringo")
69
+ end
70
+ end
71
+
72
+ context "print duplicated lines of input" do
73
+ it "prints" do
74
+ lines = %w{john john paul george george george ringo}
75
+ out = run('select { |line| self.count(line) > 1 }', lines)
76
+ expect(out).to eq("john\njohn\ngeorge\ngeorge\ngeorge")
77
+ end
78
+ end
79
+
80
+ context "remove all duplicated lines" do
81
+ it "removes" do
82
+ lines = %w{john john paul george george george ringo}
83
+ out = run('select { |line| self.count(line) == 1 }', lines)
84
+ expect(out).to eq("paul\nringo")
85
+ end
86
+ end
87
+
88
+ context "squeezing blank lines" do
89
+ it "squeezes" do
90
+ lines = "john\n\npaul\ngeorge\n\n\nringo"
91
+ out = run('to_s.squeeze("\n")', lines)
92
+ expect(out).to eq("john\npaul\ngeorge\nringo")
93
+ end
94
+ end
95
+ end