raggi-sized_header_protocol 0.1.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/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.1.0 / 2008-10-01
2
+
3
+ * 1 major enhancement
4
+ * Birthday!
data/Manifest.txt ADDED
@@ -0,0 +1,25 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ TODO.txt
6
+ lib/sized_header_protocol.rb
7
+ sized_header_protocol.gemspec
8
+ spec/.bacon
9
+ spec/helper.rb
10
+ spec/runner
11
+ spec/spec_sized_header_protocol.rb
12
+ tasks/ann.rake
13
+ tasks/autospec.rake
14
+ tasks/bones.rake
15
+ tasks/gem.rake
16
+ tasks/git.rake
17
+ tasks/manifest.rake
18
+ tasks/notes.rake
19
+ tasks/post_load.rake
20
+ tasks/rdoc.rake
21
+ tasks/rubyforge.rake
22
+ tasks/setup.rb
23
+ tasks/spec.rake
24
+ tasks/svn.rake
25
+ tasks/test.rake
data/README.txt ADDED
@@ -0,0 +1,97 @@
1
+ sized_header_protocol
2
+ by James Tucker
3
+ http://ra66i.org
4
+ http://github.com/raggi/sized_header_protocol
5
+
6
+ == DESCRIPTION:
7
+
8
+ A simple chainable implementation of a basic sized header protocol. The
9
+ 'protocol' does not define its transport, that is left as configurable.
10
+ Implementing wire protocols should call send_data to construct packets or
11
+ frames and receive_data to deconstruct packets or frames.
12
+
13
+ == FEATURES/PROBLEMS:
14
+
15
+ * Simplicity.
16
+ * Not really suitable for streaming (this is not a problem, it's a feature).
17
+ * Needs its documentation filling in.
18
+
19
+ == SYNOPSIS:
20
+
21
+ The following example does not cover common usage exactly, in that it talks to
22
+ itself, however, it shows an example of the data flow of an application
23
+ utilizing the protocol.
24
+
25
+ For incoming data, receive_data calls the protocol in order to unpack the
26
+ data. The protocol will call receive_packet when a fully buffered packet has
27
+ been deconstructed and the data may be read by the application.
28
+
29
+ For outgoing data, the application calls send_packet, and that will call on
30
+ the protocol. The protocol will use the callback send_data specified upon
31
+ construction in order to send data 'over the wire' or to the next protocol in
32
+ the chain, in this case send_data.
33
+
34
+ module BackChatExample
35
+ def post_init
36
+ @prot = SizedHeaderProtocol.new(self, :send_data, self, :receive_packet)
37
+ end
38
+
39
+ def receive_data(data)
40
+ @prot.receive_data(data)
41
+ end
42
+
43
+ def send_packet(data)
44
+ @prot.send_data(data)
45
+ end
46
+
47
+ def receive_packet(data)
48
+ puts "<< #{data}"
49
+ end
50
+
51
+ def send_data(data)
52
+ puts ">> #{data}"
53
+ end
54
+ end
55
+
56
+ conn = Class.new
57
+ conn.extend EmMock
58
+ conn.post_init
59
+ conn.send_data('send')
60
+ data = "read"
61
+ conn.receive_data("#{[data.size].pack('N')}#{data}")
62
+
63
+ See the specs (tests) for further details.
64
+
65
+ == REQUIREMENTS:
66
+
67
+ * A transport system (eventmachine / packet / rev)
68
+ * Bacon for the test (spec) suite.
69
+
70
+ == INSTALL:
71
+
72
+ * sudo gem install sized_header_protocol
73
+
74
+ == LICENSE:
75
+
76
+ (The MIT License)
77
+
78
+ Copyright (c) 2008 James Tucker
79
+
80
+ Permission is hereby granted, free of charge, to any person obtaining
81
+ a copy of this software and associated documentation files (the
82
+ 'Software'), to deal in the Software without restriction, including
83
+ without limitation the rights to use, copy, modify, merge, publish,
84
+ distribute, sublicense, and/or sell copies of the Software, and to
85
+ permit persons to whom the Software is furnished to do so, subject to
86
+ the following conditions:
87
+
88
+ The above copyright notice and this permission notice shall be
89
+ included in all copies or substantial portions of the Software.
90
+
91
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
92
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
93
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
94
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
95
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
96
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
97
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ # Look in the tasks/setup.rb file for the various options that can be
2
+ # configured in this Rakefile. The .rake files in the tasks directory
3
+ # are where the options are used.
4
+
5
+ load 'tasks/setup.rb'
6
+
7
+ ensure_in_path 'lib'
8
+ require 'sized_header_protocol'
9
+
10
+ task :default => 'spec:run'
11
+
12
+ PROJ.name = 'sized_header_protocol'
13
+ PROJ.summary = 'A simple chainable implementation of a basic sized header protocol'
14
+ PROJ.authors = 'James Tucker'
15
+ PROJ.email = 'raggi@rubyforge.org'
16
+ PROJ.url = 'http://github.com/raggi/sized_header_protocol'
17
+ PROJ.rubyforge.name = 'sized_header_protocol'
18
+ PROJ.version = SizedHeaderProtocol.version
19
+
20
+ PROJ.exclude = %w(tmp$ bak$ ~$ CVS \.git \.hg \.svn ^pkg ^doc \.DS_Store
21
+ \.cvs \.svn \.hgignore \.gitignore \.dotest \.swp$ ~$)
22
+
23
+ namespace :gem do
24
+ task :spec do
25
+ open(PROJ.name + '.gemspec', 'w') { |f| f << PROJ.gem._spec.to_ruby }
26
+ end
27
+ end
@@ -0,0 +1,42 @@
1
+ class SizedHeaderProtocol
2
+
3
+ Version = '0.1.1'
4
+
5
+ def self.version
6
+ Version
7
+ end
8
+
9
+ attr_accessor :send_obj, :send_meth, :read_obj, :read_meth
10
+
11
+ def initialize(send_obj, send_meth, read_obj, read_meth)
12
+ @send_obj, @send_meth = send_obj, send_meth
13
+ @read_obj, @read_meth = read_obj, read_meth
14
+ @buffer, @next_size = '', nil
15
+ end
16
+
17
+ def send_data(data)
18
+ @send_obj.send @send_meth, "#{[data.size].pack('N')}#{data}"
19
+ end
20
+
21
+ def receive_data(data)
22
+ @buffer << data
23
+ while more?
24
+ unpack
25
+ end
26
+ end
27
+
28
+ private
29
+ def unpack
30
+ @next_size ||= @buffer.slice!(0, 4).unpack('N').first
31
+ return unless more?
32
+ data = @buffer.slice!(0, @next_size)
33
+ @next_size = nil
34
+ @read_obj.send(@read_meth, data)
35
+ end
36
+
37
+ def more?
38
+ # XXX 1.9 has bytesize, too, but not bytewise slice.
39
+ @buffer.size >= (@next_size || 4)
40
+ end
41
+
42
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{sized_header_protocol}
5
+ s.version = "0.1.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["James Tucker"]
9
+ s.date = %q{2008-10-02}
10
+ s.description = %q{A simple chainable implementation of a basic sized header protocol. The 'protocol' does not define it's transport, that is left as configurable. Implementing wire protocols should call send_data to construct packets or frames and receive_data to deconstruct packets or frames.}
11
+ s.email = %q{raggi@rubyforge.org}
12
+ s.extra_rdoc_files = ["History.txt", "README.txt", "TODO.txt"]
13
+ s.files = ["History.txt", "Manifest.txt", "README.txt", "Rakefile", "TODO.txt", "lib/sized_header_protocol.rb", "sized_header_protocol.gemspec", "spec/.bacon", "spec/helper.rb", "spec/runner", "spec/spec_sized_header_protocol.rb", "tasks/ann.rake", "tasks/autospec.rake", "tasks/bones.rake", "tasks/gem.rake", "tasks/git.rake", "tasks/manifest.rake", "tasks/notes.rake", "tasks/post_load.rake", "tasks/rdoc.rake", "tasks/rubyforge.rake", "tasks/setup.rb", "tasks/spec.rake", "tasks/svn.rake", "tasks/test.rake"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{http://github.com/raggi/sized_header_protocol}
16
+ s.rdoc_options = ["--main", "README.txt"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{sized_header_protocol}
19
+ s.rubygems_version = %q{1.3.0}
20
+ s.summary = %q{A simple chainable implementation of a basic sized header protocol}
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 2
25
+
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
+ else
28
+ end
29
+ else
30
+ end
31
+ end
data/spec/.bacon ADDED
File without changes
data/spec/helper.rb ADDED
@@ -0,0 +1,16 @@
1
+ # Disable test/unit and rspec from running, in case loaded by broken tools.
2
+ Test::Unit.run = false if defined?(Test::Unit)
3
+ Spec::run = false if defined?(Spec) && Spec::respond_to?(:run=)
4
+
5
+ # Setup a nice testing environment
6
+ $TESTING=true
7
+ $:.push File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
8
+ $:.uniq!
9
+
10
+ %w[rubygems bacon].each { |r| require r }
11
+
12
+ # Bacon doesn't do any automagic, so lets tell it to!
13
+ Bacon.summary_on_exit
14
+
15
+ require File.expand_path(
16
+ File.join(File.dirname(__FILE__), *%w[.. lib sized_header_protocol]))
data/spec/runner ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ __DIR__ = File.dirname(__FILE__)
3
+ __APP__ = File.expand_path(__DIR__ + '/../')
4
+
5
+ puts
6
+ Dir.chdir(__APP__) do
7
+ files = ARGV.empty? ? Dir.glob('{test,spec}/**/{test,spec}_*.rb') : ARGV
8
+ files.each { |f| require f }
9
+ end
@@ -0,0 +1,76 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class MockTransport
4
+
5
+ attr_accessor :sent, :recv
6
+
7
+ def initialize
8
+ @sent, @recv = [], []
9
+ end
10
+
11
+ def send_data(data)
12
+ @sent << data
13
+ end
14
+
15
+ def receive_data(data)
16
+ @recv << data
17
+ end
18
+
19
+ end
20
+
21
+ describe 'sized header protocol sending a frame' do
22
+ before do
23
+ @trans = MockTransport.new
24
+ @proto = SizedHeaderProtocol.new(@trans, :send_data, @trans, :receive_data)
25
+ end
26
+
27
+ should 'call the send method on the target object with a size header' do
28
+ data = '1234567890'
29
+ size = data.size
30
+ @proto.send_data(data)
31
+ @trans.sent.first[0,4].unpack('N').first.should.eql(size)
32
+ end
33
+
34
+ should 'send the data immediately after the header' do
35
+ data = '1234567890'
36
+ @proto.send_data(data)
37
+ @trans.sent.first[4,data.size].should.eql(data)
38
+ end
39
+ end
40
+
41
+ describe 'sized header protocol receiving a frame' do
42
+ before do
43
+ @trans = MockTransport.new
44
+ @proto = SizedHeaderProtocol.new(@trans, :send_data, @trans, :receive_data)
45
+ end
46
+
47
+ should 'call receive_data with the data when a whole frame is read' do
48
+ data = '1234567890'
49
+ size = data.size
50
+ packet = "#{[size].pack('N')}#{data}"
51
+ @proto.receive_data(packet)
52
+ @trans.recv.first.should.eql(data)
53
+ end
54
+
55
+ should 'buffer data until a whole frame is read' do
56
+ data = '1234567890'
57
+ size = data.size
58
+ packet = "#{[size].pack('N')}#{data}"
59
+ first = packet.slice 0, packet.size/2
60
+ second = packet.slice packet.size/2, packet.size # excessive, but meh.
61
+ @proto.receive_data(first)
62
+ @trans.recv.should.be.empty
63
+ @proto.receive_data(second)
64
+ @trans.recv.first.should.eql(data)
65
+ end
66
+
67
+ should 'call the receive callback for each packet read' do
68
+ data = '1234567890'
69
+ size = data.size
70
+ packet = "#{[size].pack('N')}#{data}"
71
+ @proto.receive_data(packet + packet)
72
+ @trans.recv.size.should.eql(2)
73
+ @trans.recv[0].should.eql(data)
74
+ @trans.recv[1].should.eql(data)
75
+ end
76
+ end
data/tasks/ann.rake ADDED
@@ -0,0 +1,81 @@
1
+ # $Id$
2
+
3
+ begin
4
+ require 'bones/smtp_tls'
5
+ rescue LoadError
6
+ require 'net/smtp'
7
+ end
8
+ require 'time'
9
+
10
+ namespace :ann do
11
+
12
+ # A prerequisites task that all other tasks depend upon
13
+ task :prereqs
14
+
15
+ file PROJ.ann.file do
16
+ ann = PROJ.ann
17
+ puts "Generating #{ann.file}"
18
+ File.open(ann.file,'w') do |fd|
19
+ fd.puts("#{PROJ.name} version #{PROJ.version}")
20
+ fd.puts(" by #{Array(PROJ.authors).first}") if PROJ.authors
21
+ fd.puts(" #{PROJ.url}") if PROJ.url.valid?
22
+ fd.puts(" (the \"#{PROJ.release_name}\" release)") if PROJ.release_name
23
+ fd.puts
24
+ fd.puts("== DESCRIPTION")
25
+ fd.puts
26
+ fd.puts(PROJ.description)
27
+ fd.puts
28
+ fd.puts(PROJ.changes.sub(%r/^.*$/, '== CHANGES'))
29
+ fd.puts
30
+ ann.paragraphs.each do |p|
31
+ fd.puts "== #{p.upcase}"
32
+ fd.puts
33
+ fd.puts paragraphs_of(PROJ.readme_file, p).join("\n\n")
34
+ fd.puts
35
+ end
36
+ fd.puts ann.text if ann.text
37
+ end
38
+ end
39
+
40
+ desc "Create an announcement file"
41
+ task :announcement => ['ann:prereqs', PROJ.ann.file]
42
+
43
+ desc "Send an email announcement"
44
+ task :email => ['ann:prereqs', PROJ.ann.file] do
45
+ ann = PROJ.ann
46
+ from = ann.email[:from] || PROJ.email
47
+ to = Array(ann.email[:to])
48
+
49
+ ### build a mail header for RFC 822
50
+ rfc822msg = "From: #{from}\n"
51
+ rfc822msg << "To: #{to.join(',')}\n"
52
+ rfc822msg << "Subject: [ANN] #{PROJ.name} #{PROJ.version}"
53
+ rfc822msg << " (#{PROJ.release_name})" if PROJ.release_name
54
+ rfc822msg << "\n"
55
+ rfc822msg << "Date: #{Time.new.rfc822}\n"
56
+ rfc822msg << "Message-Id: "
57
+ rfc822msg << "<#{"%.8f" % Time.now.to_f}@#{ann.email[:domain]}>\n\n"
58
+ rfc822msg << File.read(ann.file)
59
+
60
+ params = [:server, :port, :domain, :acct, :passwd, :authtype].map do |key|
61
+ ann.email[key]
62
+ end
63
+
64
+ params[3] = PROJ.email if params[3].nil?
65
+
66
+ if params[4].nil?
67
+ STDOUT.write "Please enter your e-mail password (#{params[3]}): "
68
+ params[4] = STDIN.gets.chomp
69
+ end
70
+
71
+ ### send email
72
+ Net::SMTP.start(*params) {|smtp| smtp.sendmail(rfc822msg, from, to)}
73
+ end
74
+ end # namespace :ann
75
+
76
+ desc 'Alias to ann:announcement'
77
+ task :ann => 'ann:announcement'
78
+
79
+ CLOBBER << PROJ.ann.file
80
+
81
+ # EOF
@@ -0,0 +1,33 @@
1
+ # Poor mans autotest, for when you absolutely positively, just need an autotest.
2
+ # N.B. Uses a runner under test/ or spec/, so you can customize the runtime.
3
+ # Thanks to manveru for this!
4
+ desc "Run specs every time a file changes in lib or spec"
5
+ task :autospec do
6
+ rb = Gem.ruby rescue nil
7
+ rb ||= (require 'rbconfig'; File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']))
8
+ command = 'spec/runner' if test ?e, 'spec/runner'
9
+ command ||= 'test/runner' if test ?e, 'test/runner'
10
+ files = Dir.glob('{lib,spec,test}/**/*.rb')
11
+ mtimes = {}
12
+ sigtrap = proc { puts "\rDo that again, I dare you!"; trap(:INT){ exit 0 }; sleep 0.8; trap(:INT, &sigtrap) }
13
+ trap(:INT, &sigtrap)
14
+ system "#{rb} -I#{GSpec.require_path} #{command}"
15
+ while file = files.shift
16
+ begin
17
+ mtime = File.mtime(file)
18
+ mtimes[file] ||= mtime
19
+ if mtime > mtimes[file]
20
+ files = Dir.glob('{lib,spec,test}/**/*.rb') - [file] # refresh the file list.
21
+ puts
22
+ system "#{rb} -I#{GSpec.require_path} #{command} #{file}"
23
+ puts
24
+ end
25
+ mtimes[file] = mtime
26
+ files << file
27
+ rescue Exception
28
+ retry
29
+ end
30
+ # print "\rChecking: #{file.ljust((ENV['COLUMNS']||80)-11)}";$stdout.flush
31
+ sleep 0.2
32
+ end
33
+ end