lib/bencode.rb in bencode-0.4.0 vs lib/bencode.rb in bencode-0.5.0

- old
+ new

@@ -3,12 +3,12 @@ # Bencode is a Ruby implementation of the Bencode data serialization # format used in the BitTorrent protocol. # # == Synopsis # -# Bencoding (pronounced <i>bee-encode</i>) is a simple protocol, consisting of -# only 4 value types. +# Bencoding (pronounced <i>bee-encode</i>) is a simple protocol, +# consisting of only 4 value types. # # === Integers # # An integer is encoded as an _i_ followed by the numeral itself, followed # by an _e_. Leading zeros are not allowed. Negative values are prefixed @@ -19,12 +19,12 @@ # 0.bencode #=> "i0e" # # === Strings # # Strings are sequences of zero or more bytes. They are encoded as -# <i>&lt;length&gt;:&lt;contents&gt;</i>, where _length_ is the length of _contents_. _length_ -# must be non-negative. +# <i>&lt;length&gt;:&lt;contents&gt;</i>, where _length_ is the +# length of _contents_. _length_ must be non-negative. # # "".bencode #=> "0:" # "foo".bencode #=> "3:foo" # # === Lists @@ -34,13 +34,13 @@ # # [1, 2, 3].bencode #=> "li1ei2ei3ee" # # === Dictionaries # -# Dictionaries are encoded as _d_ followed by a sequence of key-value pairs, followed by _e_. -# Each value must be immediately preceded by a key. Keys must be strings, and must appear in -# lexicographical order. +# Dictionaries are encoded as _d_ followed by a sequence of key-value +# pairs, followed by _e_. Each value must be immediately preceded by +# a key. Keys must be strings, and must appear in lexicographical order. # # {"foo" => 3, "bar" => 1, "baz" => 2}.bencode # #=> "d3:bari1e3:bazi2e3:fooi3ee" # # @@ -60,181 +60,19 @@ # terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later # version. # # Bencode is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR -# A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. -class Object - # - # Raises an exception. Subclasses of Object must themselves - # define meaningful #bencode methods. - def bencode - raise BencodeError, "object cannot be bencoded" - end -end -class Integer - # - # Bencodes the Integer object. Bencoded integers are represented - # as +ixe+, where +x+ is the integer with an optional - # hyphen prepended, indicating negativity. - # - # 42.bencode #=> "i42e" - # -7.bencode #=> "i-7e" - def bencode - "i#{self}e" - end -end +prefix = File.dirname(__FILE__) -class String - # - # Bencodes the String object. Bencoded strings are represented - # as <code>x</code>:<code>y</code>, where +y+ is the string and +x+ is the length of the - # string. - # - # "foo".bencode #=> "3:foo" - # "".bencode #=> "0:" - # - def bencode - "#{length}:#{self}" - end - - # - # Bdecodes the String object and returns the data serialized - # through bencoding. - # - # "li1ei2ei3ee".bdecode #=> [1, 2, 3] - # - def bdecode - Bencode.load(self) - end - - # - # Tests whether the String object is a valid bencoded string. - def bencoded? - bdecode - rescue BdecodeError - false - else - true - end +%w[object integer string array hash].each do |type| + require prefix + '/bencode/encode/' + type end -class Array - # - # Bencodes the Array object. Bencoded arrays are represented as - # +lxe+, where +x+ is zero or more bencoded objects. - # - # [1, "foo"].bencode #=> "li1e3:fooe" - # - def bencode - "l#{map{|obj| obj.bencode }.join('')}e" - rescue BencodeError - raise BencodeError, "list items must be bencodable" - end -end - -class Hash - # - # Bencodes the Hash object. Bencoded hashes are represented as - # +dxe+, where +x+ is zero or a power of two bencoded objects. - # each key is immediately followed by its associated value. - # All keys must be strings. The keys of the bencoded hash will - # be in lexicographical order. - def bencode - pairs = sort.map{|key, val| [key.to_str.bencode, val.bencode] } - "d#{pairs.join('')}e" - rescue NoMethodError => error - if error.name == :to_str - raise BencodeError, "dictionary keys must be strings" - else - raise - end - end -end - -class IO - def self.bdecode(filename) - open(filename, 'r').bdecode - end - - def self.bencode(filename) - open(filename, 'r').bencode - end - - def bdecode - read.chomp.bdecode - end - - def bencode - read.chomp.bencode - end -end - -class BencodeError < StandardError; end - -class BdecodeError < StandardError; end - -module Bencode - class << self - # Bencodes +obj+ - def dump(obj) - obj.bencode - end - - # Bdecodes +str+ - def load(str) - require 'strscan' - - scanner = StringScanner.new(str) - obj = parse(scanner) - raise BdecodeError unless scanner.eos? - return obj - end - - # Bdecodes the file located at +path+ - def load_file(path) - load(File.open(path).read) - end - - def parse(scanner) # :nodoc: - case token = scanner.scan(/[ild]|\d+:|\s/) - when nil - raise BdecodeError - when "i" - number = scanner.scan(/0|(?:-?[1-9][0-9]*)/) - raise BdecodeError unless number and scanner.scan(/e/) - return number.to_i - when "l" - ary = [] - until scanner.peek(1) == "e" - ary.push(parse(scanner)) - end - scanner.pos += 1 - return ary - when "d" - hsh = {} - until scanner.peek(1) == "e" - key, value = parse(scanner), parse(scanner) - raise BdecodeError, "key must be a string" unless key.is_a? String - hsh.store(key, value) - end - scanner.pos += 1 - return hsh - when /\d+:/ - length = token.chop.to_i - str = scanner.peek(length) - scanner.pos += length - return str - when /\s/ - nil - else - raise BdecodeError - end - end - - private :parse - end +%w[decode io encode_error decode_error].each do |file| + require prefix + '/bencode/' + file end