bencodr 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.autotest ADDED
@@ -0,0 +1,34 @@
1
+ module Autotest::GnomeNotify
2
+
3
+ # Time notification will be displayed before disappearing automatically
4
+ EXPIRATION_IN_SECONDS = 2
5
+ ERROR_STOCK_ICON = "gtk-dialog-error"
6
+ SUCCESS_STOCK_ICON = "gtk-dialog-info"
7
+
8
+ # Convenience method to send an error notification message
9
+ #
10
+ # [stock_icon] Stock icon name of icon to display
11
+ # [title] Notification message title
12
+ # [message] Core message for the notification
13
+ def self.notify stock_icon, title, message
14
+ options = "-t #{EXPIRATION_IN_SECONDS * 1000} -i #{stock_icon}"
15
+ system "notify-send #{options} '#{title}' \"#{message}\""
16
+ end
17
+
18
+ Autotest.add_hook :red do |at|
19
+ example_text = ""
20
+ num_examples = 0
21
+ examples = at.files_to_test.each_pair do |key, values|
22
+ example_text += "- #{key}\n"
23
+ values.each do |value|
24
+ num_examples += 1
25
+ example_text += " * #{value}\n"
26
+ end
27
+ end
28
+ notify ERROR_STOCK_ICON, "Tests failed", "<b>#{num_examples} examples failed in #{at.files_to_test.size} files</b>\n#{example_text}"
29
+ end
30
+
31
+ Autotest.add_hook :green do |at|
32
+ notify SUCCESS_STOCK_ICON, "All tests passed, good job!", ""
33
+ end
34
+ end
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,25 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ .idea
23
+ doc
24
+ .yardoc
25
+ tmp/*
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 blatyo
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,147 @@
1
+ = BEncodr
2
+ * *Author* Allen Madsen (blatyo)
3
+ * *My* *Site* http://www.allenmadsen.com
4
+ * *Gem* http://gemcutter.org/gems/bencodr
5
+ * *Source* http://github.com/blatyo/bencodr
6
+ * *Documentation* http://blatyo.github.com/bencodr
7
+ * *Issue* *Tracker* http://github.com/blatyo/bencodr/issues
8
+
9
+ == Synopsis
10
+ This gem provides a way to encode and parse bencodings used by the Bit Torrent protocol.
11
+
12
+ == Installation
13
+
14
+ # install the gem
15
+ > gem install bencodr
16
+ Successfully installed bencodr
17
+ 1 gem installed
18
+ Installing ri documentation for bencodr...
19
+ Building YARD (yri) index for bencodr...
20
+ Installing RDoc documentation for bencodr
21
+
22
+ # somefile.rb
23
+ require 'bencodr'
24
+
25
+ == Examples
26
+ === String
27
+ BEncoded strings are length-prefixed base ten followed by a colon and the string.
28
+
29
+ # strings
30
+ "".bencode #=> "0:"
31
+ "string".bencode #=> "6:string"
32
+
33
+ # symbols
34
+ :symbol.bencode #=> "6:symbol"
35
+
36
+ # URIs
37
+ uri = URI.parse("http://github.com/blatyo/bencode")
38
+ uri.bencode #=> "32:http://github.com/blatyo/bencode"
39
+
40
+ === Integer
41
+ Bencoded integers are represented by an 'i' followed by the number in base 10 followed by an 'e'.
42
+
43
+ # integers
44
+ 1.bencode #=> "i1e"
45
+ -1.bencode #=> "i-1e"
46
+ 10_000_000_000.bencode #=> "i10000000000e"
47
+
48
+ # other numerics
49
+ 1.1.bencode #=> "i1e"
50
+ -1e10.bencode #=> "i-10000000000e"
51
+
52
+ # times
53
+ Time.at(4).bencode #=> "i4e"
54
+
55
+ === List
56
+ Bencoded lists are encoded as an 'l' followed by their elements (also bencoded) followed by an 'e'.
57
+
58
+ # arrays
59
+ [].bencode #=> "le"
60
+ [:e, "a", 1, Time.at(11)].bencode #=> "l1:e1:ai1ei11ee"
61
+
62
+ === Dictionary
63
+ Bencoded dictionaries are encoded as a 'd' followed by a list of alternating keys and their corresponding values
64
+ followed by an 'e'. Keys appear in sorted order (sorted as raw strings, not alphanumerics) and are always strings.
65
+
66
+ # hashes
67
+ {}.bencode #=> "de"
68
+ {"string" => "string"}.bencode #=> "d6:string6:stringe"
69
+ {:symbol => :symbol}.bencode #=> "d6:symbol6:symbole"
70
+ {1 => 1}.bencode #=> "d1:1i1ee"
71
+ {1.1 => 1.1}.bencode #=> "d3:1.1i1ee"
72
+ {{} => {}}.bencode #=> "d2:{}dee"
73
+
74
+ time = Time.utc(0)
75
+ {time => time}.bencode #=> "d23:2000-01-01 00:00:00 UTCi946684800ee"
76
+
77
+ array = (1..4).to_a
78
+ {array => array}.bencode #=> "d12:[1, 2, 3, 4]li1ei2ei3ei4eee"
79
+
80
+ # Note: keys are sorted as raw strings.
81
+ {:a => 1, "A" => 1, 1=> 1}.bencode #=> "d1:1i1e1:Ai1e1:ai1ee"
82
+
83
+ === Encoding and Decoding
84
+
85
+ # encoding is just like calling bencode on the object
86
+ BEncodr.encode("string") #=> "6:string"
87
+
88
+ # decoding takes a string and return either a String, Integer, Array, or Hash
89
+ BEncodr.decode("6:string") #=> "string"
90
+ BEncodr.decode("i1e") #=> 1
91
+ BEncodr.decode("le") #=> []
92
+ BEncodr.decode("de") #=> {}
93
+
94
+ # you can work directly with files too
95
+ BEncodr.encode_file("my_awesome.torrent", {:announce => "http://www.sometracker.com/announce:80"})
96
+ BEncodr.decode_file("my_awesome.torrent") #=> {:announce => "http://www.sometracker.com/announce:80"}
97
+
98
+ === Registering Types
99
+ When using bencodings it may be useful to translate your own objects into bencoded strings. You can do that with the
100
+ register methods on each BEncode type. All register does is define a bencode instance method for the class that
101
+ internally uses type conversion. That means if you want to specify a String type then your class must have a to_s or
102
+ to_str instance method. The same goes for all the other types.
103
+
104
+ Keep in mind when registering that if you register a class for two separate types it will only retain the bencode method
105
+ of the last type registered for.
106
+
107
+ # register string type
108
+ BEncodr::String.register Range
109
+ (1..2).bencode #=> "4:1..2"
110
+
111
+ # register integer type
112
+ BEncodr::Integer.register NilClass
113
+ nil.bencode #=> "i0e"
114
+
115
+ # register list type
116
+ BEncodr::List.register Range
117
+ (1..2).bencode #=> "li1ei2ee"
118
+
119
+ #register dictionary type
120
+ MyClass = Class.new do
121
+ def to_h
122
+ {:a => "a", :b => "b"}
123
+ end
124
+ end
125
+ BEncodr::Dictionary.register MyClass
126
+ MyClass.new.bencode #=> "d1:a1:a1:b1:be"
127
+
128
+ == Note on Reporting Issues
129
+
130
+ * Try to make a failing test case
131
+ * Tell me which version of ruby you're using
132
+ * Tell me which OS you are using
133
+ * Provide me with any extra files if necessary
134
+
135
+ == Note on Patches/Pull Requests
136
+
137
+ * Fork the project.
138
+ * Make your feature addition or bug fix.
139
+ * Add tests for it. This is important so I don't break it in a
140
+ future version unintentionally.
141
+ * Commit, do not mess with rakefile, version, or history.
142
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
143
+ * Send me a pull request. Bonus points for topic branches.
144
+
145
+ == Copyright
146
+
147
+ Copyright (c) 2010 blatyo. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "bencodr"
8
+ gem.summary = "This gem provides a way to encode and parse bencodings used by the Bit Torrent protocol."
9
+ gem.description = "This gem provides a way to encode and parse bencodings used by the Bit Torrent protocol."
10
+ gem.email = "blatyo@gmail.com"
11
+ gem.homepage = "http://github.com/blatyo/bencodr"
12
+ gem.authors = ["Allen Madsen"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ gem.add_development_dependency "yard", ">= 0"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'spec/rake/spectask'
23
+ Spec::Rake::SpecTask.new(:spec) do |spec|
24
+ spec.libs << 'lib' << 'spec'
25
+ spec.spec_files = FileList['spec/**/*_spec.rb']
26
+ end
27
+
28
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.pattern = 'spec/**/*_spec.rb'
31
+ spec.rcov = true
32
+ end
33
+
34
+ task :spec => :check_dependencies
35
+
36
+ task :default => :spec
37
+
38
+ begin
39
+ require 'yard'
40
+ YARD::Rake::YardocTask.new
41
+ rescue LoadError
42
+ task :yardoc do
43
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
44
+ end
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.1
@@ -0,0 +1,3 @@
1
+ Autotest.add_discovery do
2
+ "rspec"
3
+ end
@@ -0,0 +1,78 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{bencode_blatyo}
8
+ s.version = "1.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Allen Madsen"]
12
+ s.date = %q{2010-02-02}
13
+ s.description = %q{This gem has been renamed to bencodr. Use that one instead.}
14
+ s.email = %q{blatyo@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".autotest",
21
+ ".document",
22
+ ".gitignore",
23
+ "LICENSE",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "autotest/discover.rb",
28
+ "bencode_blatyo.gemspec",
29
+ "lib/bencodr.rb",
30
+ "lib/bencodr/dictionary.rb",
31
+ "lib/bencodr/integer.rb",
32
+ "lib/bencodr/list.rb",
33
+ "lib/bencodr/parser.rb",
34
+ "lib/bencodr/string.rb",
35
+ "spec/bencodr/dictionary_spec.rb",
36
+ "spec/bencodr/integer_spec.rb",
37
+ "spec/bencodr/list_spec.rb",
38
+ "spec/bencodr/parser_spec.rb",
39
+ "spec/bencodr/string_spec.rb",
40
+ "spec/bencode_spec.rb",
41
+ "spec/samples/bencodr.rb.torrent",
42
+ "spec/samples/mini.bencodr",
43
+ "spec/samples/python.torrent",
44
+ "spec/spec.opts",
45
+ "spec/spec_helper.rb"
46
+ ]
47
+ s.homepage = %q{http://github.com/blatyo/bencodr}
48
+ s.rdoc_options = ["--charset=UTF-8"]
49
+ s.require_paths = ["lib"]
50
+ s.rubygems_version = %q{1.3.5}
51
+ s.summary = %q{This gem has been renamed to bencodr. Use that one instead.}
52
+ s.test_files = [
53
+ "spec/bencodr/dictionary_spec.rb",
54
+ "spec/bencodr/integer_spec.rb",
55
+ "spec/bencodr/list_spec.rb",
56
+ "spec/bencodr/parser_spec.rb",
57
+ "spec/bencodr/string_spec.rb",
58
+ "spec/bencode_spec.rb",
59
+ "spec/spec_helper.rb"
60
+ ]
61
+
62
+ if s.respond_to? :specification_version then
63
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
64
+ s.specification_version = 3
65
+
66
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
67
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
68
+ s.add_development_dependency(%q<yard>, [">= 0"])
69
+ else
70
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
71
+ s.add_dependency(%q<yard>, [">= 0"])
72
+ end
73
+ else
74
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
75
+ s.add_dependency(%q<yard>, [">= 0"])
76
+ end
77
+ end
78
+
@@ -0,0 +1,57 @@
1
+ # encoding: UTF-8
2
+
3
+ module BEncodr
4
+ module Dictionary
5
+ module Generic
6
+ module InstanceMethods
7
+ # Encodes an array into a bencoded dictionary. Bencoded dictionaries are encoded as a 'd' followed by a list of
8
+ # alternating keys and their corresponding values followed by an 'e'. Keys appear in sorted order (sorted as raw
9
+ # strings, not alphanumerics).
10
+ #
11
+ # {:cow => "moo", :seven => 7}.bencodr #=> "d3:cow3:moo5:seveni7ee"
12
+ #
13
+ # @return [::String] the bencoded dictionary
14
+ def bencode
15
+ (respond_to?(:to_h) ? to_h : to_hash).bencode
16
+ end
17
+ end
18
+ end
19
+
20
+ # Registers a class as an object that can be converted into a bencoded dictionary. Class must have instance method
21
+ # to_h or to_hash.
22
+ #
23
+ # class MyClass
24
+ # def to_h
25
+ # {:a => :a, :b => 1}
26
+ # end
27
+ # end
28
+ #
29
+ # BEncodr::String.register MyClass
30
+ # my_class = MyClass.new
31
+ # my_class.bencodr #=> "d1:a1:a1:bi1ee"
32
+ #
33
+ # @param [Class#to_h, Class#to_hash] type the class to add the bencodr instance method to
34
+ def self.register(type)
35
+ type.send :include, Generic::InstanceMethods
36
+ end
37
+
38
+ module Hash
39
+ module InstanceMethods
40
+ # Encodes an array into a bencoded dictionary. Bencoded dictionaries are encoded as a 'd' followed by a list of
41
+ # alternating keys and their corresponding values followed by an 'e'. Keys appear in sorted order (sorted as raw
42
+ # strings, not alphanumerics).
43
+ #
44
+ # {:cow => "moo", :seven => 7}.bencodr #=> "d3:cow3:moo5:seveni7ee"
45
+ #
46
+ # @return [::String] the bencoded dictionary
47
+ def bencode
48
+ keys.sort{|a, b| a.to_s <=> b.to_s}.collect do |key|
49
+ key.to_s.bencode + self[key].bencode
50
+ end.unshift(:d).push(:e).join
51
+ end
52
+ end
53
+
54
+ ::Hash.send :include, InstanceMethods
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,57 @@
1
+ # encoding: UTF-8
2
+
3
+ module BEncodr
4
+ module Integer
5
+ module Generic
6
+ module InstanceMethods
7
+ # Encodes object into a bencoded integer. BEncoded strings are length-prefixed base ten followed by a colon and
8
+ # the string. Object must implement to_i or to_int.
9
+ #
10
+ # 1.bencodr #=> "i1e"
11
+ #
12
+ # @return [::String] the bencoded integer
13
+ def bencode
14
+ (respond_to?(:to_i) ? to_i : to_int).bencode
15
+ end
16
+ end
17
+ end
18
+
19
+ # Registers a class as an object that can be converted into a bencoded integer. Class must have instance method to_i
20
+ # or to_int.
21
+ #
22
+ # class MyClass
23
+ # def to_i
24
+ # 1
25
+ # end
26
+ # end
27
+ #
28
+ # BEncodr::Integer.register MyClass
29
+ # my_class = MyClass.new
30
+ # my_class.bencodr #=> "i1e"
31
+ #
32
+ # @param [Class#to_i, Class#to_int] type the class to add the bencodr instance method to
33
+ def self.register(type)
34
+ type.send :include, Generic::InstanceMethods
35
+ end
36
+
37
+ register Numeric
38
+ register Time
39
+
40
+ module Integer
41
+ module InstanceMethods
42
+ # Encodes an integer into a bencoded integer. Bencoded integers are represented by an 'i' followed by the number
43
+ # in base 10 followed by an 'e'.
44
+ #
45
+ # 3.bencodr #=> "i3e"
46
+ # -3.bencodr #=> "i-3e"
47
+ #
48
+ # @return [::String] the bencoded integer
49
+ def bencode
50
+ [:i, self, :e].join
51
+ end
52
+ end
53
+
54
+ ::Integer.send :include, InstanceMethods
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,55 @@
1
+ # encoding: UTF-8
2
+
3
+ module BEncodr
4
+ module List
5
+ module Generic
6
+ module InstanceMethods
7
+ # Encodes object into a bencoded list. BEncoded strings are length-prefixed base ten followed by a colon and
8
+ # the string. Object must implement to_a or to_ary.
9
+ #
10
+ # [].bencodr #=> "le"
11
+ #
12
+ # @return [::String] the bencoded list
13
+ def bencode
14
+ (respond_to?(:to_ary) ? to_ary : to_a).bencode
15
+ end
16
+ end
17
+ end
18
+
19
+ # Registers a class as an object that can be converted into a bencoded list. Class must have instance method to_a
20
+ # or to_ary.
21
+ #
22
+ # class MyClass
23
+ # def to_a
24
+ # [1, :cat]
25
+ # end
26
+ # end
27
+ #
28
+ # BEncodr::Integer.register MyClass
29
+ # my_class = MyClass.new
30
+ # my_class.bencodr #=> "li1e3:cate"
31
+ #
32
+ # @param [Class#to_a, Class#to_ary] type the class to add the bencodr instance method to
33
+ def self.register(type)
34
+ type.send :include, Generic::InstanceMethods
35
+ end
36
+
37
+ module Array
38
+ module InstanceMethods
39
+ # Encodes an array into a bencoded list. Bencoded lists are encoded as an 'l' followed by their elements (also
40
+ # bencoded) followed by an 'e'.
41
+ #
42
+ # [:eggs, "ham", 3, 4.1].bencodr #=> "l4:eggs3:hami3ei4ee"
43
+ #
44
+ # @return [::String] the bencoded list
45
+ def bencode
46
+ collect do |element|
47
+ element.bencode
48
+ end.unshift(:l).push(:e).join
49
+ end
50
+ end
51
+
52
+ ::Array.send :include, InstanceMethods
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,113 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'strscan'
4
+
5
+ module BEncodr
6
+ module Parser
7
+ class << self
8
+ # This method parases a bencoded object.
9
+ #
10
+ # scanner = StringScanner.new("6:string")
11
+ # BEncodr::Parser.parse_object(scanner) #=> "string"
12
+ #
13
+ # @param [StringScanner] scanner the scanner of a bencoded object
14
+ # @return [::String, ::Integer, ::Hash, ::Array, nil] an object if type is recognized or nil
15
+ def parse_object(scanner)
16
+ case scanner.peek(1)[0]
17
+ when ?0..?9
18
+ parse_string(scanner)
19
+ when ?i
20
+ parse_integer(scanner)
21
+ when ?l
22
+ parse_list(scanner)
23
+ when ?d
24
+ parse_dictionary(scanner)
25
+ else
26
+ nil
27
+ end
28
+ end
29
+
30
+ # This method parases a bencoded string.
31
+ #
32
+ # scanner = StringScanner.new("6:string")
33
+ # BEncodr::Parser.parse_string(scanner) #=> "string"
34
+ #
35
+ # @param [StringScanner] scanner the scanner of a bencoded string
36
+ # @return [::String] the parsed string
37
+ def parse_string(scanner)
38
+ length = scanner.scan(/[1-9][0-9]*|0/) or raise BEncodeError, "Invalid string: length invalid. #{scanner.pos}"
39
+ scanner.scan(/:/) or raise BEncodeError, "Invalid string: missing colon(:). #{scanner.pos}"
40
+ scanner.scan(/.{#{length}}/m) or raise BEncodeError, "Invalid string: length too long(#{length}) #{scanner.pos}."
41
+ end
42
+
43
+ # This method parases a bencoded integer.
44
+ #
45
+ # scanner = StringScanner.new("i1e")
46
+ # BEncodr::Parser.parse_integer(scanner) #=> 1
47
+ #
48
+ # @param [StringScanner] scanner the scanner of a bencoded integer
49
+ # @return [::Integer] the parsed integer
50
+ def parse_integer(scanner)
51
+ scanner.scan(/i/) or raise BEncodeError, "Invalid integer: missing opening i. #{scanner.pos}"
52
+ integer = scanner.scan(/-?[1-9][0-9]*|0/) or raise BEncodeError, "Invalid integer: valid integer not found. #{scanner.pos}"
53
+ scanner.scan(/e/) or raise BEncodeError, "Invalid integer: missing closing e. #{scanner.pos}"
54
+ integer.to_i
55
+ end
56
+
57
+ # This method parases a bencoded list.
58
+ #
59
+ # scanner = StringScanner.new("le")
60
+ # BEncodr::Parser.parse_list(scanner) #=> []
61
+ #
62
+ # @param [StringScanner] scanner the scanner of a bencoded list
63
+ # @return [::Array] the parsed array
64
+ def parse_list(scanner)
65
+ list = []
66
+
67
+ scanner.scan(/l/) or raise BEncodeError, "Invalid list: missing opening l. #{scanner.pos}"
68
+ while true
69
+ object = parse_object(scanner)
70
+ break unless object
71
+ list << object
72
+ end
73
+ scanner.scan(/e/) or raise BEncodeError, "Invalid list: missing closing e. #{scanner.pos}"
74
+
75
+ list
76
+ end
77
+
78
+ # This method parases a bencoded dictionary.
79
+ #
80
+ # scanner = StringScanner.new("de")
81
+ # BEncodr::Parser.parse_dictionary(scanner) #=> {}
82
+ #
83
+ # @param [StringScanner] scanner the scanner of a bencoded dictionary
84
+ # @return [::Hash] the parsed hash
85
+ def parse_dictionary(scanner)
86
+ dictionary = {}
87
+
88
+ scanner.scan(/d/) or raise BEncodeError, "Invalid dictionary: missing opening d. #{scanner.pos}"
89
+ while true
90
+ key_value = parse_key_value(scanner)
91
+ break unless key_value
92
+ dictionary.store(*key_value)
93
+ end
94
+ scanner.scan(/e/) or raise BEncodeError, "Invalid dictionary: missing closing e. #{scanner.pos}"
95
+
96
+ dictionary
97
+ end
98
+
99
+
100
+ def parse_key_value(scanner) # :nodoc:
101
+ key = parse_object(scanner)
102
+ return key unless key
103
+ raise BEncodeError, "Invalid dictionary: key is not a string. #{scanner.pos}" unless key.is_a?(::String)
104
+
105
+ value = parse_object(scanner)
106
+ raise BEncodeError, "Invalid dictionary: missing value for key (#{key}). #{scanner.pos}" unless value
107
+
108
+ [key, value]
109
+ end
110
+ private :parse_key_value
111
+ end
112
+ end
113
+ end