The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
require 'strscan'
require 'nack/error'

module Nack
  # http://cr.yp.to/proto/netstrings.txt
  module NetString
    def ns_length(str)
      s = StringScanner.new(str)

      if slen = s.scan(/\d+/)
        if slen =~ /^0\d+$/
          raise Error, "Invalid netstring with leading 0"
        elsif slen.length > 9
          raise Error, "netstring is too large"
        end

        len = Integer(slen)

        if s.scan(/:/)
          len
        elsif s.eos?
          raise Error, "Invalid netstring terminated after length"
        else
          raise Error, "Unexpected character '#{s.peek(1)}' found at offset #{s.pos}"
        end
      elsif s.peek(1) == ':'
        raise Error, "Invalid netstring with leading ':'"
      else
        raise Error, "Unexpected character '#{s.peek(1)}' found at offset #{s.pos}"
      end
    end
    module_function :ns_length
    protected :ns_length

    def read(io)
      buf = ""

      until io.eof?
        buf << io.readline(":")
        len = ns_length(buf)

        io.read(len, buf)

        if io.eof?
          return
        elsif (c = io.getc) && c != ?,
          raise Error, "Invalid netstring length, expected to be #{len}"
        else
          yield buf
        end

        buf = ""
      end

      nil
    end
    module_function :read

    def encode(str)
      io = StringIO.new
      write(io, str)
      io.string
    end
    module_function :encode

    def write(io, str)
      io << "#{str.bytesize}:" << str << ","
      io.flush
    end
    module_function :write
  end
end