module Bencoding class ParseError < Exception ; end class InvalidInput < Exception ; end module Parser DICTIONARY_TOKEN = 100 # d # :nodoc: LIST_TOKEN = 108 # l # :nodoc: INTEGER_TOKEN = 105 # i # :nodoc: TERMINATOR_TOKEN = 101 # e # :nodoc: SEPERATOR_TOKEN = 58 # : # :nodoc: ZERO_TOKEN = 48 # 0 # :nodoc: NINE_TOKEN = 57 # 9 # :nodoc: MINUS_TOKEN = 45 # - # :nodoc: def load(io) any_io(io) do |io| parse_anytype(io) end end def any_io(io) close_io = false if io.is_a? String if io =~ %r{^(http(s?)|file)://} io = open(io) close_io = true elsif File.exist?(io) io = File.open(io) close_io = true else io = StringIO.new(io) end end raise InvalidInput unless io.respond_to?(:getc) raise InvalidInput unless io.respond_to?(:read) raise InvalidInput unless io.respond_to?(:close) or not close_io return_value = yield(io) io.close if close_io return return_value end def parse_anytype(io, typechar=nil) # :nodoc: typechar ||= io.getc case typechar when DICTIONARY_TOKEN then parse_dictionary(io) when LIST_TOKEN then parse_list(io) when INTEGER_TOKEN then parse_integer(io) when (ZERO_TOKEN..NINE_TOKEN) parse_string(io, typechar) else raise ParseError end end def parse_dictionary(io) # :nodoc: dictionary = ::Hash.new until (c = io.getc) == TERMINATOR_TOKEN raise ParseError if c.nil? key = parse_string(io, c) val = parse_anytype(io) dictionary[key] = val end dictionary end def parse_list(io) # :nodoc: list = ::Array.new until (c = io.getc) == TERMINATOR_TOKEN raise ParseError if c.nil? val = parse_anytype(io, c) list << val end list end def parse_integer(io, terminator=TERMINATOR_TOKEN, first_char=nil) # :nodoc: integer_string = "" integer_string << first_char unless first_char.nil? until (c = io.getc) == terminator raise ParseError if c.nil? raise ParseError unless (ZERO_TOKEN..NINE_TOKEN).member?(c) or MINUS_TOKEN == c integer_string << c end integer_string.to_i end def parse_string(io, first_char) # :nodoc: length = parse_integer(io, SEPERATOR_TOKEN, first_char) raise ParseError if length < 0 string = io.read(length) raise ParseError if length != string.length string end extend self end end