ll-mktorrent 1.8.0
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.
- checksums.yaml +7 -0
- data/lib/mktorrent.rb +266 -0
- data/test/mktorrent_test.rb +95 -0
- data/test/test_helper.rb +22 -0
- metadata +133 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: b226f9099a7a0c1a43add68ff6e730b3229c1fb1
|
|
4
|
+
data.tar.gz: e3c5cb0daf276d661b6f57a044dd31c6a557268a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 105583b686255b9f09ea34c62f88b2be042fa61da03ff62bce5622d5a2df2fcdbd2007909af8b78763d482dcb93227fde2d9ed628d96fbfe0d5f3f869fd2c67d
|
|
7
|
+
data.tar.gz: c75b506d907354b9123180150d41b01ad36f268cecbd0443a9ba2695b8a6e8101d060b5678b15a84906865c3b20fd6c2732ce9d1b2f5cc98c37b56ebb48650f4
|
data/lib/mktorrent.rb
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
require 'bencode'
|
|
2
|
+
require 'digest/sha1'
|
|
3
|
+
require 'date'
|
|
4
|
+
require 'uri'
|
|
5
|
+
|
|
6
|
+
# Sample usage
|
|
7
|
+
#t = Torrent.new("http://your.tracker.com")
|
|
8
|
+
#t.add_file("path/to/file.foo")
|
|
9
|
+
#t.write_torrent("~/Downloads/mytorrent.torrent")
|
|
10
|
+
|
|
11
|
+
class Torrent
|
|
12
|
+
include Comparable
|
|
13
|
+
|
|
14
|
+
attr_reader :torrent_file, :infohash
|
|
15
|
+
attr_accessor :info, :filehashes, :piecelength, :files, :defaultdir, :tracker, :size, :privacy, :webseed, :tracker_list, :pieces_from_file
|
|
16
|
+
attr_writer :creation_date, :from_file
|
|
17
|
+
|
|
18
|
+
# optionally initialize filename
|
|
19
|
+
def initialize(tracker, from_file = false)
|
|
20
|
+
@tracker = tracker
|
|
21
|
+
@piecelength = 512 * 1024 # 512 KB
|
|
22
|
+
@files = []
|
|
23
|
+
@filehashes = []
|
|
24
|
+
@size = 0
|
|
25
|
+
@tracker_list = [ [@tracker] ]
|
|
26
|
+
@defaultdir = "torrent"
|
|
27
|
+
@privacy = 0
|
|
28
|
+
@webseed = ""
|
|
29
|
+
@dirbase = ""
|
|
30
|
+
@from_file = from_file
|
|
31
|
+
build_the_torrent
|
|
32
|
+
yield(self) if block_given?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def all_files
|
|
36
|
+
if @files.any?
|
|
37
|
+
@files.collect { |file| file[:path] }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def from_file?
|
|
42
|
+
!!@from_file
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def count
|
|
46
|
+
@files.count
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def creation_date
|
|
50
|
+
@creation_date ||= DateTime.now.strftime("%s")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def path_for_reading_pieces(f)
|
|
54
|
+
if @dirbase.empty? # it's a single file torrent
|
|
55
|
+
f = File.join(File.join(f))
|
|
56
|
+
end
|
|
57
|
+
f = File.join(@dirbase, f) unless @dirbase.empty?
|
|
58
|
+
f
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def read_pieces(files, length)
|
|
62
|
+
buffer = ""
|
|
63
|
+
files.each do |file|
|
|
64
|
+
f = path_for_reading_pieces(file)
|
|
65
|
+
next if File.directory?(f)
|
|
66
|
+
File.open(f) do |fh|
|
|
67
|
+
begin
|
|
68
|
+
read = fh.read(length - buffer.length)
|
|
69
|
+
|
|
70
|
+
# Make sure file not empty
|
|
71
|
+
unless read.nil?
|
|
72
|
+
if (buffer.length + read.length) == length
|
|
73
|
+
yield(buffer + read)
|
|
74
|
+
buffer = ""
|
|
75
|
+
else
|
|
76
|
+
buffer += read
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end until fh.eof?
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
yield buffer
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def build
|
|
87
|
+
@info = {
|
|
88
|
+
:announce => @tracker,
|
|
89
|
+
:'announce-list' => @tracker_list,
|
|
90
|
+
:'creation date' => creation_date,
|
|
91
|
+
:info => {
|
|
92
|
+
:name => @defaultdir,
|
|
93
|
+
:'piece length' => @piecelength,
|
|
94
|
+
:files => @files,
|
|
95
|
+
:private => @privacy,
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
@info[:info][:pieces] = ""
|
|
99
|
+
@info.merge!({ :'url-list' => @webseed }) if @webseed
|
|
100
|
+
if @files.count > 0
|
|
101
|
+
if from_file?
|
|
102
|
+
@info[:info][:pieces] = pieces_from_file
|
|
103
|
+
else
|
|
104
|
+
read_pieces(all_files, @piecelength) do |piece|
|
|
105
|
+
@info[:info][:pieces] += Digest::SHA1.digest(piece)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
set_infohash
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def self.from_file(filename)
|
|
113
|
+
data = data_from_file(filename)
|
|
114
|
+
|
|
115
|
+
to = new(data['announce'], true ) do |t|
|
|
116
|
+
t.tracker_list = data['announce-list']
|
|
117
|
+
t.creation_date = data['creation date']
|
|
118
|
+
t.defaultdir = data['info']['name']
|
|
119
|
+
t.piecelength = data['info']['piece length']
|
|
120
|
+
t.files = data['info']['files']
|
|
121
|
+
t.privacy = data['info']['private']
|
|
122
|
+
t.webseed = data['url-list']
|
|
123
|
+
t.pieces_from_file = data['info']['pieces']
|
|
124
|
+
t.build
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
yield to if block_given?
|
|
128
|
+
to
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self.data_from_file(filename)
|
|
132
|
+
torrent_file = File.absolute_path(filename)
|
|
133
|
+
data = nil
|
|
134
|
+
File.open(torrent_file, 'rb') do |f|
|
|
135
|
+
data = BEncode.load(StringIO.new f.read)
|
|
136
|
+
end
|
|
137
|
+
raise unless data
|
|
138
|
+
data
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def write(filename)
|
|
142
|
+
build_the_torrent
|
|
143
|
+
@torrent_file = File.absolute_path(filename)
|
|
144
|
+
open(@torrent_file, 'wb') do |file|
|
|
145
|
+
file.write self.to_s
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Return the .torrent file as a string
|
|
150
|
+
def to_s
|
|
151
|
+
return "You must add at least one file." if(@files.count < 1)
|
|
152
|
+
build_the_torrent unless (@info[:info][:files].count == @files.count)
|
|
153
|
+
@info.bencode
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def add_file(filepath)
|
|
157
|
+
path_for_torrent = path_for_torrent_from_file(filepath)
|
|
158
|
+
|
|
159
|
+
if((@files.select { |f| f[:path] == path_for_torrent } ).count > 0)
|
|
160
|
+
raise IOError, "Can't add duplicate file #{File.basename(filepath)}"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Remove a leading slash
|
|
164
|
+
if ( ! @dirbase.empty?) && filepath[0] == '/'
|
|
165
|
+
filepath = filepath.slice(1, filepath.length)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
if File.exist?(filepath)
|
|
169
|
+
@files << { path: path_for_torrent, length: File::open(filepath, "rb").size }
|
|
170
|
+
elsif @dirbase && File.exist?(File.join(@dirbase, filepath))
|
|
171
|
+
@files << { path: path_for_torrent, length: File::open(File.join(@dirbase, filepath), "rb").size }
|
|
172
|
+
else
|
|
173
|
+
raise IOError, "Couldn't access #{filepath}"
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def add_directory(path)
|
|
178
|
+
path = Pathname.new(path)
|
|
179
|
+
@dirbase = File.dirname(path) unless path.relative?
|
|
180
|
+
add_directory_to_torrent(path)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def add_directory_to_torrent(path)
|
|
184
|
+
# Using Dir.entries instead of glob so that non-escaped paths can be used
|
|
185
|
+
Dir.entries(path).each do |entry|
|
|
186
|
+
# Ignore unix current and parent directories
|
|
187
|
+
next if entry == '.' or entry == '..'
|
|
188
|
+
|
|
189
|
+
filename = File.join(path, entry).gsub(@dirbase, '') # Add a relative path
|
|
190
|
+
if File.directory?(filename)
|
|
191
|
+
add_directory_to_torrent(filename)
|
|
192
|
+
else
|
|
193
|
+
add_file(filename)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def set_webseed(url)
|
|
199
|
+
validate_url!(url)
|
|
200
|
+
@webseed = url
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def add_tracker(tracker)
|
|
204
|
+
@tracker_list << [tracker]
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def set_private
|
|
208
|
+
@privacy = 1
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def set_public
|
|
212
|
+
@privacy = 0
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Implement Comparable. Provides info about whether this torrent
|
|
216
|
+
# defines the same files as another one.
|
|
217
|
+
def <=>(obj)
|
|
218
|
+
build
|
|
219
|
+
obj.build
|
|
220
|
+
return -1 if infohash < obj.infohash
|
|
221
|
+
return 0 if infohash == obj.infohash
|
|
222
|
+
1
|
|
223
|
+
rescue StandardError
|
|
224
|
+
-1
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Determine if this matches all the same resources
|
|
228
|
+
# (including tracker information)
|
|
229
|
+
def eql?(obj)
|
|
230
|
+
build
|
|
231
|
+
obj.build
|
|
232
|
+
obj.all_info_matches?(@info)
|
|
233
|
+
rescue StandardError
|
|
234
|
+
false
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def all_info_matches?(info)
|
|
238
|
+
@info.eql?(info)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
alias build_the_torrent build
|
|
242
|
+
alias write_torrent write
|
|
243
|
+
|
|
244
|
+
private
|
|
245
|
+
def validate_url!(url)
|
|
246
|
+
u = URI.parse(url)
|
|
247
|
+
if u.scheme.nil? || u.host.nil?
|
|
248
|
+
raise ArgumentError.new("#{url} is not a valid URL")
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def path_for_torrent_from_file(filepath)
|
|
253
|
+
unless @dirbase.empty?
|
|
254
|
+
filepath = filepath.sub(@dirbase, '')
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Remove leading blank item
|
|
258
|
+
path_for_torrent = filepath.split('/') - [""]
|
|
259
|
+
|
|
260
|
+
path_for_torrent
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def set_infohash
|
|
264
|
+
@infohash = Digest::SHA1.hexdigest @info[:info].bencode
|
|
265
|
+
end
|
|
266
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class MktorrentTest < Minitest::Test
|
|
4
|
+
|
|
5
|
+
def setup
|
|
6
|
+
@torrent = Torrent.new(TRACKER)
|
|
7
|
+
@torrent2 = Torrent.new(TRACKER)
|
|
8
|
+
# Lol. This is pretty bad :)
|
|
9
|
+
fail "Could not find #{VALIDFILEPATH}" unless File.exist? VALIDFILEPATH
|
|
10
|
+
fail "Could not find #{VALIDFILE2PATH}" unless File.exist? VALIDFILE2PATH
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_create_torrent
|
|
14
|
+
assert_equal @torrent.tracker, TRACKER
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_add_file_with_invalid_file
|
|
18
|
+
assert_raises(IOError) { @torrent.add_file("../tmp/bogusfile.vhd") }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_add_single_valid_file
|
|
22
|
+
@torrent.add_file(VALIDFILEPATH)
|
|
23
|
+
assert_equal @torrent.count, 1
|
|
24
|
+
assert(@torrent.info[:info][:files].select { |f| f[:name] == VALIDFILENAME })
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_add_second_tracker
|
|
28
|
+
@torrent.add_tracker(SNDTRACKER)
|
|
29
|
+
assert_equal 2, @torrent.tracker_list.count
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_add_multiple_trackers
|
|
33
|
+
@torrent.add_tracker(SNDTRACKER)
|
|
34
|
+
@torrent.add_tracker(THDTRACKER)
|
|
35
|
+
assert_equal SNDTRACKER, @torrent.tracker_list[1][0]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_add_valid_file_as_path_array
|
|
39
|
+
@torrent.add_file(VALIDFILEPATH)
|
|
40
|
+
path = @torrent.info[:info][:files].first[:path]
|
|
41
|
+
assert_kind_of Array, path, "Path should be an array, not #{path}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_add_another_valid_file
|
|
45
|
+
@torrent.add_file(VALIDFILEPATH)
|
|
46
|
+
@torrent.add_file(VALIDFILE2PATH)
|
|
47
|
+
assert_equal 2, @torrent.count
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_prevent_duplicate_file_from_being_added
|
|
51
|
+
@torrent.add_file(VALIDFILEPATH)
|
|
52
|
+
assert_raises(IOError) { @torrent.add_file(VALIDFILEPATH) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_add_directory_increments_file_count
|
|
56
|
+
@torrent.add_directory(VALIDPATH)
|
|
57
|
+
assert_equal 3, @torrent.count
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# When adding a directory, only the folder that's added (and everything below) should appear in the metadata
|
|
61
|
+
def test_add_directory_uses_relative_paths
|
|
62
|
+
assert [ VALIDFILEPATH, VALIDFILE2PATH ].each { |p| p.start_with?(VALIDPATH) }
|
|
63
|
+
@torrent.add_directory(VALIDPATH)
|
|
64
|
+
assert @torrent.files.each { |f| ! f[:path].join('/').start_with?(File.basename(File.dirname(VALIDPATH))) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_default_privacy
|
|
68
|
+
@torrent.add_file(VALIDFILEPATH)
|
|
69
|
+
assert_equal 0, @torrent.privacy
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_set_privacy
|
|
73
|
+
test_default_privacy
|
|
74
|
+
@torrent.set_private
|
|
75
|
+
assert_equal 1, @torrent.privacy
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_set_valid_webseed
|
|
79
|
+
@torrent.set_webseed(WEBSEED)
|
|
80
|
+
assert_equal WEBSEED, @torrent.webseed
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def test_add_invalid_webseed
|
|
84
|
+
assert_raises(ArgumentError) {
|
|
85
|
+
@torrent.set_webseed('uheoatnhuetano')
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def test_torrent2_equality
|
|
90
|
+
assert_equal @torrent, @torrent2
|
|
91
|
+
@torrent2.tracker = 'http://example.com'
|
|
92
|
+
assert @torrent == @torrent2, 'info hash should still match, so torrent == torrent2'
|
|
93
|
+
assert !@torrent.eql?(@torrent2), 'announce uri changed, so torrent ! eql? torrent2'
|
|
94
|
+
end
|
|
95
|
+
end
|
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'minitest/autorun'
|
|
2
|
+
require 'minitest/unit'
|
|
3
|
+
require 'minitest/reporters'
|
|
4
|
+
require 'shellwords'
|
|
5
|
+
|
|
6
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'mktorrent')
|
|
7
|
+
Dir.glob('test/support/*.rb').each { |r| load r }
|
|
8
|
+
|
|
9
|
+
Minitest::Reporters.use! [
|
|
10
|
+
Minitest::Reporters::DefaultReporter.new(color: true),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
TRACKER = "http://test.example.com"
|
|
15
|
+
SNDTRACKER = "udp://test.tracker.tk:80"
|
|
16
|
+
THDTRACKER = "udp://open.testtracker.com:6500/announce"
|
|
17
|
+
WEBSEED = "http://seed.example.com/webseed"
|
|
18
|
+
EXPECTED_INFOHASH = "a8036d51ada8fb699c9f29d7861e5589f2d20cf9"
|
|
19
|
+
VALIDPATH = File.expand_path("#{File.dirname(__FILE__)}/test_data")
|
|
20
|
+
VALIDFILEPATH = File.expand_path("#{File.dirname(__FILE__)}/test_data/sample_file1.vhd")
|
|
21
|
+
VALIDFILE2PATH = File.expand_path("#{File.dirname(__FILE__)}/test_data/sample_file2.vhd")
|
|
22
|
+
VALIDFILENAME = "randomfile.vhd"
|
metadata
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ll-mktorrent
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.8.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Timothy Mukaibo
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2018-07-30 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bencode
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.8'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.8'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: pry
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: minitest
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '5.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '5.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: minitest-reporters
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '1.0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '1.0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rubygems-tasks
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - '='
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: 0.2.4
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - '='
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: 0.2.4
|
|
97
|
+
description: Create .torrent files easily with this gem. The code is ugly, but it
|
|
98
|
+
works :)
|
|
99
|
+
email: timothy@mukaibo.com
|
|
100
|
+
executables: []
|
|
101
|
+
extensions: []
|
|
102
|
+
extra_rdoc_files: []
|
|
103
|
+
files:
|
|
104
|
+
- lib/mktorrent.rb
|
|
105
|
+
- test/mktorrent_test.rb
|
|
106
|
+
- test/test_helper.rb
|
|
107
|
+
homepage: https://github.com/mukaibot/mktorrent
|
|
108
|
+
licenses:
|
|
109
|
+
- MIT
|
|
110
|
+
metadata: {}
|
|
111
|
+
post_install_message:
|
|
112
|
+
rdoc_options: []
|
|
113
|
+
require_paths:
|
|
114
|
+
- lib
|
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
116
|
+
requirements:
|
|
117
|
+
- - ">="
|
|
118
|
+
- !ruby/object:Gem::Version
|
|
119
|
+
version: '0'
|
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
125
|
+
requirements: []
|
|
126
|
+
rubyforge_project:
|
|
127
|
+
rubygems_version: 2.6.13
|
|
128
|
+
signing_key:
|
|
129
|
+
specification_version: 4
|
|
130
|
+
summary: Create .torrent files easily with this gem
|
|
131
|
+
test_files:
|
|
132
|
+
- test/mktorrent_test.rb
|
|
133
|
+
- test/test_helper.rb
|