em-irc 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -15,4 +15,5 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
- .DS_Store
18
+ .DS_Store
19
+ .rbx
data/.rvmrc CHANGED
@@ -1 +1 @@
1
- rvm use 1.9.3
1
+ rvm use 1.9.3@em-irc
@@ -1,6 +1,10 @@
1
1
  rvm:
2
- - 1.9.2
3
2
  - 1.9.3
3
+ - 1.9.2
4
+ - 1.8.7
5
+ - ree
6
+ - rbx
7
+ - ruby-head
4
8
  bundler_args: --without darwin --without development
5
9
  gemfile:
6
10
  - Gemfile
@@ -9,4 +13,4 @@ before_script:
9
13
  - "service ngircd stop"
10
14
  - "ngircd -f spec/config/ngircd-unencrypted.conf"
11
15
  - "ngircd -f spec/config/ngircd-encrypted-gnutls.conf"
12
- script: "bundle exec rake"
16
+ script: "bundle exec rake spec"
data/Gemfile CHANGED
@@ -4,7 +4,6 @@ source 'http://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  group :development do
7
- gem 'pry'
8
7
  gem 'yard'
9
8
  gem 'redcarpet'
10
9
  gem 'guard'
@@ -13,6 +12,7 @@ group :development do
13
12
  end
14
13
 
15
14
  group :development, :test do
15
+ gem 'pry'
16
16
  gem 'rake'
17
17
  gem 'rspec', "~> 2"
18
18
  end
@@ -20,4 +20,8 @@ end
20
20
  group :darwin do
21
21
  gem 'rb-fsevent'
22
22
  gem 'growl'
23
+ end
24
+
25
+ platform :ruby_19 do
26
+ gem 'guard-yard', :group => :development
23
27
  end
data/Guardfile CHANGED
@@ -5,9 +5,16 @@ guard 'bundler' do
5
5
  watch('Gemfile')
6
6
  end
7
7
 
8
- guard 'rspec', :version => 2 do
8
+ guard 'rspec', :version => 2, :cli => '--tag ~integration' do
9
9
  watch(%r{^spec/.+_spec\.rb$})
10
10
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
11
11
  watch('lib/em-irc.rb') { "spec" }
12
12
  watch('spec/spec_helper.rb') { "spec" }
13
+ end
14
+
15
+ begin
16
+ guard 'yard', :stdout => '/dev/null' do
17
+ watch(%r{lib/.+\.rb})
18
+ end
19
+ rescue => e
13
20
  end
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # EventMachine IRC Client
2
2
 
3
- [CI Build Status](https://secure.travis-ci.org/jch/em-irc.png?branch=master)
3
+ [![Build Status](https://secure.travis-ci.org/jch/em-irc.png?branch=master)](http://travis-ci.org/jch/em-irc)
4
4
 
5
5
  em-irc is an IRC client that uses EventMachine to handle connections to servers.
6
6
 
@@ -9,26 +9,29 @@ em-irc is an IRC client that uses EventMachine to handle connections to servers.
9
9
  ````ruby
10
10
  require 'em-irc'
11
11
 
12
- client = EventMachine::IRC::Client.new do |c|
13
- c.host = 'irc.freenode.net'
14
- c.port = '6667'
15
- c.nick = 'jch'
12
+ client = EventMachine::IRC::Client.new do
13
+ host 'irc.freenode.net'
14
+ port '6667'
15
+
16
+ on(:connect) do
17
+ nick('jch')
18
+ end
16
19
 
17
- c.on(:connect) do
20
+ on(:nick) do
18
21
  join('#general')
19
22
  join('#private', 'key')
20
23
  end
21
24
 
22
- c.on(:join) do |channel| # called after joining a channel
25
+ on(:join) do |channel| # called after joining a channel
23
26
  message(channel, "howdy all")
24
27
  end
25
28
 
26
- c.on(:message) do |source, target, message| # called when being messaged
29
+ on(:message) do |source, target, message| # called when being messaged
27
30
  puts "<#{source}> -> <#{target}>: #{message}"
28
31
  end
29
32
 
30
33
  # callback for all messages sent from IRC server
31
- c.on(:raw) do |hash|
34
+ on(:raw) do |hash|
32
35
  puts "#{hash[:prefix]} #{hash[:command]} #{hash[:params].join(' ')}"
33
36
  end
34
37
  end
@@ -36,21 +39,34 @@ end
36
39
  client.run! # start EventMachine loop
37
40
  ````
38
41
 
39
- ## Examples
40
-
41
- In the examples folder, there are runnable examples.
42
+ Alternatively, if local variable access is needed, the first block variable is
43
+ the client:
42
44
 
43
- * cli.rb - takes input from keyboard, outputs to stdout
44
- * websocket.rb -
45
- * echo.rb - bot that echos everything
46
- * callback.rb - demonstrate how callbacks work
45
+ ````ruby
46
+ client = EventMachine::IRC::Client.new do |c|
47
+ # c is the client instance
48
+ end
49
+ ````
47
50
 
48
51
  ## References
49
52
 
53
+ * [API Documentation](http://rubydoc.info/gems/em-irc/0.0.1/frames)
50
54
  * [RFC 1459 - Internet Relay Chat Protocol](http://tools.ietf.org/html/rfc1459) overview of IRC architecture
51
55
  * [RFC 2812 - Internet Relay Chat: Client Protocol](http://tools.ietf.org/html/rfc2812) specifics of client protocol
52
56
  * [RFC 2813 - Internet Relay Chat: Server Protocol](http://tools.ietf.org/html/rfc2813) specifics of server protocol
53
57
 
58
+ ## Platforms
59
+
60
+ The following platforms are tentatively supported:
61
+
62
+ * 1.8.7
63
+ * 1.9.2
64
+ * 1.9.3
65
+ * ree
66
+ * Rubinius
67
+
68
+ I currently develop ruby 1.9.3, so it'll be the VM that gets the most support.
69
+
54
70
  ## Development
55
71
 
56
72
  To run integration specs, you'll need to run a ssl and a non-ssl irc server locally.
@@ -89,12 +105,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
89
105
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
90
106
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
91
107
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
92
- THE SOFTWARE.
93
-
94
- ## TODO
95
-
96
- * can we skip using Dispatcher connection handler class?
97
- * extract :on, :trigger callback gem that works on instances. [hook](https://github.com/apotonick/hooks), but works with instances
98
- * would prefer the interface to look synchronous, but work async
99
- * ssl dispatcher testing
100
- * speed up integration specs
108
+ THE SOFTWARE.
data/Rakefile CHANGED
@@ -2,5 +2,13 @@
2
2
  require "bundler/gem_tasks"
3
3
  Bundler::GemHelper.install_tasks
4
4
  require 'rspec/core/rake_task'
5
- RSpec::Core::RakeTask.new(:spec)
5
+
6
+ RSpec::Core::RakeTask.new('spec') do |t|
7
+ t.rspec_opts = '--tag ~integration'
8
+ end
9
+
10
+ RSpec::Core::RakeTask.new('spec:ci') do |t|
11
+ t.pattern = 'spec/integration/*_spec.rb'
12
+ end
13
+
6
14
  task :default => :spec
@@ -4,6 +4,9 @@ Bundler.setup :default
4
4
  require 'active_support/core_ext/hash/keys'
5
5
  require 'active_support/core_ext/object/blank'
6
6
  require 'active_support/callbacks'
7
+ require 'active_support/deprecation'
8
+ require 'active_support/concern'
9
+ require 'active_support/core_ext/array/extract_options'
7
10
  require 'eventmachine'
8
11
  require 'forwardable'
9
12
  require 'set'
@@ -14,5 +17,7 @@ module EventMachine
14
17
  module IRC
15
18
  autoload :Client, 'em-irc/client'
16
19
  autoload :Dispatcher, 'em-irc/dispatcher'
20
+ autoload :Commands, 'em-irc/commands'
21
+ autoload :Responses, 'em-irc/responses'
17
22
  end
18
23
  end
@@ -1,22 +1,35 @@
1
+ require 'support/dsl_accessor'
2
+
1
3
  module EventMachine
2
4
  module IRC
3
5
  class Client
6
+ include DslAccessor
7
+ include IRC::Commands
8
+ include IRC::Responses
9
+
4
10
  # EventMachine::Connection object to IRC server
5
11
  # @private
6
12
  attr_accessor :conn
7
13
 
8
- # IRC server to connect to. Defaults to 127.0.0.1:6667
9
- attr_accessor :host, :port
14
+ # @macro dsl_accessor
15
+ # Accessor for `$1`
16
+ # Defaults to '127.0.0.1'
17
+ dsl_accessor :host
18
+
19
+ # @macro dsl_accessor
20
+ # Defaults to '6667'
21
+ dsl_accessor :port
22
+
23
+ # @macro dsl_accessor
24
+ dsl_accessor :realname
10
25
 
11
- attr_accessor :nick
12
- attr_accessor :realname
13
- attr_accessor :ssl
26
+ # @macro dsl_accessor
27
+ dsl_accessor :ssl
14
28
 
15
- # Custom logger
16
- attr_accessor :logger
29
+ # @macro dsl_accessor
30
+ dsl_accessor :logger
17
31
 
18
32
  # Set of channels that this client is connected to
19
- # @private
20
33
  attr_reader :channels
21
34
 
22
35
  # Hash of callbacks on events. key is symbol event name.
@@ -30,29 +43,33 @@ module EventMachine
30
43
  # @option options [String] :host
31
44
  # @option options [String] :port
32
45
  # @option options [Boolean] :ssl
33
- # @option options [String] :nick
34
46
  # @option options [String] :realname
35
47
  #
36
48
  # @yield [client] new instance for decoration
37
49
  def initialize(options = {}, &blk)
38
50
  options.symbolize_keys!
39
51
  options = {
40
- host: '127.0.0.1',
41
- port: '6667',
42
- ssl: false,
43
- realname: 'Anonymous Annie',
44
- nick: "guest-#{Time.now.to_i % 1000}"
52
+ :host => '127.0.0.1',
53
+ :port => '6667',
54
+ :ssl => false,
55
+ :realname => 'Anonymous Annie'
45
56
  }.merge!(options)
46
57
 
47
58
  @host = options[:host]
48
59
  @port = options[:port]
49
60
  @ssl = options[:ssl]
50
61
  @realname = options[:realname]
51
- @nick = options[:nick]
52
62
  @channels = Set.new
53
63
  @callbacks = Hash.new
54
64
  @connected = false
55
- yield self if block_given?
65
+
66
+ if block_given?
67
+ if blk.arity == 1
68
+ yield self
69
+ else
70
+ instance_eval(&blk)
71
+ end
72
+ end
56
73
  end
57
74
 
58
75
  # Creates a Eventmachine TCP connection with :host and :port. It should be called
@@ -60,7 +77,7 @@ module EventMachine
60
77
  # @see #on
61
78
  # @return [EventMachine::Connection]
62
79
  def connect
63
- self.conn ||= EventMachine::connect(@host, @port, Dispatcher, parent: self)
80
+ self.conn ||= EventMachine::connect(@host, @port, Dispatcher, :parent => self, :ssl => @ssl)
64
81
  end
65
82
 
66
83
  # @return [Boolean]
@@ -68,9 +85,22 @@ module EventMachine
68
85
  @connected
69
86
  end
70
87
 
71
- # Callbacks
88
+ # Start running the client
89
+ def run!
90
+ EM.epoll
91
+ EventMachine.run do
92
+ trap("TERM") { EM::stop }
93
+ trap("INT") { EM::stop }
94
+ connect
95
+ log Logger::INFO, "Starting IRC client..."
96
+ end
97
+ log Logger::INFO, "Stopping IRC client"
98
+ @logger.close if @logger
99
+ end
100
+
101
+ # === Callbacks
72
102
 
73
- # Register a callback with :name as one of the following, and
103
+ # Register a callback with `:name` as one of the following, and
74
104
  # a block with the same number of params.
75
105
  #
76
106
  # @example
@@ -97,13 +127,20 @@ module EventMachine
97
127
  end
98
128
 
99
129
  # Trigger a named callback
130
+ # @private
100
131
  def trigger(name, *args)
101
132
  # TODO: should this be instance_eval(&blk)? prevents it from non-dsl style
102
133
  (@callbacks[name.to_sym] || []).each {|blk| blk.call(*args)}
103
134
  end
104
135
 
136
+ # @private
137
+ def log(*args)
138
+ @logger.log(*args) if @logger
139
+ end
140
+
105
141
  # Sends raw message to IRC server. Assumes message is correctly formatted
106
142
  # TODO: what if connect fails? or disconnects?
143
+ # @private
107
144
  def send_data(message)
108
145
  return false unless connected?
109
146
  message = message + "\r\n"
@@ -111,82 +148,20 @@ module EventMachine
111
148
  self.conn.send_data(message)
112
149
  end
113
150
 
114
- # Client commands
115
- # See [RFC 2812](http://tools.ietf.org/html/rfc2812)
116
- def renick(nick)
117
- send_data("NICK #{nick}")
118
- end
119
-
120
- def user(username, mode, realname)
121
- send_data("USER #{username} #{mode} * :#{realname}")
122
- end
123
-
124
- def join(channel_name, channel_key = nil)
125
- send_data("JOIN #{channel_name} #{channel_key}".strip)
126
- end
127
-
128
- def pong(servername)
129
- send_data("PONG :#{servername}")
130
- end
131
-
132
- # @param target [String] nick or channel name
133
- # @param message [String]
134
- def privmsg(target, message)
135
- send_data("PRIVMSG #{target} :#{message}")
136
- end
137
- alias_method :message, :privmsg
138
-
139
- def quit(message = 'leaving')
140
- send_data("QUIT :#{message}")
141
- end
142
-
143
- # @return [Hash] h
144
- # @option h [String] :prefix
145
- # @option h [String] :command
146
- # @option h [Array] :params
151
+ # === EventMachine Callbacks
147
152
  # @private
148
- def parse_message(message)
149
- # TODO: error handling
150
- result = {}
151
- parts = message.split(' ')
152
- result[:prefix] = parts.shift.gsub(/^:/, '') if parts[0] =~ /^:/
153
- result[:command] = parts[0] # cleanup?
154
- result[:params] = parts.slice(1..-1).map {|s| s.gsub(/^:/, '')}
155
- result
156
- end
157
-
158
- def handle_parsed_message(m)
159
- case m[:command]
160
- when '001' # welcome message
161
- when 'PING'
162
- pong(m[:params].first)
163
- trigger(:ping, *m[:params])
164
- when 'PRIVMSG'
165
- trigger(:message, m[:prefix], m[:params].first, m[:params].slice(1..-1).join(' '))
166
- when 'QUIT'
167
- when 'JOIN'
168
- trigger(:join, m[:prefix], m[:params].first)
169
- else
170
- # noop
171
- # {:prefix=>"irc.the.net", :command=>"433", :params=>["*", "one", "Nickname", "already", "in", "use", "irc.the.net", "451", "*", "Connection", "not", "registered"]}
172
- # {:prefix=>"irc.the.net", :command=>"432", :params=>["*", "one_1328243723", "Erroneous", "nickname"]}
173
- end
174
- trigger(:raw, m)
175
- end
176
-
177
- # EventMachine Callbacks
178
153
  def receive_data(data)
179
154
  data.split("\r\n").each do |message|
180
155
  parsed = parse_message(message)
181
156
  handle_parsed_message(parsed)
157
+ trigger(:raw, parsed)
182
158
  end
183
159
  end
184
160
 
185
161
  # @private
186
162
  def ready
187
163
  @connected = true
188
- renick(@nick)
189
- user(@nick, '0', @realname)
164
+ user('guest', '0', @realname)
190
165
  trigger(:connect)
191
166
  end
192
167
 
@@ -195,20 +170,9 @@ module EventMachine
195
170
  trigger(:disconnect)
196
171
  end
197
172
 
198
- def log(*args)
199
- @logger.log(*args) if @logger
200
- end
201
-
202
- def run!
203
- EM.epoll
204
- EventMachine.run do
205
- trap("TERM") { EM::stop }
206
- trap("INT") { EM::stop }
207
- connect
208
- log Logger::INFO, "Starting IRC client..."
209
- end
210
- log Logger::INFO, "Stopping IRC client"
211
- @logger.close if @logger
173
+ protected
174
+ def channel?(string)
175
+ !!(string =~ /^(#|&)/)
212
176
  end
213
177
  end
214
178
  end