blather 0.3.0 → 0.3.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/README.rdoc CHANGED
@@ -60,7 +60,7 @@ Guards act like AND statements. Each condition must be met if the handler is to
60
60
  # Equivalent to saying (stanza.chat? && stanza.body)
61
61
  message :chat?, :body
62
62
 
63
- There are 5 different types of guards:
63
+ The different types of guards are:
64
64
 
65
65
  # Symbol
66
66
  # Checks for a non-false reply to calling the symbol on the stanza
@@ -93,9 +93,24 @@ There are 5 different types of guards:
93
93
  # Equivalent to stanza.body == 'foo' || stanza.body == 'baz'
94
94
  message [{:body => 'foo'}, {:body => 'baz'}]
95
95
 
96
+ == On the Command Line:
97
+
98
+ Default usage is:
99
+
100
+ [blather_script] [options] node@domain.com/resource password [host] [port]
101
+
102
+ Command line options:
103
+
104
+ -D, --debug Run in debug mode (you will see all XMPP communication)
105
+ -d, --daemonize Daemonize the process
106
+ --pid=[PID] Write the PID to this file
107
+ --log=[LOG] Write to the [LOG] file instead of stdout/stderr
108
+ -h, --help Show this message
109
+ -v, --version Show version
110
+
111
+
96
112
  = TODO
97
113
 
98
- * Better Documentation
99
114
  * Add XPath guard that passes the result to the handler
100
115
  * Add Disco the the DSL
101
116
  * PubSub (XEP-0060: http://xmpp.org/extensions/xep-0060.html)
data/Rakefile CHANGED
@@ -16,7 +16,7 @@ begin
16
16
  gem.extensions = ['Rakefile']
17
17
 
18
18
  gem.add_dependency 'eventmachine', '>= 0.12.6'
19
- gem.add_dependency 'libxml-ruby', '>= 1.1.3'
19
+ gem.add_dependency 'libxml-ruby', '>= 1.1.2'
20
20
 
21
21
  gem.files = FileList['examples/**/*', 'lib/**/*', 'ext/*.{rb,c}'].to_a
22
22
 
@@ -0,0 +1,37 @@
1
+ require 'blather/client/dsl'
2
+ $stdout.sync = true
3
+
4
+ module Ping
5
+ extend Blather::DSL
6
+ def self.run; client.run; end
7
+
8
+ setup 'echo@jabber.local/ping', 'echo'
9
+
10
+ status :from => Blather::JID.new('echo@jabber.local/pong') do |s|
11
+ puts "serve!"
12
+ say s.from, 'ping'
13
+ end
14
+
15
+ message :chat?, :body => 'pong' do |m|
16
+ puts "ping!"
17
+ say m.from, 'ping'
18
+ end
19
+ end
20
+
21
+ module Pong
22
+ extend Blather::DSL
23
+ def self.run; client.run; end
24
+
25
+ setup 'echo@jabber.local/pong', 'echo'
26
+ message :chat?, :body => 'ping' do |m|
27
+ puts "pong!"
28
+ say m.from, 'pong'
29
+ end
30
+ end
31
+
32
+ trap(:INT) { EM.stop }
33
+ trap(:TERM) { EM.stop }
34
+ EM.run do
35
+ Ping.run
36
+ Pong.run
37
+ end
data/lib/blather.rb CHANGED
@@ -1,6 +1,3 @@
1
- $:.unshift File.dirname(__FILE__)
2
- $:.unshift File.join(File.dirname(__FILE__), '..')
3
-
4
1
  # Require the necessary files
5
2
  %w[
6
3
  rubygems
@@ -48,6 +45,6 @@ $:.unshift File.join(File.dirname(__FILE__), '..')
48
45
  XML.indent_tree_output = false
49
46
 
50
47
  module Blather
51
- LOG = Logger.new(STDOUT) unless const_defined?(:LOG)
48
+ LOG = Logger.new($stdout) unless const_defined?(:LOG)
52
49
  LOG.level = Logger::INFO
53
50
  end
@@ -1,13 +1,85 @@
1
+ require 'optparse'
1
2
  require File.join(File.dirname(__FILE__), *%w[client dsl])
2
3
 
3
4
  include Blather::DSL
4
5
 
6
+ options = {}
7
+ optparse = OptionParser.new do |opts|
8
+ opts.banner = "Run with #{$0} [options] user@server/resource password [host] [port]"
9
+
10
+ opts.on('-D', '--debug', 'Run in debug mode (you will see all XMPP communication)') do |debug|
11
+ options[:debug] = debug
12
+ end
13
+
14
+ opts.on('-d', '--daemonize', 'Daemonize the process') do |daemonize|
15
+ options[:daemonize] = daemonize
16
+ end
17
+
18
+ opts.on('--pid=[PID]', 'Write the PID to this file') do |pid|
19
+ if !File.writable?(File.dirname(pid))
20
+ $stderr.puts "Unable to write log file to #{pid}"
21
+ exit 1
22
+ end
23
+ options[:pid] = pid
24
+ end
25
+
26
+ opts.on('--log=[LOG]', 'Write to the [LOG] file instead of stdout/stderr') do |log|
27
+ if !File.writable?(File.dirname(log))
28
+ $stderr.puts "Unable to write log file to #{log}"
29
+ exit 1
30
+ end
31
+ options[:log] = log
32
+ end
33
+
34
+ opts.on_tail('-h', '--help', 'Show this message') do
35
+ puts opts
36
+ exit
37
+ end
38
+
39
+ opts.on_tail('-v', '--version', 'Show version') do
40
+ require 'yaml'
41
+ version = YAML.load_file File.join(File.dirname(__FILE__), %w[.. .. VERSION.yml])
42
+ puts "Blather v#{version[:major]}.#{version[:minor]}.#{version[:patch]}"
43
+ exit
44
+ end
45
+ end
46
+ optparse.parse!
47
+
5
48
  at_exit do
6
49
  unless client.setup?
7
50
  if ARGV.length < 2
8
- puts "Run with #{$0} user@server/resource password [host] [port]"
9
- else
10
- client.setup(*ARGV).run
51
+ puts optparse
52
+ exit 1
53
+ end
54
+ client.setup(*ARGV)
55
+ end
56
+
57
+ def run(options)
58
+ $stdin.reopen "/dev/null"
59
+
60
+ if options[:log]
61
+ log = File.new(options[:log], 'a')
62
+ log.sync = options[:debug]
63
+ Blather::LOG.level = Logger::DEBUG if options[:debug]
64
+ $stdout.reopen log
65
+ $stderr.reopen $stdout
66
+ end
67
+ trap(:INT) { EM.stop }
68
+ trap(:TERM) { EM.stop }
69
+ EM.run { client.run }
70
+ end
71
+
72
+ if options[:daemonize]
73
+ pid = fork do
74
+ Process.setsid
75
+ exit if fork
76
+ File.open(options[:pid], 'w') { |f| f << Process.pid } if options[:pid]
77
+ run options
78
+ FileUtils.rm(options[:pid]) if options[:pid]
11
79
  end
80
+ ::Process.detach pid
81
+ exit
82
+ else
83
+ run options
12
84
  end
13
85
  end
@@ -3,8 +3,8 @@ require File.join(File.dirname(__FILE__), *%w[.. .. blather])
3
3
  module Blather #:nodoc:
4
4
 
5
5
  class Client #:nodoc:
6
- attr_accessor :jid,
7
- :roster
6
+ attr_reader :jid,
7
+ :roster
8
8
 
9
9
  def initialize
10
10
  @state = :initializing
@@ -17,6 +17,24 @@ module Blather #:nodoc:
17
17
  setup_initial_handlers
18
18
  end
19
19
 
20
+ def jid=(new_jid)
21
+ @jid = JID.new new_jid
22
+ end
23
+
24
+ def status
25
+ @status.state
26
+ end
27
+
28
+ def status=(state)
29
+ state, msg, to = state
30
+
31
+ status = Stanza::Presence::Status.new state, msg
32
+ status.to = to
33
+ @status = status unless to
34
+
35
+ write status
36
+ end
37
+
20
38
  def setup?
21
39
  @setup.is_a? Array
22
40
  end
@@ -30,11 +48,8 @@ module Blather #:nodoc:
30
48
 
31
49
  def run
32
50
  raise 'not setup!' unless setup?
33
- trap(:INT) { EM.stop }
34
- EM.run {
35
- klass = @setup[0].node ? Blather::Stream::Client : Blather::Stream::Component
36
- klass.start self, *@setup
37
- }
51
+ klass = @setup[0].node ? Blather::Stream::Client : Blather::Stream::Component
52
+ @stream = klass.start self, *@setup
38
53
  end
39
54
 
40
55
  def register_tmp_handler(id, &handler)
@@ -42,62 +57,38 @@ module Blather #:nodoc:
42
57
  end
43
58
 
44
59
  def register_handler(type, *guards, &handler)
60
+ check_guards guards
45
61
  @handlers[type] ||= []
46
62
  @handlers[type] << [guards, handler]
47
63
  end
48
64
 
49
- def status
50
- @status.state
51
- end
52
-
53
- def status=(state)
54
- state, msg, to = state
55
-
56
- status = Stanza::Presence::Status.new state, msg
57
- status.to = to
58
- @status = status unless to
59
-
60
- write status
61
- end
62
-
63
65
  def write(stanza)
64
66
  stanza.from ||= jid if stanza.respond_to?(:from)
65
67
  @stream.send(stanza) if @stream
66
68
  end
67
69
 
68
- def write_with_handler(stanza, &hanlder)
70
+ def write_with_handler(stanza, &handler)
69
71
  register_tmp_handler stanza.id, &handler
70
72
  write stanza
71
73
  end
72
74
 
73
- def stream_started(stream)
74
- @stream = stream
75
-
76
- #retreive roster
77
- if @stream.is_a?(Stream::Component)
78
- @state = :ready
79
- call_handler_for :ready, nil
80
- else
81
- r = Stanza::Iq::Roster.new
82
- register_tmp_handler r.id do |node|
83
- roster.process node
84
- @state = :ready
85
- write @status
86
- call_handler_for :ready, nil
87
- end
88
- write r
75
+ def post_init
76
+ case @stream
77
+ when Stream::Component then ready!
78
+ when Stream::Client then client_post_init
79
+ else raise "Don't know #{@stream.class} stream type. How the hell did this happen!?"
89
80
  end
90
81
  end
91
82
 
92
- def stop
83
+ def close
93
84
  @stream.close_connection_after_writing
94
85
  end
95
86
 
96
- def stopped
97
- EM.stop
87
+ def unbind
88
+ EM.stop if EM.reactor_running?
98
89
  end
99
90
 
100
- def call(stanza)
91
+ def receive_data(stanza)
101
92
  if handler = @tmp_handlers.delete(stanza.id)
102
93
  handler.call stanza
103
94
  else
@@ -107,21 +98,14 @@ module Blather #:nodoc:
107
98
  end
108
99
  end
109
100
 
110
- def call_handler_for(type, stanza)
111
- if @handlers[type]
112
- @handlers[type].find { |guards, handler| handler.call(stanza) unless guarded?(guards, stanza) }
113
- true
114
- end
115
- end
116
-
117
101
  protected
118
102
  def setup_initial_handlers
119
103
  register_handler :error do |err|
120
104
  raise err
121
105
  end
122
106
 
123
- register_handler :iq do |iq|
124
- write(StanzaError.new(iq, 'service-unavailable', :cancel).to_node) if [:set, :get].include?(iq.type)
107
+ register_handler :iq, :type => [:get, :set] do |iq|
108
+ write(StanzaError.new(iq, 'service-unavailable', :cancel).to_node)
125
109
  end
126
110
 
127
111
  register_handler :status do |status|
@@ -133,8 +117,30 @@ module Blather #:nodoc:
133
117
  end
134
118
  end
135
119
 
120
+ def ready!
121
+ @state = :ready
122
+ call_handler_for :ready, nil
123
+ end
124
+
125
+ def client_post_init
126
+ write_with_handler Stanza::Iq::Roster.new do |node|
127
+ roster.process node
128
+ write @status
129
+ ready!
130
+ end
131
+ end
132
+
133
+ def call_handler_for(type, stanza)
134
+ if @handlers[type]
135
+ @handlers[type].find { |guards, handler| handler.call(stanza) unless guarded?(guards, stanza) }
136
+ true
137
+ end
138
+ end
139
+
136
140
  ##
137
141
  # If any of the guards returns FALSE this returns true
142
+ # the logic is reversed to allow short circuiting
143
+ # (why would anyone want to loop over more values than necessary?)
138
144
  def guarded?(guards, stanza)
139
145
  guards.find do |guard|
140
146
  case guard
@@ -158,8 +164,16 @@ module Blather #:nodoc:
158
164
  end
159
165
  when Proc
160
166
  !guard.call(stanza)
161
- else
162
- raise "Bad guard: #{guard.inspect}"
167
+ end
168
+ end
169
+ end
170
+
171
+ def check_guards(guards)
172
+ guards.each do |guard|
173
+ case guard
174
+ when Array then guard.each { |g| check_guards([g]) }
175
+ when Symbol, Proc, Hash then nil
176
+ else raise "Bad guard: #{guard.inspect}"
163
177
  end
164
178
  end
165
179
  end
@@ -13,14 +13,13 @@ module Blather
13
13
  # host and port are optional defaulting to the domain in the JID and 5222 respectively
14
14
  def setup(jid, password, host = nil, port = nil)
15
15
  client.setup(jid, password, host, port)
16
- at_exit { client.run }
17
16
  end
18
17
 
19
18
  ##
20
19
  # Shutdown the connection.
21
20
  # Flushes the write buffer then stops EventMachine
22
21
  def shutdown
23
- client.stop
22
+ client.close
24
23
  end
25
24
 
26
25
  ##
@@ -37,13 +36,13 @@ module Blather
37
36
 
38
37
  ##
39
38
  # Set current status
40
- def status(state = nil, msg = nil)
39
+ def set_status(state = nil, msg = nil)
41
40
  client.status = state, msg
42
41
  end
43
42
 
44
43
  ##
45
44
  # Direct access to the roster
46
- def roster
45
+ def my_roster
47
46
  client.roster
48
47
  end
49
48
 
@@ -51,7 +50,7 @@ module Blather
51
50
  # Write data to the stream
52
51
  # Anything that resonds to #to_s can be paseed to the stream
53
52
  def write(stanza)
54
- client.write(stanza)
53
+ client.write stanza
55
54
  end
56
55
 
57
56
  ##
@@ -68,7 +67,9 @@ module Blather
68
67
  end
69
68
 
70
69
  ##
71
- #
70
+ # Request items or info from an entity
71
+ # discover (items|info), [jid], [node] do |response|
72
+ # end
72
73
  def discover(what, who, where, &callback)
73
74
  stanza = Blather::Stanza.class_from_registration(:query, "http://jabber.org/protocol/disco##{what}").new
74
75
  stanza.to = who
@@ -78,22 +79,16 @@ module Blather
78
79
  write stanza
79
80
  end
80
81
 
81
- ##
82
- # PubSub proxy
83
- def pubsub
84
- client.pubsub
85
- end
86
-
87
82
  ##
88
83
  # Checks to see if the method is part of the handlers list.
89
84
  # If so it creates a handler, otherwise it'll pass it back
90
85
  # to Ruby's method_missing handler
91
- def method_missing(method, *args, &block)
92
- if Blather::Stanza.handler_list.include?(method)
93
- handle method, *args, &block
94
- else
95
- super
96
- end
86
+ Blather::Stanza.handler_list.each do |handler_name|
87
+ module_eval <<-METHOD, __FILE__, __LINE__
88
+ def #{handler_name}(*args, &callback)
89
+ handle :#{handler_name}, *args, &callback
90
+ end
91
+ METHOD
97
92
  end
98
93
  end #DSL
99
94
  end #Blather