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 +34 -0
- data/.document +5 -0
- data/.gitignore +25 -0
- data/LICENSE +20 -0
- data/README.rdoc +147 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/autotest/discover.rb +3 -0
- data/bencode_blatyo.gemspec +78 -0
- data/lib/bencodr/dictionary.rb +57 -0
- data/lib/bencodr/integer.rb +57 -0
- data/lib/bencodr/list.rb +55 -0
- data/lib/bencodr/parser.rb +113 -0
- data/lib/bencodr/string.rb +58 -0
- data/lib/bencodr.rb +56 -0
- data/spec/bencode_spec.rb +86 -0
- data/spec/bencodr/dictionary_spec.rb +79 -0
- data/spec/bencodr/integer_spec.rb +72 -0
- data/spec/bencodr/list_spec.rb +36 -0
- data/spec/bencodr/parser_spec.rb +176 -0
- data/spec/bencodr/string_spec.rb +73 -0
- data/spec/samples/bencode.rb.torrent +1 -0
- data/spec/samples/mini.bencode +1 -0
- data/spec/samples/python.torrent +0 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +11 -0
- metadata +106 -0
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
data/.gitignore
ADDED
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,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
|
data/lib/bencodr/list.rb
ADDED
@@ -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
|