bencodr 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|