require 'stringio' class Vooly class Pull def initialize(io, strip_whitespace=false) if io.respond_to? :to_io @io = io.to_io elsif io.respond_to? :to_str @io = StringIO.new io.to_str else raise TypeError, "Can't pull from #{io.class}, only from String and IO" end @strip = strip_whitespace @next_state = :text @state = nil @text = nil @depths = [2] end attr_reader :state, :text def strip! case @strip when true # Strip everything @text.strip! when false # Strip whitespace if only whitespace @text.strip! unless @text =~ /\S/ when nil # Strip nothing # end end def next # puts @io.string # puts " " * @io.pos + "^" open = close = 0 if @next_state @state = @next_state @state = nil if @state == 0 @text = "" end while c = @io.getc @next_state = nil if @io.eof? raise ParseError, "Invalid nesting (underflow)" if @depths.size < 1 case @state when :text # Ordinary text if c == ?< open += 1 close = 0 elsif c == ?> close += 1 open = 0 end if c != ?< && open >= @depths.last # Open is greedy @io.ungetc c @depths << open @next_state = :open @text = @text[0...-open] strip! if @text.empty? @state = @next_state @next_state = nil else return true end elsif close == @depths.last # Close is not @depths.pop @next_state = :close @text = @text[0..-close] @text.sub! /\s$/, '' if @strip == false strip! if @text.empty? @state = @next_state @next_state = nil else return true end else @text << c open = close = 0 if c != ?< and c != ?> end when :open # Just after <<... if c.chr =~ /\w/ @text << c else @io.ungetc c unless @strip == false && c.chr =~ /\s/ @text = nil if @text.empty? @next_state = :text return true end when :close # Just after ...>> @io.ungetc c if @strip != true @text = nil @next_state = :text return true when nil # At EOF @text = nil return false else raise "Unhandled state #@state" end end if @state == :text strip! @state = @text = nil if @text.empty? @next_state = 0 return true else raise ParseError, "Invalid nesting (overflow)" if @depths.size != 1 @text = nil return false end end end end