I came across the question about creating memory-efficient Ruby pipe class with lazy evaluation. Some code is given to effectively create a pipeline of commands using lazy enumerators. I have been messing round with it and have implemented a tee
command shown below. I feel like there is a better way to do it though.
class Pipe
class Tee
def initialize(pipe)
@pipe = pipe
@buffer = []
@pipe.source = self.to_enum
end
def <<(item)
@buffer << item
# puts "Added '#{item}' to buffer: #{@buffer.inspect}"
end
def to_enum
Enumerator.new do |yielder|
item = @buffer.shift
yielder << item if item
end.lazy
end
end
attr_writer :source
def initialize
@commands = []
@source = nil
end
def add(command, opts = {})
@commands << [command, opts]
self
end
def run
enum = @source
@commands.each do |command, options|
enum = method(command).call(enum, options)
end
enum.each {}
enum
end
def cat(enum, options)
Enumerator.new do |yielder|
enum.map { |line| yielder << line } if enum
options[:input].tap do |ios|
ios.each { |line| yielder << line }
end
end.lazy
end
def out(enum, options)
Enumerator.new do |yielder|
enum.each do |line|
puts line
yielder << line
end
end.lazy
end
def cut(enum, options)
Enumerator.new do |yielder|
enum.each do |line|
fields = line.chomp.split(%r{#{options[:delimiter]}})
yielder << fields[options[:field]]
end
end.lazy
end
def upcase(enum, options)
Enumerator.new do |yielder|
enum.each do |line|
yielder << line.upcase
end
end
end
def tee(enum, options)
teed = Tee.new(options.fetch(:other))
Enumerator.new do |yielder|
enum.each do |line|
yielder << line
teed << line
end
end
end
def grep(enum, options)
Enumerator.new do |yielder|
enum.each do |line|
yielder << line if line.match(options[:pattern])
end
end.lazy
end
end
another_pipe = Pipe.new
another_pipe.add(:grep, pattern: /testing/i)
another_pipe.add(:out)
pipe = Pipe.new
pipe.add(:cat, :input => StringIO.new("testing\na new\nline"))
# pipe.add(:grep, pattern: /line/)
pipe.add(:tee, :other => another_pipe)
pipe.add(:upcase)
pipe.add(:out)
pipe.run
puts "================================================="
another_pipe.run
Here's the output:
TESTING A NEW LINE ================================================= testing
Does anyone see a smarter way of doing this?
Enumerator#lazy
in thepipe
ortee
methods when you do in all of the others? – Jordan Oct 15 '14 at 20:08