raccdoc 0.0.10

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.
@@ -0,0 +1,5 @@
1
+ tmp/
2
+ coverage/
3
+ doc/
4
+ pkg/
5
+ *.gem
@@ -0,0 +1 @@
1
+ For changes, see the github source at http://github.com/minter/raccdoc/
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 H. Wade Minter
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.
@@ -0,0 +1,14 @@
1
+ CHANGELOG
2
+ lib/raccdoc/connection.rb
3
+ lib/raccdoc/forum.rb
4
+ lib/raccdoc/post.rb
5
+ lib/raccdoc.rb
6
+ LICENSE
7
+ Manifest
8
+ raccdoc.gemspec
9
+ Rakefile
10
+ README.rdoc
11
+ test/raccdoc_connection_test.rb
12
+ test/raccdoc_forum_test.rb
13
+ test/raccdoc_post_test.rb
14
+ test/test_helper.rb
@@ -0,0 +1,92 @@
1
+ = Raccdoc
2
+
3
+ == DESCRIPTION
4
+
5
+ Raccdoc is an API into the data of several classic telnet-based BBS systems, including:
6
+
7
+ * ISCABBS[http://iscabbs.com/]
8
+ * InaraBBS[http://www.inarabbs.com/]
9
+ * Gestalt
10
+
11
+ This is a Ruby version of that API.
12
+
13
+ The Raccdoc API document can be found at http://dev.iscabbs.com/doku.php?id=raccdoc_protocol_documentation
14
+
15
+ == EXAMPLE
16
+
17
+ require 'raccdoc'
18
+
19
+ # Connect to the BBS as user Bugcrusher
20
+ bbs = Raccdoc::Connection.new(:host => 'bbs.iscabbs.com', :port => 6145, :user => 'Bugcrusher', :password => 'MyPass')
21
+
22
+ # Find out information on each forum
23
+ bbs.forums.each do |id, data|
24
+ print "On forum number #{id} named #{data[:name]}\n"
25
+ end
26
+
27
+ # Join the Babble forum
28
+ forum = bbs.jump("Babble")
29
+
30
+ # See if you have permission to post
31
+ print "Can you post in there? #{forum.post?}\n"
32
+
33
+ # Read post number 2280
34
+ post = forum.read(2280)
35
+
36
+ # Print out the post body for 2280
37
+ print "Body is #{post.body}\n"
38
+
39
+ # Make your own post
40
+ mypost = forum.post("Hello world!")
41
+
42
+ # Get information on it
43
+ print "My new post is id number #{mypost.id}\n"
44
+
45
+ # Now delete it
46
+ mypost.delete
47
+
48
+ # Log out
49
+ bbs.logout
50
+
51
+ == REQUIREMENTS
52
+
53
+ * Ruby 1.8
54
+ * An account on the BBS that you wish to access (unless doing Guest/read-only operations)
55
+ * Mocha (for tests)
56
+
57
+ == TODO & KNOWN ISSUES
58
+
59
+ * X-Message support
60
+ * Mail> support
61
+
62
+ == INSTALL
63
+
64
+ * gem sources -a http://gems.rubyforge.org/
65
+ * sudo gem install raccdoc
66
+
67
+ Then require 'raccdoc' in your Ruby program.
68
+
69
+ == LICENSE
70
+
71
+ (The MIT License)
72
+
73
+ Copyright (c) 2009 H. Wade Minter
74
+
75
+ Permission is hereby granted, free of charge, to any person obtaining
76
+ a copy of this software and associated documentation files (the
77
+ 'Software'), to deal in the Software without restriction, including
78
+ without limitation the rights to use, copy, modify, merge, publish,
79
+ distribute, sublicense, and/or sell copies of the Software, and to
80
+ permit persons to whom the Software is furnished to do so, subject to
81
+ the following conditions:
82
+
83
+ The above copyright notice and this permission notice shall be
84
+ included in all copies or substantial portions of the Software.
85
+
86
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
87
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
88
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
89
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
90
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
91
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
92
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ require './lib/raccdoc.rb'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gemspec|
6
+ gemspec.name = "raccdoc"
7
+ gemspec.summary = "Ruby interface into the Raccdoc BBS API"
8
+ gemspec.description = "A Ruby way to speak the Raccdoc protocol"
9
+ gemspec.email = "minter@lunenburg.org"
10
+ gemspec.homepage = "http://github.com/minter/raccdoc"
11
+ gemspec.authors = ["H. Wade Minter"]
12
+ end
13
+ Jeweler::GemcutterTasks.new
14
+ rescue LoadError
15
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
16
+ end
17
+
18
+ namespace :test do
19
+ desc 'Check test coverage'
20
+ task :coverage do
21
+ rm_f "coverage"
22
+ system("rcov -x '/Library/Ruby/Gems/1.8/gems/' --sort coverage #{File.join(File.dirname(__FILE__), 'test/*_test.rb')}")
23
+ system("open #{File.join(File.dirname(__FILE__), 'coverage/index.html')}") if PLATFORM['darwin']
24
+ end
25
+
26
+ desc 'Remove coverage products'
27
+ task :clobber_coverage do
28
+ rm_r 'coverage' rescue nil
29
+ end
30
+
31
+ end
32
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.10
@@ -0,0 +1,21 @@
1
+ module Raccdoc
2
+
3
+ VERSION = '0.0.10'
4
+ require 'socket'
5
+ require 'time'
6
+
7
+ $:.unshift(File.dirname(__FILE__))
8
+ require 'raccdoc/connection'
9
+ require 'raccdoc/forum'
10
+ require 'raccdoc/post'
11
+
12
+ end
13
+
14
+ class InvalidLogin < StandardError
15
+ end
16
+
17
+ class ConnectionError < StandardError
18
+ end
19
+
20
+ class ResponseError < StandardError
21
+ end
@@ -0,0 +1,108 @@
1
+ module Raccdoc
2
+ class Connection
3
+
4
+ attr_reader :host
5
+ attr_reader :post
6
+
7
+ # Creates a new Raccdoc connection. By default, it connects to bbs.iscabbs.com, port 6145, which is the
8
+ # ISCABBS server.
9
+ #
10
+ # If a :user and :password is not supplied, a guest/anonymous login is done. Most, if not all, posting is
11
+ # restricted under this account.
12
+ #
13
+ # If the TCP connection fails, or the login is rejected, and exception is raised.
14
+ def initialize(param_args = {:host => 'bbs.iscabbs.com', :port => '6145', :user => nil, :password => nil})
15
+ args = {:host => 'bbs.iscabbs.com', :port => '6145', :user => nil, :password => nil }
16
+
17
+ args.merge!(param_args)
18
+ begin
19
+ @socket = TCPSocket.new(args[:host],args[:port])
20
+ rescue Errno::ECONNREFUSED
21
+ raise ConnectionError, "Could not connect to #{args[:host]}, port #{args[:port]}\n"
22
+ end
23
+
24
+ begin
25
+ response = @socket.readline.chomp
26
+ rescue EOFError
27
+ raise ConnectionError, "Got an unexpected EOF from the remote end"
28
+ end
29
+
30
+ unless response.match(/^2/)
31
+ raise ConnectionError, response
32
+ end
33
+
34
+ if (args[:user] && args[:password])
35
+ @socket.puts("LOGIN #{args[:user]}\t#{args[:password]}")
36
+ response = @socket.readline.chomp
37
+ unless response.match(/^2/)
38
+ raise InvalidLogin, response
39
+ end
40
+ end
41
+ @host = args[:host]
42
+ @port = args[:port]
43
+ end
44
+
45
+ # Closes the Raccdoc connection, then closes the socket.
46
+ def logout
47
+ unless @socket.closed?
48
+ @socket.puts "QUIT"
49
+
50
+ response = @socket.readline.chomp
51
+ unless response.match(/^2/)
52
+ raise ConnectionError, response
53
+ end
54
+ @socket.close
55
+ end
56
+ return true
57
+ end
58
+
59
+ # Tests the TCP socket to make sure that it's still connected to the remote server.
60
+ def connected?
61
+ @socket.closed? ? false : true
62
+ end
63
+
64
+ # Sets the active forum on the server to the specified forum name or ID, and returns a new Raccdoc::Forum object.
65
+ #
66
+ # The forum can be specified by number (0), or name ("Lobby" or "Program")
67
+ def jump(forum = 0)
68
+ Raccdoc::Forum.new(@socket, forum.to_s)
69
+ end
70
+
71
+ # Returns a hash of forum information. The key is the forum ID number, and the value is a hash containing the following data:
72
+ #
73
+ # * name - The forum name
74
+ # * lastnote - The earliest post ID number in the forum
75
+ # * admin - The handle of the forum admin
76
+ #
77
+ # The argument can be one of: ALL (default), PUBLIC, PRIVATE, TODO, JOINED, NAMED, THREADS
78
+ # depending on server support
79
+ #
80
+ # Note that this does not return actual Raccdoc::Forum objects in the interest of saving resources, so you'll need to jump to the forum that you want.
81
+ def forums(type="all")
82
+ @socket.puts "LIST #{type}"
83
+ forums = Hash.new
84
+
85
+ response = @socket.readline.chomp
86
+ unless response.match(/^3/)
87
+ raise ResponseError, response
88
+ end
89
+
90
+ while line = @socket.readline.chomp
91
+ break if line.match(/^\.$/)
92
+ tmp = Hash.new
93
+ line.split(/\t/).each do |pair|
94
+ (key, value) = pair.split(/:/)
95
+ if key == "admin"
96
+ value = value.split('/')[1]
97
+ end
98
+ tmp[key.to_sym] = value
99
+ end
100
+ id = tmp[:topic].to_i
101
+ forums[id] = tmp
102
+ end
103
+
104
+ return forums
105
+ end
106
+
107
+ end
108
+ end
@@ -0,0 +1,204 @@
1
+ module Raccdoc
2
+ class Forum
3
+
4
+ attr_reader :id
5
+ attr_reader :name
6
+ attr_reader :admin
7
+ attr_reader :anonymous
8
+ attr_reader :private
9
+
10
+ # Sets the active forum on the server to the specified forum number (0) or name (Lobby, Program), then returns a new Raccdoc::Forum object.
11
+ #
12
+ # There are five instance variables exposed through attr_reader:
13
+ #
14
+ # * id - The forum ID number
15
+ # * name - The forum name
16
+ # * admin - The name of the forum administrator
17
+ # * anonymous - either true, false, or force, depending on whether the anonymous posting option is on, off, or required
18
+ # * private - True if the forum is private (invite-only), false otherwise
19
+ def initialize(socket, forum = 0)
20
+ @socket = socket
21
+ @socket.puts "TOPIC #{forum.to_s}"
22
+ response = @socket.readline.chomp
23
+ unless response.match(/^2/)
24
+ raise ResponseError, response
25
+ end
26
+
27
+ @headers = {}
28
+ tuples = response.split(/\t/)
29
+ tuples.delete_at(0)
30
+ tuples.each do |pair|
31
+ (key, value) = pair.split(/:/)
32
+ @headers[key.downcase.to_sym] = value
33
+ end
34
+
35
+ flags = @headers[:flags].split(',')
36
+
37
+ if flags.include?("forceanonymous")
38
+ @anonymous = "force"
39
+ elsif flags.include?("cananonymous")
40
+ @anonymous = true
41
+ else
42
+ @anonymous = false
43
+ end
44
+
45
+ @private = flags.include?("private") ? true : false
46
+
47
+ @id = @headers[:topic]
48
+ @name = @headers[:name]
49
+ @admin = @headers[:admin].split('/')[1]
50
+ end
51
+
52
+ # Returns an array of all current post IDs in the forum.
53
+ #
54
+ # Takes an optional range string of the form oldest_noteid-newest_noteid. The oldest or newest can be omitted as needed.
55
+ def noteids(range = "")
56
+ noteids = Array.new
57
+ @socket.puts("XHDR noteno #{range.to_s}")
58
+ response = @socket.readline.chomp
59
+ unless response.match(/^3/)
60
+ raise ResponseError, response
61
+ end
62
+
63
+ while line = @socket.readline.chomp
64
+ break if line.match(/^\.$/)
65
+ (tag,noteid) = line.split(/:/)
66
+ noteids.push(noteid.to_i)
67
+ end
68
+
69
+ return noteids
70
+ end
71
+
72
+ # Returns a hash of the forum information. Keys are:
73
+ #
74
+ # * from - The username of the user who last updated the FI
75
+ # * date - The date the forum information was last updated (a Time object)
76
+ # * body - The actual text of the FI
77
+ def forum_information
78
+ fi = Hash.new
79
+ @socket.puts("SHOW info")
80
+ response = @socket.readline.chomp
81
+ unless response.match(/^3/)
82
+ raise ResponseError, response
83
+ end
84
+
85
+ # Get header information
86
+ while line = @socket.readline.chomp
87
+ break if line.match(/^$/)
88
+ (key, value) = line.split(/: /)
89
+ if key.downcase == "date"
90
+ value = Time.parse(value)
91
+ end
92
+ fi[key.downcase.to_sym] = value
93
+ end
94
+
95
+ body = ""
96
+ while line = @socket.readline
97
+ break if line.match(/^\.$/)
98
+ body << line
99
+ end
100
+ fi[:body] = body
101
+
102
+ return fi
103
+ end
104
+
105
+ # Sets the current user's first-unread pointer to the specified number. Any posts greater than that number will be listed as unread.
106
+ def first_unread=(postid = 0)
107
+ @socket.puts("SETRC #{postid.to_s}")
108
+ response = @socket.readline.chomp
109
+ unless response.match(/^2/)
110
+ raise ResponseError, response
111
+ end
112
+ end
113
+
114
+ # Returns the post ID number of the last read post in the forum. Any posts greater than that number can be considered unread.
115
+ def first_unread
116
+ @socket.puts("SHOW rcval")
117
+ response = @socket.readline.chomp
118
+ unless response.match(/^2/)
119
+ raise ResponseError, response
120
+ end
121
+ response.match(/\d+.*?:\s+(\d+)/)
122
+ return $1
123
+ end
124
+
125
+ # Returns a hash of information about the posts in the forum. The hash is keyed off of the post ID, and the value is a hash containing the following information:
126
+ #
127
+ # * author - The username of the user who created the post
128
+ # * date - The date the post was created or, if the post is anonymous, the current date in UTC
129
+ # * subject - The first line of the post
130
+ # * size - The size of the post in bytes
131
+ # * authority - If the post was made with Sysop or Forum Manager status, this is set
132
+ #
133
+ # You may provide an optional range (in the form "start_id-end_id") to limit the number of posts returned. The default is to return all posts in the forum.
134
+ def post_headers(range = "")
135
+ @socket.puts("XHDR ALL #{range.to_s}")
136
+ response = @socket.readline.chomp
137
+ unless response.match(/^3/)
138
+ raise ResponseError, response
139
+ end
140
+
141
+ posts = Hash.new
142
+
143
+ while (line = @socket.readline.chomp)
144
+ break if line.match(/^\.$/)
145
+ tmpdata = Hash.new
146
+ line.split(/\t/).each do |tuple|
147
+ (key, value) = tuple.split(/:/, 2)
148
+ tmpdata[key.downcase.to_sym] = value
149
+ end
150
+ tmpdata[:date] = tmpdata[:date] ? Time.parse(tmpdata[:date]) : Time.new.getgm
151
+ tmpdata[:author] = tmpdata[:"formal-author"] ? tmpdata[:"formal-author"].split('/')[1] : 'Anonymous'
152
+ posts[tmpdata[:noteno]] = tmpdata
153
+ end
154
+ return posts
155
+ end
156
+
157
+ # Returns a new Raccdoc::Post object for the specified post ID in the current forum.
158
+ def read(postid)
159
+ Post.new(@socket,postid)
160
+ end
161
+
162
+ # Attempts to do a "DAMMIT" read, overriding anonymous and deleted flags. If you do not have permission to bypass those flags, this is equivalent to a plain read.
163
+ def read!(postid)
164
+ Post.new(@socket,postid,:dammit => true)
165
+ end
166
+
167
+ # Creates a new post in the current forum using the text provided in the argument.
168
+ #
169
+ # Returns a new Raccdoc::Post object that results from reading the newly created post.
170
+ def post(body)
171
+ @socket.puts("POST")
172
+ response = @socket.readline.chomp
173
+ unless response.match(/^3/)
174
+ raise ResponseError, response
175
+ end
176
+
177
+ @socket.puts(body)
178
+ @socket.puts(".")
179
+
180
+ response = @socket.readline.chomp
181
+ if response.match(/^2/)
182
+ postid = response.split(/:\s+/)[1]
183
+ return Post.new(@socket,postid)
184
+ else
185
+ raise ResponseError, response
186
+ end
187
+ end
188
+
189
+ # Checks to see if the currently logged in user has permission to post in the current forum.
190
+ #
191
+ # Returns true or false.
192
+ def post?
193
+ @socket.puts("OKAY POST")
194
+ response = @socket.readline.chomp
195
+ if response.match(/^2/)
196
+ return true
197
+ elsif response.match(/^4/)
198
+ return false
199
+ else
200
+ raise ResponseError, response
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,83 @@
1
+ module Raccdoc
2
+ class Post
3
+
4
+ attr_reader :id
5
+ attr_reader :date
6
+ attr_reader :author
7
+ attr_reader :body
8
+ attr_reader :authority
9
+
10
+ # Creates a new Raccdoc::Post object by reading the specified post. If no post is given, it returns the first unread post, if any.
11
+ #
12
+ # Useful information returned:
13
+ #
14
+ # * id - The numerical post ID
15
+ # * date - The date the post was made (or the current date in UTC if the post is anonymous)
16
+ # * author - The username of the user that created the post
17
+ # * body - The textual body of the post.
18
+ # * authority - either "Sysop" or "Forum Moderator" if the post was made with that header.
19
+ def initialize(socket,postid,args={})
20
+ @socket = socket
21
+ dammit = args[:dammit] ? 'DAMMIT' : ''
22
+ @socket.puts "READ #{postid.to_s} #{dammit}"
23
+ response = @socket.readline.chomp
24
+ unless response.match(/^3/)
25
+ raise ResponseError, response
26
+ end
27
+
28
+ # Get header information
29
+ post = Hash.new
30
+
31
+ while line = @socket.readline.chomp
32
+ break if line.match(/^$/)
33
+ (key,value) = line.split(/: /)
34
+ post[key.downcase.to_sym] = value
35
+ end
36
+ post[:id] = postid
37
+ post[:date] = post[:date] ? Time.parse(post[:date]) : Time.new.getgm
38
+
39
+ post[:body] = ""
40
+ while (line = @socket.readline)
41
+ break if line.match(/^\.$/)
42
+ post[:body] << line
43
+ end
44
+
45
+ @id = post[:id]
46
+ @date = post[:date]
47
+ @author = post[:from]
48
+ @body = post[:body]
49
+ @authority = post[:authority]
50
+ return post
51
+ end
52
+
53
+ # Checks to see if the currently-logged-in user has permission to delete the current post.
54
+ #
55
+ # Returns true or false
56
+ def delete?
57
+ @socket.puts("OKAY DELETE NOTE #{self.id}")
58
+ response = @socket.readline.chomp
59
+ if response.match(/^2/)
60
+ return true
61
+ elsif response.match(/^4/)
62
+ return false
63
+ else
64
+ raise ResponseError, response
65
+ end
66
+ end
67
+
68
+ # Attempts to delete the current post.
69
+ #
70
+ # Returns true or false
71
+ def delete
72
+ @socket.puts("DELETE NOTE #{self.id}")
73
+ response = @socket.readline.chomp
74
+ if response.match(/^2/)
75
+ return true
76
+ elsif response.match(/^4/)
77
+ return false
78
+ else
79
+ raise ResponseError, response
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,58 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{raccdoc}
8
+ s.version = "0.0.10"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["H. Wade Minter"]
12
+ s.date = %q{2009-10-08}
13
+ s.description = %q{A Ruby way to speak the Raccdoc protocol}
14
+ s.email = %q{minter@lunenburg.org}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "CHANGELOG",
22
+ "LICENSE",
23
+ "Manifest",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "lib/raccdoc.rb",
28
+ "lib/raccdoc/connection.rb",
29
+ "lib/raccdoc/forum.rb",
30
+ "lib/raccdoc/post.rb",
31
+ "raccdoc.gemspec",
32
+ "test/raccdoc_connection_test.rb",
33
+ "test/raccdoc_forum_test.rb",
34
+ "test/raccdoc_post_test.rb",
35
+ "test/test_helper.rb"
36
+ ]
37
+ s.homepage = %q{http://github.com/minter/raccdoc}
38
+ s.rdoc_options = ["--charset=UTF-8"]
39
+ s.require_paths = ["lib"]
40
+ s.rubygems_version = %q{1.3.5}
41
+ s.summary = %q{Ruby interface into the Raccdoc BBS API}
42
+ s.test_files = [
43
+ "test/raccdoc_connection_test.rb",
44
+ "test/raccdoc_forum_test.rb",
45
+ "test/raccdoc_post_test.rb",
46
+ "test/test_helper.rb"
47
+ ]
48
+
49
+ if s.respond_to? :specification_version then
50
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
51
+ s.specification_version = 3
52
+
53
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
54
+ else
55
+ end
56
+ else
57
+ end
58
+ end
@@ -0,0 +1,89 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class RaccdocConnectionTest < Test::Unit::TestCase
4
+ def setup
5
+ @socket = mock()
6
+ @socket.stubs(:puts)
7
+ @socket.expects(:readline).returns("200 Server started seed:Ci0B2YeMio8ftYjElTa+EQ==")
8
+ TCPSocket.expects(:new).returns(@socket)
9
+ @bbs = Raccdoc::Connection.new(:host => 'test.bbs.example')
10
+ end
11
+
12
+ def test_valid_anonymous_connection
13
+ assert_equal @bbs.class, Raccdoc::Connection
14
+ assert_equal @bbs.host, "test.bbs.example"
15
+ end
16
+
17
+ def test_connection_refused_anonymous
18
+ TCPSocket.expects(:new).with('test.bbs.example', '6145').raises(Errno::ECONNREFUSED)
19
+ assert_raise(ConnectionError, 'Could not connect to test.bbs.example, port 6145') do
20
+ bbs = Raccdoc::Connection.new(:host => 'test.bbs.example')
21
+ end
22
+ end
23
+
24
+ def test_connection_bad_login
25
+ socket = mock()
26
+ socket.stubs(:readline).returns("200 Server started seed:Ci0B2YeMio8ftYjElTa+EQ==").then.returns("405 Cannot process login Invalid password")
27
+ socket.stubs(:puts)
28
+ TCPSocket.expects(:new).returns(socket)
29
+ assert_raise(InvalidLogin, '404 User does not exist') do
30
+ bbs = Raccdoc::Connection.new(:host => 'test.bbs.example', :user => 'Bugcrusher', :password => 'BadPassword')
31
+ end
32
+ end
33
+
34
+ def test_connection_that_returns_unknown_status
35
+ socket = mock()
36
+ TCPSocket.expects(:new).returns(socket)
37
+ socket.expects(:readline).returns("999 Stuff is messed up")
38
+ assert_raise(ConnectionError) do
39
+ bbs = Raccdoc::Connection.new(:host => 'test.bbs.example')
40
+ end
41
+ end
42
+
43
+ def test_logout_succeeds
44
+ @socket.stubs(:close)
45
+ @socket.stubs(:closed?).returns(false)
46
+ @socket.stubs(:readline).returns("201 Session ended")
47
+ result = @bbs.logout
48
+ assert_equal result, true
49
+ end
50
+
51
+ def test_logout_fails
52
+ @socket.stubs(:close)
53
+ @socket.stubs(:closed?).returns(false)
54
+ @socket.stubs(:readline).returns("999 Something Broke")
55
+ assert_raise(ConnectionError) do
56
+ @bbs.logout
57
+ end
58
+ end
59
+
60
+ def test_connected
61
+ @socket.stubs(:puts)
62
+ @socket.stubs(:closed?).returns(false)
63
+ @socket.stubs(:readline).returns("999 Something Broke")
64
+ assert_equal @bbs.connected?, true
65
+ end
66
+
67
+ def test_forums_success
68
+ @socket.stubs(:readline).returns("301 Topic list follows").then.returns("topic:0 name:Lobby lastnote:2331 flags:nosubject,sparse admin:acct578247-oldisca/Elvis/(hidden)").then.returns(".")
69
+ forums = @bbs.forums
70
+ assert_equal forums.class, Hash
71
+ assert_equal forums.size, 1
72
+ assert_equal forums[0][:name], "Lobby"
73
+ end
74
+
75
+ def test_forums_failure
76
+ @socket.stubs(:readline).returns("999 Stuff Broke")
77
+ assert_raise(ResponseError) do
78
+ forums = @bbs.forums
79
+ end
80
+ end
81
+
82
+ def test_jump
83
+ @forum = mock()
84
+ @forum.expects(:name).returns("Lobby")
85
+ Raccdoc::Forum.expects(:new).returns(@forum)
86
+ forum = @bbs.jump(0)
87
+ assert_equal forum.name, "Lobby"
88
+ end
89
+ end
@@ -0,0 +1,167 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class RaccdocForumTest < Test::Unit::TestCase
4
+ def setup
5
+ @socket = mock()
6
+ @socket.stubs(:puts)
7
+ @socket.stubs(:readline).returns("204 Topic set to: topic:0 name:Lobby lastnote:2331 flags:nosubject,sparse admin:acct578247-oldisca/Elvis/(hidden) firstnote:2275")
8
+ @forum = Raccdoc::Forum.new(@socket, 0)
9
+ end
10
+
11
+ def test_creation
12
+ assert_equal @forum.class, Raccdoc::Forum
13
+ assert_equal @forum.name, "Lobby"
14
+ assert_equal @forum.admin, "Elvis"
15
+ assert_equal @forum.id, "0"
16
+ assert_equal @forum.anonymous, false
17
+ assert_equal @forum.private, false
18
+ end
19
+
20
+ def test_creation_fail
21
+ socket = mock()
22
+ socket.stubs(:puts)
23
+ socket.stubs(:readline).returns('999 Stuff broke')
24
+ assert_raise(ResponseError) do
25
+ forum = Raccdoc::Forum.new(socket,0)
26
+ end
27
+ end
28
+
29
+ def test_anonymous_allowed
30
+ socket = mock()
31
+ socket.stubs(:puts)
32
+ socket.stubs(:readline).returns("204 Topic set to: topic:173 name:Kama Sutra lastnote:57263 flags:nosubject,sparse,private,cananonymous admin:acct30177-oldisca/Kubla Khan/(hidden) firstnote:57111")
33
+ forum = Raccdoc::Forum.new(socket, 173)
34
+ assert_equal forum.anonymous, true
35
+ end
36
+
37
+ def test_anonymous_forced
38
+ socket = mock()
39
+ socket.stubs(:puts)
40
+ socket.stubs(:readline).returns("204 Topic set to: topic:7 name:Weird Soup lastnote:864957 flags:nosubject,sparse,forceanonymous admin:acct576222-oldisca/Weird FM/(hidden) firstnote:864808")
41
+ forum = Raccdoc::Forum.new(socket,7)
42
+ assert_equal forum.anonymous, "force"
43
+ end
44
+
45
+ def test_noteids
46
+ @socket.stubs(:readline).returns("306 Note headers follow").then.returns("noteno:2275").then.returns("noteno:2280").then.returns(".")
47
+ notes = @forum.noteids
48
+ assert_equal notes.class, Array
49
+ assert_equal notes.size, 2
50
+ end
51
+
52
+ def test_noteids_failure
53
+ @socket.stubs(:readline).returns("999 Stuff Broke")
54
+ assert_raise(ResponseError) do
55
+ notes = @forum.noteids
56
+ end
57
+ end
58
+
59
+ def test_forum_information
60
+ @socket.stubs(:readline).returns("303 Topic info follows").then.returns("From: Adonis").then.returns("Date: Fri, 30 May 2003 02:05:00 GMT").then.returns("").then.returns("This forum is where the Sysops post administrative announcements...").then.returns(".")
61
+ fi = @forum.forum_information
62
+ assert_equal fi[:from], "Adonis"
63
+ end
64
+
65
+ def test_forum_information_failure
66
+ @socket.stubs(:readline).returns("999 Stuff broke")
67
+ assert_raise(ResponseError) do
68
+ fi = @forum.forum_information
69
+ end
70
+ end
71
+
72
+ def test_set_first_unread
73
+ @socket.stubs(:readline).returns('206 Value set')
74
+ @forum.first_unread=2139
75
+ end
76
+
77
+ def test_set_first_unread_fails
78
+ @socket.stubs(:readline).returns('999 Stuff Broke')
79
+ assert_raises(ResponseError) do
80
+ @forum.first_unread=99999
81
+ end
82
+ end
83
+
84
+ def test_first_unread
85
+ @socket.stubs(:readline).returns("207 RC Value is: 2281")
86
+ first = @forum.first_unread
87
+ assert_equal first, "2281"
88
+ end
89
+
90
+ def test_first_unread_fails
91
+ @socket.stubs(:readline).returns("999 Stuff Broke")
92
+ assert_raises(ResponseError) do
93
+ first = @forum.first_unread
94
+ end
95
+ end
96
+
97
+ def test_post_headers
98
+ @socket.stubs(:readline).returns("306 Note headers follow").then.returns("noteno:2275 formal-author:acct89144-oldisca/Devil Lady/(hidden) date:Sat, 16 Jun 2007 00:11:00 GMT subject:Have you ever wanted to give a little back? ISCA is size:670").then.returns("noteno:2280 formal-author:acct493910-oldisca/Bleeding Me/(hidden) date:Sat, 13 Oct 2007 18:28:00 GMT subject:Do you have an idea for a permanent forum but aren't sure size:465").then.returns(".")
99
+ headers = @forum.post_headers
100
+ assert_equal headers.class, Hash
101
+ assert_equal headers.size, 2
102
+ post = headers['2275']
103
+ assert_equal post.class, Hash
104
+ assert_equal post[:author], 'Devil Lady'
105
+ assert_equal post[:size], '670'
106
+ assert_equal post[:noteno], '2275'
107
+ end
108
+
109
+ def test_post_headers_fail
110
+ @socket.stubs(:readline).returns("999 Stuff broke")
111
+ assert_raise(ResponseError) do
112
+ headers = @forum.post_headers
113
+ end
114
+ end
115
+
116
+ def test_read
117
+ @socket.stubs(:readline).returns("302 Note body follows noteno:71493 size:66").then.returns("From: Lunenburg").then.returns("Formal-Name: acct123507-oldisca/Lunenburg/(hidden)").then.returns("Date: Wed, 06 Jun 2007 01:02:00 GMT").then.returns("").then.returns("Testing to see if posting via the ISCAweb interface still works.").then.returns(".")
118
+ post = @forum.read("9999")
119
+ assert_equal post.class, Raccdoc::Post
120
+ end
121
+
122
+ def test_read_dammit
123
+ @socket.stubs(:readline).returns("302 Note body follows noteno:71493 size:66").then.returns("From: Lunenburg").then.returns("Formal-Name: acct123507-oldisca/Lunenburg/(hidden)").then.returns("Date: Wed, 06 Jun 2007 01:02:00 GMT").then.returns("").then.returns("Testing to see if posting via the ISCAweb interface still works.").then.returns(".")
124
+ post = @forum.read!("9999")
125
+ assert_equal post.class, Raccdoc::Post
126
+ end
127
+
128
+ def test_create_post
129
+ @socket.stubs(:readline).returns('350 Send note body maxsize:50000').then.returns('205 Note posted as note: 71508').then.returns("302 Note body follows noteno:71493 size:66").then.returns("From: Lunenburg").then.returns("Formal-Name: acct123507-oldisca/Lunenburg/(hidden)").then.returns("Date: Wed, 06 Jun 2007 01:02:00 GMT").then.returns("").then.returns("This is a test").then.returns(".")
130
+ post = @forum.post("This is a test")
131
+ assert_equal post.class, Raccdoc::Post
132
+ assert_equal post.body, "This is a test"
133
+ end
134
+
135
+ def test_create_post_early_failure
136
+ @socket.stubs(:readline).returns('999 Stuff broke')
137
+ assert_raise(ResponseError) do
138
+ post = @forum.post("This is a test")
139
+ end
140
+ end
141
+
142
+ def test_create_post_late_failure
143
+ @socket.stubs(:readline).returns('350 Send note body maxsize:50000').then.returns('999 Stuff broke')
144
+ assert_raise(ResponseError) do
145
+ post = @forum.post("This is a test")
146
+ end
147
+ end
148
+
149
+ def test_okay_post
150
+ @socket.stubs(:readline).returns("211 Request okay")
151
+ assert_equal @forum.post?, true
152
+ end
153
+
154
+ def test_okay_post_denied
155
+ @socket.stubs(:readline).returns("408 Permission denied")
156
+ assert_equal @forum.post?, false
157
+ end
158
+
159
+ def test_okay_post_fail
160
+ @socket.stubs(:readline).returns("999 Stuff broke")
161
+ assert_raise(ResponseError) do
162
+ @forum.post?
163
+ end
164
+ end
165
+
166
+
167
+ end
@@ -0,0 +1,61 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class RaccdocPostTest < Test::Unit::TestCase
4
+ def setup
5
+ @socket = mock()
6
+ @socket.stubs(:puts)
7
+ @socket.stubs(:readline).returns("302 Note body follows noteno:71493 size:66").then.returns("From: Lunenburg").then.returns("Formal-Name: acct123507-oldisca/Lunenburg/(hidden)").then.returns("Date: Wed, 06 Jun 2007 01:02:00 GMT").then.returns("").then.returns("Testing to see if posting via the ISCAweb interface still works.").then.returns(".")
8
+ @post = Raccdoc::Post.new(@socket, 9999)
9
+ end
10
+
11
+ def test_successful_post_object_creation
12
+ assert_equal @post.class, Raccdoc::Post
13
+ end
14
+
15
+ def test_post_object_creation_failure
16
+ @socket.stubs(:readline).returns("410 No Such Note")
17
+ assert_raise(ResponseError) do
18
+ @post = Raccdoc::Post.new(@socket, 9999)
19
+ end
20
+ end
21
+
22
+ def test_delete_post
23
+ @socket.stubs(:readline).returns("208 Note deleted")
24
+ status = @post.delete
25
+ assert_equal status, true
26
+ end
27
+
28
+ def test_delete_failure
29
+ @socket.stubs(:readline).returns("408 Permission denied")
30
+ status = @post.delete
31
+ assert_equal status, false
32
+ end
33
+
34
+ def test_delete_exception
35
+ @socket.stubs(:readline).returns("999 Stuff Broke")
36
+ assert_raise(ResponseError) do
37
+ status = @post.delete
38
+ end
39
+ end
40
+
41
+ def test_delete_permissions
42
+ @socket.stubs(:readline).returns("211 Request okay")
43
+ status = @post.delete?
44
+ assert_equal status, true
45
+ end
46
+
47
+ def test_delete_permissions_failure
48
+ @socket.stubs(:readline).returns("408 Permission denied")
49
+ status = @post.delete?
50
+ assert_equal status, false
51
+ end
52
+
53
+ def test_delete_permissions_exception
54
+ @socket.stubs(:readline).returns("999 Stuff broke")
55
+ assert_raise(ResponseError) do
56
+ status = @post.delete?
57
+ end
58
+ end
59
+
60
+
61
+ end
@@ -0,0 +1,5 @@
1
+ require 'test/unit'
2
+ $:.unshift File.dirname(__FILE__) + '/../lib'
3
+ require 'raccdoc'
4
+ require 'rubygems'
5
+ require 'mocha'
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: raccdoc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.10
5
+ platform: ruby
6
+ authors:
7
+ - H. Wade Minter
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-08 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A Ruby way to speak the Raccdoc protocol
17
+ email: minter@lunenburg.org
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.rdoc
25
+ files:
26
+ - .gitignore
27
+ - CHANGELOG
28
+ - LICENSE
29
+ - Manifest
30
+ - README.rdoc
31
+ - Rakefile
32
+ - VERSION
33
+ - lib/raccdoc.rb
34
+ - lib/raccdoc/connection.rb
35
+ - lib/raccdoc/forum.rb
36
+ - lib/raccdoc/post.rb
37
+ - raccdoc.gemspec
38
+ - test/raccdoc_connection_test.rb
39
+ - test/raccdoc_forum_test.rb
40
+ - test/raccdoc_post_test.rb
41
+ - test/test_helper.rb
42
+ has_rdoc: true
43
+ homepage: http://github.com/minter/raccdoc
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options:
48
+ - --charset=UTF-8
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.3.5
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Ruby interface into the Raccdoc BBS API
70
+ test_files:
71
+ - test/raccdoc_connection_test.rb
72
+ - test/raccdoc_forum_test.rb
73
+ - test/raccdoc_post_test.rb
74
+ - test/test_helper.rb