pants 0.1.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.
- data/.gemtest +1 -0
- data/.gitignore +23 -0
- data/.rspec +3 -0
- data/.travis.yml +4 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +42 -0
- data/History.rdoc +6 -0
- data/README.rdoc +339 -0
- data/Rakefile +23 -0
- data/bin/pants +59 -0
- data/lib/pants.rb +84 -0
- data/lib/pants/core.rb +216 -0
- data/lib/pants/error.rb +5 -0
- data/lib/pants/logger.rb +8 -0
- data/lib/pants/network_helpers.rb +25 -0
- data/lib/pants/readers/base_reader.rb +302 -0
- data/lib/pants/readers/file_reader.rb +81 -0
- data/lib/pants/readers/udp_reader.rb +80 -0
- data/lib/pants/seam.rb +120 -0
- data/lib/pants/version.rb +3 -0
- data/lib/pants/writers/base_writer.rb +73 -0
- data/lib/pants/writers/file_writer.rb +59 -0
- data/lib/pants/writers/udp_writer.rb +125 -0
- data/pants.gemspec +31 -0
- data/spec/acceptance/file_reads_spec.rb +24 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/matchers/file_same_size_as.rb +20 -0
- data/spec/support/pants.wav +0 -0
- data/spec/unit/pants/core_spec.rb +105 -0
- data/spec/unit/pants/readers/base_reader_spec.rb +340 -0
- data/spec/unit/pants/readers/file_reader_spec.rb +94 -0
- data/spec/unit/pants/version_spec.rb +7 -0
- data/spec/unit/pants/writers/base_writer_spec.rb +35 -0
- data/spec/unit/pants/writers/file_writer_spec.rb +71 -0
- data/spec/unit/pants/writers/udp_writer_spec.rb +146 -0
- data/spec/unit/pants_spec.rb +6 -0
- data/tasks/pantsmark.thor +66 -0
- data/test_readers.rb +43 -0
- data/test_seams.rb +107 -0
- metadata +215 -0
data/pants.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
require 'pants/version'
|
3
|
+
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "pants"
|
7
|
+
s.version = Pants::VERSION
|
8
|
+
s.author = %q(Steve Loveless)
|
9
|
+
s.homepage = %q(http://github.com/turboladen/pants)
|
10
|
+
s.email = %q(steve.loveless@gmail.com)
|
11
|
+
s.summary = %q(Easy, fast, I/O multiplexer)
|
12
|
+
s.description = %q[Pants redirects IO using EventMachine from one input source
|
13
|
+
to many different destinations. In some senses, pants is like a *nix pipe that
|
14
|
+
(works on Windows and) allows for duplicating data across many pipes (like
|
15
|
+
splice and tee).]
|
16
|
+
|
17
|
+
s.required_rubygems_version = ">=1.8.0"
|
18
|
+
s.files = `git ls-files`.split($/)
|
19
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
20
|
+
s.require_paths = %w(lib)
|
21
|
+
s.executables = %w(pants)
|
22
|
+
|
23
|
+
s.add_dependency "eventmachine", ">= 1.0.0"
|
24
|
+
s.add_dependency "log_switch", ">= 0.4.0"
|
25
|
+
s.add_dependency "thor"
|
26
|
+
|
27
|
+
s.add_development_dependency "rake"
|
28
|
+
s.add_development_dependency "rspec", ">= 2.6.0"
|
29
|
+
s.add_development_dependency "simplecov", ">= 0"
|
30
|
+
s.add_development_dependency "yard", ">= 0.7.2"
|
31
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pants'
|
3
|
+
|
4
|
+
|
5
|
+
describe "File reads" do
|
6
|
+
let(:original_file_path) do
|
7
|
+
File.expand_path(File.dirname(__FILE__) + "/../support/pants.wav")
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:dest_file_path) { 'acceptance_test_dest_file' }
|
11
|
+
|
12
|
+
after do
|
13
|
+
FileUtils.rm(dest_file_path)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "reads from a file and copies all data to all other writer types" do
|
17
|
+
Pants.read(original_file_path) do |reader|
|
18
|
+
reader.write_to(dest_file_path)
|
19
|
+
reader.write_to('udp://127.0.0.1:0')
|
20
|
+
end
|
21
|
+
|
22
|
+
original_file_path.should be_the_same_size_as dest_file_path
|
23
|
+
end
|
24
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
RSpec::Matchers.define :be_the_same_size_as do |expected|
|
2
|
+
match do |actual|
|
3
|
+
actual_size = File.stat(actual).size
|
4
|
+
expected_size = File.stat(expected).size
|
5
|
+
@difference = expected_size - actual_size
|
6
|
+
@difference == 0
|
7
|
+
end
|
8
|
+
|
9
|
+
failure_message_for_should do |actual|
|
10
|
+
"expected #{actual} to be the same size as #{expected}; diff: #{@difference}"
|
11
|
+
end
|
12
|
+
|
13
|
+
failure_message_for_should_not do |actual|
|
14
|
+
"expected #{actual} to not be the same size as #{expected}"
|
15
|
+
end
|
16
|
+
|
17
|
+
description do
|
18
|
+
"be the same size as #{expected}"
|
19
|
+
end
|
20
|
+
end
|
Binary file
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require "pants/core"
|
3
|
+
|
4
|
+
|
5
|
+
describe Pants::Core do
|
6
|
+
let(:callback) { double "EM.Callback" }
|
7
|
+
|
8
|
+
describe "#read" do
|
9
|
+
context "unknown URI scheme" do
|
10
|
+
it "raises an ArgumentError" do
|
11
|
+
expect {
|
12
|
+
subject.read("test://stuff")
|
13
|
+
}.to raise_error ArgumentError
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "known URI scheme" do
|
18
|
+
let(:test_reader) do
|
19
|
+
double "Pants::TestReader"
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:readers) do
|
23
|
+
[{ uri_scheme: 'test', klass: test_reader }]
|
24
|
+
end
|
25
|
+
|
26
|
+
before do
|
27
|
+
Pants.stub(:readers).and_return readers
|
28
|
+
end
|
29
|
+
|
30
|
+
it "creates the new reader and adds it to @readers" do
|
31
|
+
uri = URI "test://somehost"
|
32
|
+
|
33
|
+
subject.should_receive(:new_reader_from_uri) do |arg1, arg2|
|
34
|
+
arg1.should == uri
|
35
|
+
arg2.should be_a EM.Callback
|
36
|
+
end.and_return(test_reader)
|
37
|
+
|
38
|
+
subject.read('test://somehost')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#run" do
|
44
|
+
let(:reader) do
|
45
|
+
r = double "Pants::TestReader"
|
46
|
+
r.stub(:read_object)
|
47
|
+
r.stub_chain(:writers, :size)
|
48
|
+
r.stub_chain(:writers, :each_with_index)
|
49
|
+
|
50
|
+
r
|
51
|
+
end
|
52
|
+
|
53
|
+
let(:iterator) do
|
54
|
+
i = double "EventMachine::Iterator"
|
55
|
+
i.should_receive(:each).and_yield(reader, i)
|
56
|
+
i.stub(:next)
|
57
|
+
|
58
|
+
i
|
59
|
+
end
|
60
|
+
|
61
|
+
before do
|
62
|
+
EM.stub(:run).and_yield
|
63
|
+
EM::Iterator.stub(:new).and_return(iterator)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "starts all of the readers" do
|
67
|
+
reader.should_receive(:start)
|
68
|
+
subject.instance_variable_set(:@readers, [reader])
|
69
|
+
subject.run
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "#new_reader_from_uri" do
|
74
|
+
context "uri_scheme exists in readers" do
|
75
|
+
let(:test_reader) do
|
76
|
+
double "Pants::TestReader"
|
77
|
+
end
|
78
|
+
|
79
|
+
let(:readers) do
|
80
|
+
[{ uri_scheme: 'test', klass: test_reader, args: [:host] }]
|
81
|
+
end
|
82
|
+
|
83
|
+
before do
|
84
|
+
Pants.stub(:readers).and_return readers
|
85
|
+
end
|
86
|
+
|
87
|
+
it "creates a new Reader based on the scheme mapping" do
|
88
|
+
uri = URI "test://testhost"
|
89
|
+
test_reader.should_receive(:new).with("testhost", callback)
|
90
|
+
|
91
|
+
subject.send(:new_reader_from_uri, uri, callback)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "uri_scheme does not exist in readers" do
|
96
|
+
it "raises" do
|
97
|
+
uri = URI "bobo://host:1234/path"
|
98
|
+
|
99
|
+
expect {
|
100
|
+
subject.send(:new_reader_from_uri, uri, callback)
|
101
|
+
}.to raise_error(ArgumentError)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,340 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pants/readers/base_reader'
|
3
|
+
|
4
|
+
|
5
|
+
describe Pants::Readers::BaseReader do
|
6
|
+
let(:test_writer) { double "Pants::Writers::TestWriter" }
|
7
|
+
let(:core_stopper_callback) { double "EM.Callback" }
|
8
|
+
subject { Pants::Readers::BaseReader.new(core_stopper_callback) }
|
9
|
+
|
10
|
+
describe "#initialize" do
|
11
|
+
it "creates a write_to_channel" do
|
12
|
+
reader = Pants::Readers::BaseReader.new(core_stopper_callback)
|
13
|
+
reader.write_to_channel.should be_a EM::Channel
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#start" do
|
18
|
+
around(:each) do |example|
|
19
|
+
EM.run do
|
20
|
+
example.run
|
21
|
+
EM.stop
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:callback) { double "EventMachine.Callback" }
|
26
|
+
|
27
|
+
let(:em_iterator) do
|
28
|
+
EventMachine::Iterator.new([test_writer])
|
29
|
+
end
|
30
|
+
|
31
|
+
let(:tick_loop) do
|
32
|
+
t = double "EventMachine::TickLoop"
|
33
|
+
t.should_receive(:on_stop).and_yield
|
34
|
+
|
35
|
+
t
|
36
|
+
end
|
37
|
+
|
38
|
+
before do
|
39
|
+
subject.instance_variable_set(:@writers, [test_writer])
|
40
|
+
end
|
41
|
+
|
42
|
+
it "starts the writers first, then the readers" do
|
43
|
+
EventMachine.stub(:tick_loop).and_yield.and_return(tick_loop)
|
44
|
+
test_writer.stub(:running?).and_return(false, true)
|
45
|
+
|
46
|
+
em_iterator.should_receive(:each).and_yield(test_writer, em_iterator)
|
47
|
+
em_iterator.stub(:next)
|
48
|
+
EventMachine::Iterator.should_receive(:new).and_return(em_iterator)
|
49
|
+
test_writer.should_receive(:start)
|
50
|
+
|
51
|
+
callback.should_receive(:call)
|
52
|
+
|
53
|
+
subject.start(callback)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#stop!" do
|
58
|
+
let(:stopper) do
|
59
|
+
s = double "EventMachine::Callback"
|
60
|
+
s.should_receive(:call)
|
61
|
+
|
62
|
+
s
|
63
|
+
end
|
64
|
+
|
65
|
+
it "calls succeed on the stopper" do
|
66
|
+
subject.should_receive(:stopper).and_return(stopper)
|
67
|
+
|
68
|
+
subject.stop!
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "#write_to" do
|
73
|
+
context "unknown URI scheme" do
|
74
|
+
it "raises an ArgumentError" do
|
75
|
+
expect {
|
76
|
+
subject.write_to("test://stuff")
|
77
|
+
}.to raise_error ArgumentError
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context "known URI scheme" do
|
82
|
+
it "creates the new writer, adds it to @writers, and returns it" do
|
83
|
+
uri = URI "test://somehost"
|
84
|
+
subject.should_receive(:new_writer_from_uri) do |arg1, arg2|
|
85
|
+
arg1.should == uri
|
86
|
+
arg2.should be_a EventMachine::Channel
|
87
|
+
end.and_return(test_writer)
|
88
|
+
|
89
|
+
subject.write_to('test://somehost').should == test_writer
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "#add_writer" do
|
95
|
+
let(:obj_object) { double "TestWriter" }
|
96
|
+
|
97
|
+
context "obj is a Class" do
|
98
|
+
let(:obj) do
|
99
|
+
class TestWriter; self; end
|
100
|
+
end
|
101
|
+
|
102
|
+
it "creates a new object of that class with all args and the write channel" do
|
103
|
+
first_arg = double "first arg"
|
104
|
+
second_arg = double "second arg"
|
105
|
+
|
106
|
+
obj.should_receive(:new) do |arg1, arg2, arg3|
|
107
|
+
arg1.should == first_arg
|
108
|
+
arg2.should == second_arg
|
109
|
+
arg3.should be_a EventMachine::Channel
|
110
|
+
end.and_return(obj_object)
|
111
|
+
|
112
|
+
subject.add_writer(obj, first_arg, second_arg).should == obj_object
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "obj is a instantiated Writer object" do
|
117
|
+
it "adds that object to the list of writers and returns it" do
|
118
|
+
obj_object.should_receive(:kind_of?).with(Pants::Writers::BaseWriter).
|
119
|
+
and_return(true)
|
120
|
+
|
121
|
+
subject.add_writer(obj_object).should == obj_object
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context "obj isn't a Class and isn't a Writer" do
|
126
|
+
it "raises a Pants::Error" do
|
127
|
+
expect {
|
128
|
+
subject.add_writer("meow")
|
129
|
+
}.to raise_error Pants::Error
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe '#remove_writer' do
|
135
|
+
before do
|
136
|
+
subject.instance_variable_set(:@writers, writers)
|
137
|
+
end
|
138
|
+
|
139
|
+
context "using URI" do
|
140
|
+
let(:writer1) do
|
141
|
+
double "Pants::Writers::FileWriter", file_path: "file1"
|
142
|
+
end
|
143
|
+
|
144
|
+
let(:writer2) do
|
145
|
+
double "Pants::Writers::FileWriter", file_path: "file2"
|
146
|
+
end
|
147
|
+
|
148
|
+
let(:writer3) do
|
149
|
+
double "Pants::Writers::UDPWriter",
|
150
|
+
host: '127.0.0.1',
|
151
|
+
port: 1234
|
152
|
+
end
|
153
|
+
|
154
|
+
let(:writer4) do
|
155
|
+
double "Pants::Writers::UDPWriter",
|
156
|
+
host: '127.0.0.2',
|
157
|
+
port: 1234
|
158
|
+
end
|
159
|
+
|
160
|
+
let(:writers) { [writer1, writer2, writer3, writer4] }
|
161
|
+
|
162
|
+
context "Pants.writers doesn't define a writer by the given scheme" do
|
163
|
+
it "raises an ArgumentError" do
|
164
|
+
expect {
|
165
|
+
subject.remove_writer("http://1.2.3.4")
|
166
|
+
}.to raise_error ArgumentError
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context "@writers contains a writer type by the given URI" do
|
171
|
+
before do
|
172
|
+
writer1.should_receive(:is_a?).with(Pants::Writers::UDPWriter).and_return(false)
|
173
|
+
writer2.should_receive(:is_a?).with(Pants::Writers::UDPWriter).and_return(false)
|
174
|
+
writer3.should_receive(:is_a?).with(Pants::Writers::UDPWriter).and_return(true)
|
175
|
+
writer4.should_receive(:is_a?).with(Pants::Writers::UDPWriter).and_return(true)
|
176
|
+
end
|
177
|
+
|
178
|
+
context "but doesn't match criteria given by the URI" do
|
179
|
+
it "doesn't remove anything" do
|
180
|
+
subject.remove_writer("udp://127.0.0.1:9000")
|
181
|
+
subject.writers.should be writers
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
context "and criteria matches given key_value_pairs" do
|
186
|
+
it "removes the matching object" do
|
187
|
+
subject.remove_writer("udp://127.0.0.1:1234")
|
188
|
+
subject.writers.should == [writer1, writer2, writer4]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
context "using class and args" do
|
195
|
+
let(:writers) do
|
196
|
+
[String.new, Hash.new, Array.new]
|
197
|
+
end
|
198
|
+
|
199
|
+
context "@writers doesn't contain a writer by the given class" do
|
200
|
+
it "doesn't remove anything" do
|
201
|
+
subject.remove_writer(URI, {})
|
202
|
+
subject.writers.size.should be 3
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
context "@writers contains a writer by the given class" do
|
207
|
+
context "but doesn't match criteria given by key_value_pairs" do
|
208
|
+
it "doesn't remove anything" do
|
209
|
+
subject.remove_writer(String, size: 1)
|
210
|
+
subject.writers.size.should be 3
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
context "and criteria matches given key_value_pairs" do
|
215
|
+
it "removes the matching object" do
|
216
|
+
subject.remove_writer(String, size: 0)
|
217
|
+
subject.writers.size.should be 2
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
context "and criteria of more than 1 object matches given key_value_pairs" do
|
222
|
+
let(:writers) do
|
223
|
+
[String.new, String.new, String.new]
|
224
|
+
end
|
225
|
+
|
226
|
+
it "removes the matching object" do
|
227
|
+
subject.remove_writer(String, size: 0)
|
228
|
+
subject.writers.size.should be 0
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
describe "#add_seam" do
|
236
|
+
context "klass doesn't exist" do
|
237
|
+
it "raises a NameError" do
|
238
|
+
expect {
|
239
|
+
subject.add_seam(BananaSplitPantsParty)
|
240
|
+
}.to raise_error NameError
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
context "klass exists" do
|
245
|
+
let(:channel) do
|
246
|
+
double "EventMachine::Channel"
|
247
|
+
end
|
248
|
+
|
249
|
+
let(:seam_class) do
|
250
|
+
s = double "Pants::Seam"
|
251
|
+
s.should_receive(:new).with(core_stopper_callback, channel).and_return(seam)
|
252
|
+
|
253
|
+
s
|
254
|
+
end
|
255
|
+
|
256
|
+
let(:seam) do
|
257
|
+
double "Pants::Seam"
|
258
|
+
end
|
259
|
+
|
260
|
+
before do
|
261
|
+
subject.instance_variable_set(:@write_to_channel, channel)
|
262
|
+
end
|
263
|
+
|
264
|
+
it "creates the new object and adds it to the internal list of writers" do
|
265
|
+
subject.add_seam(seam_class).should == seam
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
describe "#stopper" do
|
271
|
+
context "when called back with success" do
|
272
|
+
before do
|
273
|
+
subject.instance_variable_set(:@writers, [test_writer])
|
274
|
+
EM.stub(:next_tick).and_yield
|
275
|
+
EM.stub(:tick_loop).and_yield.and_return(tick_loop)
|
276
|
+
EM::Iterator.stub(:new).and_return(iterator)
|
277
|
+
end
|
278
|
+
|
279
|
+
let(:tick_loop) do
|
280
|
+
t = double "EventMachine::TickLoop"
|
281
|
+
t.should_receive(:on_stop).and_yield
|
282
|
+
|
283
|
+
t
|
284
|
+
end
|
285
|
+
|
286
|
+
let(:iterator) do
|
287
|
+
i = double "EventMachine::Iterator"
|
288
|
+
i.should_receive(:each).and_yield(test_writer, i)
|
289
|
+
i.stub(:next)
|
290
|
+
|
291
|
+
i
|
292
|
+
end
|
293
|
+
|
294
|
+
it "calls each writer's stopper and the main callback" do
|
295
|
+
core_stopper_callback.should_receive(:call)
|
296
|
+
test_writer.should_receive(:running?)
|
297
|
+
test_writer.should_receive(:stop)
|
298
|
+
|
299
|
+
subject.send(:stopper).call
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
describe "#new_writer_from_uri" do
|
305
|
+
let(:channel) do
|
306
|
+
double "EventMachine::Channel"
|
307
|
+
end
|
308
|
+
|
309
|
+
context "uri_scheme exists in Pants.writers" do
|
310
|
+
let(:test_writer) do
|
311
|
+
double "Pants::Writers::TestWriter"
|
312
|
+
end
|
313
|
+
|
314
|
+
let(:writers) do
|
315
|
+
[{ uri_scheme: 'test', klass: test_writer, args: [:host] }]
|
316
|
+
end
|
317
|
+
|
318
|
+
before do
|
319
|
+
Pants.stub(:writers).and_return writers
|
320
|
+
end
|
321
|
+
|
322
|
+
it "creates a new Writer based on the scheme mapping" do
|
323
|
+
uri = URI "test://testhost"
|
324
|
+
test_writer.should_receive(:new).with("testhost", channel)
|
325
|
+
|
326
|
+
subject.send(:new_writer_from_uri, uri, channel)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
context "uri_scheme does not exist in writers" do
|
331
|
+
it "raises" do
|
332
|
+
uri = URI "bobo://host:1234/path"
|
333
|
+
|
334
|
+
expect {
|
335
|
+
subject.send(:new_writer_from_uri, uri, channel)
|
336
|
+
}.to raise_error(ArgumentError)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|