miu 0.1.0 → 0.2.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.
Files changed (54) hide show
  1. data/.rspec +2 -0
  2. data/.travis.yml +21 -0
  3. data/Gemfile +7 -0
  4. data/Guardfile +8 -0
  5. data/README.md +19 -2
  6. data/Rakefile +3 -0
  7. data/bin/miu +0 -2
  8. data/lib/miu/cli.rb +49 -24
  9. data/lib/miu/cli_base.rb +7 -0
  10. data/lib/miu/command.rb +19 -12
  11. data/lib/miu/errors.rb +22 -0
  12. data/lib/miu/forwarder.rb +54 -0
  13. data/lib/miu/logger.rb +11 -5
  14. data/lib/miu/messages/base.rb +14 -4
  15. data/lib/miu/messages/text.rb +4 -2
  16. data/lib/miu/messages/unknown.rb +11 -0
  17. data/lib/miu/messages.rb +17 -2
  18. data/lib/miu/{plugin.rb → node.rb} +10 -9
  19. data/lib/miu/nodes/.gitkeep +0 -0
  20. data/lib/miu/{plugins.rb → nodes.rb} +1 -1
  21. data/lib/miu/packet.rb +11 -18
  22. data/lib/miu/proxy.rb +57 -0
  23. data/lib/miu/publishable.rb +18 -0
  24. data/lib/miu/publisher.rb +16 -22
  25. data/lib/miu/resources/base.rb +2 -2
  26. data/lib/miu/resources/content.rb +1 -1
  27. data/lib/miu/resources/network.rb +1 -1
  28. data/lib/miu/resources/room.rb +1 -1
  29. data/lib/miu/resources/text_content.rb +3 -5
  30. data/lib/miu/resources/user.rb +1 -1
  31. data/lib/miu/server.rb +10 -25
  32. data/lib/miu/socket.rb +100 -28
  33. data/lib/miu/subscribable.rb +44 -0
  34. data/lib/miu/subscriber.rb +17 -48
  35. data/lib/miu/utility.rb +13 -8
  36. data/lib/miu/version.rb +1 -1
  37. data/lib/miu.rb +42 -25
  38. data/miu.gemspec +6 -4
  39. data/spec/miu/command_spec.rb +26 -0
  40. data/spec/miu/logger_spec.rb +39 -0
  41. data/spec/miu/messages/base_spec.rb +43 -0
  42. data/spec/miu/messages/text_spec.rb +35 -0
  43. data/spec/miu/node_spec.rb +31 -0
  44. data/spec/miu/packet_spec.rb +47 -0
  45. data/spec/miu/publishable_spec.rb +24 -0
  46. data/spec/miu/publisher_spec.rb +36 -0
  47. data/spec/miu/subscribable_spec.rb +44 -0
  48. data/spec/miu/subscriber_spec.rb +36 -0
  49. data/spec/miu/utility_spec.rb +60 -0
  50. data/spec/miu_spec.rb +23 -0
  51. data/spec/spec_helper.rb +15 -0
  52. metadata +86 -19
  53. data/lib/miu/plugins/null.rb +0 -59
  54. data/lib/templates/Gemfile +0 -8
data/lib/miu/publisher.rb CHANGED
@@ -1,30 +1,24 @@
1
- require 'miu'
2
1
  require 'miu/socket'
3
- require 'miu/packet'
2
+ require 'miu/publishable'
3
+ require 'miu/utility'
4
4
 
5
5
  module Miu
6
- class Publisher < Socket
7
- def initialize(options = {})
8
- options[:port] ||= Miu.default_sub_port
9
- super socket_type, options
6
+ module Publisher
7
+ class << self
8
+ def new(*args, &block)
9
+ options = Miu::Utility.extract_options! args
10
+ host = args.shift || '127.0.0.1'
11
+ port = args.shift || Miu.default_sub_port
12
+ socket = options[:socket] || PubSocket
10
13
 
11
- yield self if block_given?
12
- end
13
-
14
- # tag, time = nil, body
15
- def send(*args)
16
- packet = Packet.new *args
17
- @socket.send_strings packet.dump
18
- packet.id
19
- end
20
-
21
- private
14
+ klass = Class.new(socket, &block)
15
+ klass.send :include, Publishable
16
+ klass.send :include, self
22
17
 
23
- def socket_type
24
- if ZMQ::LibZMQ.version3?
25
- ZMQ::XPUB
26
- else
27
- ZMQ::PUB
18
+ klass.new.tap do |pub|
19
+ address = Miu::Socket.build_address host, port
20
+ pub.connect address
21
+ end
28
22
  end
29
23
  end
30
24
  end
@@ -7,8 +7,8 @@ module Miu
7
7
  {}
8
8
  end
9
9
 
10
- def to_msgpack
11
- to_hash.to_msgpack
10
+ def to_msgpack(*args)
11
+ to_hash.to_msgpack(*args)
12
12
  end
13
13
  end
14
14
  end
@@ -1,4 +1,4 @@
1
- require 'miu/resources/base'
1
+ require 'miu/resources'
2
2
 
3
3
  module Miu
4
4
  module Resources
@@ -1,4 +1,4 @@
1
- require 'miu/resources/base'
1
+ require 'miu/resources'
2
2
 
3
3
  module Miu
4
4
  module Resources
@@ -1,4 +1,4 @@
1
- require 'miu/resources/base'
1
+ require 'miu/resources'
2
2
 
3
3
  module Miu
4
4
  module Resources
@@ -1,6 +1,4 @@
1
- require 'miu/resources/content'
2
- require 'miu/resources/room'
3
- require 'miu/resources/user'
1
+ require 'miu/resources'
4
2
 
5
3
  module Miu
6
4
  module Resources
@@ -8,8 +6,8 @@ module Miu
8
6
  attr_accessor :room, :user, :text
9
7
 
10
8
  def initialize(options = {})
11
- @room = options[:room] || Room.new(options[:room] || {})
12
- @user = options[:user] || User.new(options[:user] || {})
9
+ @room = Miu::Utility.adapt(Room, options[:room] || {})
10
+ @user = Miu::Utility.adapt(User, options[:user] || {})
13
11
  @text = options[:text]
14
12
  super options
15
13
  end
@@ -1,4 +1,4 @@
1
- require 'miu/resources/base'
1
+ require 'miu/resources'
2
2
 
3
3
  module Miu
4
4
  module Resources
data/lib/miu/server.rb CHANGED
@@ -1,30 +1,19 @@
1
1
  require 'miu'
2
+ require 'ffi-rzmq'
2
3
 
3
4
  module Miu
4
5
  class Server
5
6
  attr_reader :options
6
- attr_reader :publisher, :subscriber
7
+ attr_reader :forwarder
7
8
 
8
9
  def initialize(options = {})
9
10
  @options = options
10
- if options[:verbose] && Miu.logger
11
- Miu.logger.level = ::Logger::DEBUG
12
- end
13
11
  end
14
12
 
15
13
  def run
16
- pub_address = "#{@options[:pub_host]}:#{@options[:pub_port]}"
17
- sub_address = "#{@options[:sub_host]}:#{@options[:sub_port]}"
18
-
19
- @publisher = Publisher.new({:host => @options[:pub_host], :port => @options[:pub_port]})
20
- @subscriber = Subscriber.new({:host => @options[:sub_host], :port => @options[:sub_port]})
21
-
22
- @publisher.bind
23
- @subscriber.bind
24
-
25
- Logger.info "Starting miu"
26
- Logger.info "pub: #{@publisher.host}:#{@publisher.port}"
27
- Logger.info "sub: #{@subscriber.host}:#{@subscriber.port}"
14
+ Logger.info "Starting Miu #{Miu::VERSION} (ZeroMQ #{ZMQ::LibZMQ.version.values.join('.')})"
15
+ Logger.info "Publish on #{@options[:pub_host]}:#{@options[:pub_port]}"
16
+ Logger.info "Subscribe on #{@options[:sub_host]}:#{@options[:sub_port]}"
28
17
 
29
18
  [:INT, :TERM].each do |sig|
30
19
  trap(sig) do
@@ -34,18 +23,14 @@ module Miu
34
23
  end
35
24
  end
36
25
 
37
- loop do
38
- parts = @subscriber.forward @publisher
39
- if @options[:verbose]
40
- packet = Packet.load parts
41
- Logger.debug packet.inspect
42
- end
43
- end
26
+ @forwarder = Forwarder.new @options
27
+ @forwarder.run
28
+ rescue => e
29
+ Miu::Logger.exception e
44
30
  end
45
31
 
46
32
  def close
47
- @subscriber.close
48
- @publisher.close
33
+ @forwarder.close
49
34
  end
50
35
  end
51
36
  end
data/lib/miu/socket.rb CHANGED
@@ -1,40 +1,42 @@
1
1
  require 'miu'
2
2
  require 'ffi-rzmq'
3
+ require 'forwardable'
3
4
 
4
5
  module Miu
5
6
  class Socket
6
- attr_reader :host, :port
7
- attr_reader :context, :socket
7
+ class << self
8
+ def socket_type(type)
9
+ class_eval <<-EOS
10
+ def socket_type; :#{type.to_s.upcase}; end
11
+ EOS
12
+ end
8
13
 
9
- def initialize(socket_type, options = {})
10
- @host = options[:host] || '127.0.0.1'
11
- @port = options[:port]
12
- @context = Miu.context
13
- @socket = @context.socket socket_type
14
+ def build_address(*args)
15
+ host = args.shift
16
+ port = args.shift
17
+ port ? "tcp://#{host}:#{port}" : host
18
+ end
14
19
  end
15
20
 
16
- def bind
17
- rc = @socket.bind "tcp://#{@host}:#{@port}"
18
- error_check rc
19
- self
21
+ attr_reader :socket
22
+ attr_reader :linger
23
+
24
+ def initialize
25
+ @socket = Miu.context.socket ::ZMQ.const_get(socket_type)
26
+ @linger = 0
20
27
  end
21
28
 
22
- def connect
23
- rc = @socket.connect "tcp://#{@host}:#{@port}"
24
- error_check rc
25
- self
29
+ def bind(address)
30
+ error_wrapper { @socket.bind address }
26
31
  end
27
32
 
28
- def forward(to)
29
- parts = []
30
- loop do
31
- message = ZMQ::Message.new
32
- @socket.recvmsg message
33
- parts << message.copy_out_string
34
- more = @socket.more_parts?
35
- to.socket.sendmsg message, (more ? ZMQ::SNDMORE : 0)
36
- return parts unless more
37
- end
33
+ def connect(address)
34
+ error_wrapper { @socket.connect address }
35
+ end
36
+
37
+ def linger=(value)
38
+ @linger = value || -1
39
+ error_wrapper { @socket.setsockopt(::ZMQ::LINGER, value) }
38
40
  end
39
41
 
40
42
  def close
@@ -43,10 +45,80 @@ module Miu
43
45
 
44
46
  protected
45
47
 
46
- def error_check(rc, source = nil)
47
- unless ZMQ::Util.resultcode_ok? rc
48
- raise ZMQ::ZeroMQError.new source, rc, ZMQ::Util.errno, ZMQ::Util.error_string
48
+ def error_wrapper(source = nil, &block)
49
+ error = nil
50
+
51
+ begin
52
+ rc = block.call
53
+ error = "#{::ZMQ::Util.error_string} (#{::ZMQ::Util.errno})" unless ::ZMQ::Util.resultcode_ok?(rc)
54
+ rescue => e
55
+ error = e.to_s
49
56
  end
57
+
58
+ raise IOError, error if error
59
+ true
60
+ end
61
+ end
62
+
63
+ module ReadableSocket
64
+ extend Forwardable
65
+ def_delegator :@socket, :more_parts?
66
+
67
+ def bind(address)
68
+ self.linger = @linger
69
+ super address
70
+ end
71
+
72
+ def connect(address)
73
+ self.linger = @linger
74
+ super address
75
+ end
76
+
77
+ def read(buffer = '')
78
+ error_wrapper { @socket.recv_string buffer }
79
+ buffer
80
+ end
81
+ end
82
+
83
+ module WritableSocket
84
+ def write(*args)
85
+ error_wrapper { @socket.send_strings args.flatten }
86
+ args
87
+ end
88
+
89
+ alias_method :<<, :write
90
+ end
91
+
92
+ class PubSocket < Socket
93
+ include WritableSocket
94
+ socket_type :pub
95
+ end
96
+
97
+ class SubSocket < Socket
98
+ include ReadableSocket
99
+ socket_type :sub
100
+
101
+ def subscribe(topic)
102
+ error_wrapper { @socket.setsockopt(::ZMQ::SUBSCRIBE, topic) }
103
+ end
104
+
105
+ def unsubscribe(topic)
106
+ error_wrapper { @socket.setsockopt(::ZMQ::UNSUBSCRIBE, topic) }
107
+ end
108
+ end
109
+
110
+ class XPubSocket < PubSocket
111
+ socket_type :xpub
112
+ end
113
+
114
+ class XSubSocket < SubSocket
115
+ socket_type :xsub
116
+
117
+ def subscribe(topic)
118
+ true
119
+ end
120
+
121
+ def unsubscribe(topic)
50
122
  true
51
123
  end
52
124
  end
@@ -0,0 +1,44 @@
1
+ require 'miu/packet'
2
+ require 'miu/messages'
3
+
4
+ module Miu
5
+ module Subscribable
6
+ def self.included(base)
7
+ base.class_eval do
8
+ def read_with_packet
9
+ parts = []
10
+ loop do
11
+ parts << read_without_packet
12
+ break unless more_parts?
13
+ end
14
+
15
+ packet = Packet.load parts
16
+ begin
17
+ data = Miu::Utility.symbolize_keys(packet.data, true) rescue packet.data
18
+ type = data[:type] rescue nil
19
+ message_class = Miu::Messages.guess(type)
20
+ packet.data = message_class.new data
21
+ packet
22
+ rescue => e
23
+ raise MessageLoadError, e
24
+ end
25
+ end
26
+
27
+ alias_method :read_without_packet, :read
28
+ alias_method :read, :read_with_packet
29
+ end
30
+
31
+ base.send :include, ::Enumerable
32
+ end
33
+
34
+ def each
35
+ if block_given?
36
+ loop do
37
+ yield read
38
+ end
39
+ end
40
+
41
+ return self
42
+ end
43
+ end
44
+ end
@@ -1,56 +1,25 @@
1
- require 'miu'
2
1
  require 'miu/socket'
3
- require 'miu/packet'
2
+ require 'miu/subscribable'
3
+ require 'miu/utility'
4
4
 
5
5
  module Miu
6
- class Subscriber < Socket
7
- attr_reader :subscribe
8
-
9
- def initialize(options = {})
10
- options[:port] ||= Miu.default_pub_port
11
- super socket_type, options
12
-
13
- subscribe options[:subscribe] || ''
14
- yield self if block_given?
15
- end
16
-
17
- def subscribe(value = nil)
18
- if value
19
- unsubscribe if @subscribe
20
- @subscribe = value.to_s
21
- @socket.setsockopt ZMQ::SUBSCRIBE, @subscribe
22
- else
23
- @subscribe
24
- end
25
- end
26
-
27
- def unsubscribe
28
- if @subscribe
29
- @socket.setsockopt ZMQ::UNSUBSCRIBE, @subscribe
30
- @subscribe = nil
31
- end
32
- nil
33
- end
34
-
35
- def recv
36
- parts = []
37
- @socket.recv_strings parts
38
- Packet.load parts
39
- end
40
-
41
- def each
42
- if block_given?
43
- loop do
44
- packet = recv rescue nil
45
- yield packet if packet
6
+ module Subscriber
7
+ class << self
8
+ def new(*args, &block)
9
+ options = Miu::Utility.extract_options! args
10
+ host = args.shift || '127.0.0.1'
11
+ port = args.shift || Miu.default_pub_port
12
+ socket = options[:socket] || SubSocket
13
+
14
+ klass = Class.new(socket, &block)
15
+ klass.send :include, Subscribable
16
+ klass.send :include, self
17
+
18
+ klass.new.tap do |sub|
19
+ address = Miu::Socket.build_address host, port
20
+ sub.connect address
46
21
  end
47
22
  end
48
23
  end
49
-
50
- private
51
-
52
- def socket_type
53
- ZMQ::SUB
54
- end
55
24
  end
56
25
  end
data/lib/miu/utility.rb CHANGED
@@ -2,11 +2,16 @@ module Miu
2
2
  module Utility
3
3
  module_function
4
4
 
5
+ def adapt(klass, value)
6
+ value ||= {}
7
+ value.is_a?(klass) ? value : klass.new(value)
8
+ end
9
+
5
10
  def extract_options!(args)
6
11
  args.last.is_a?(::Hash) ? args.pop : {}
7
12
  end
8
13
 
9
- def modified_keys(hash, recursive = false, &block)
14
+ def modify_keys(hash, recursive = false, &block)
10
15
  hash = hash.dup
11
16
  if block
12
17
  hash.keys.each do |key|
@@ -14,9 +19,9 @@ module Miu
14
19
  if recursive
15
20
  case value
16
21
  when ::Hash
17
- value = modified_keys(value, recursive, &block)
22
+ value = modify_keys(value, recursive, &block)
18
23
  when ::Array
19
- value = value.map { |v| v.is_a?(::Hash) ? modified_keys(v, recursive, &block) : v }
24
+ value = value.map { |v| v.is_a?(::Hash) ? modify_keys(v, recursive, &block) : v }
20
25
  end
21
26
  end
22
27
  key = block.call key
@@ -26,20 +31,20 @@ module Miu
26
31
  hash
27
32
  end
28
33
 
29
- def symbolized_keys(hash, recursive = false)
30
- modified_keys(hash, recursive) do |key|
34
+ def symbolize_keys(hash, recursive = false)
35
+ modify_keys(hash, recursive) do |key|
31
36
  key.to_sym rescue key
32
37
  end
33
38
  end
34
39
 
35
40
  def underscorize_keys(hash, recursive = false)
36
- modified_keys(hash, recursive) do |key|
41
+ modify_keys(hash, recursive) do |key|
37
42
  key.gsub('-', '_') rescue key
38
43
  end
39
44
  end
40
45
 
41
- def optionalize_keys(hash, recursive = false)
42
- modified_keys(hash, recursive) do |key|
46
+ def optionify_keys(hash, recursive = false)
47
+ modify_keys(hash, recursive) do |key|
43
48
  key.to_s.gsub('-', '_').to_sym rescue key
44
49
  end
45
50
  end
data/lib/miu/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Miu
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
data/lib/miu.rb CHANGED
@@ -1,27 +1,40 @@
1
1
  require 'miu/version'
2
2
  require 'miu/logger'
3
+ require 'miu/errors'
3
4
 
4
5
  module Miu
5
6
  autoload :CLI, 'miu/cli'
6
7
  autoload :Utility, 'miu/utility'
8
+
9
+ autoload :Socket, 'miu/socket'
10
+ autoload :PubSocket, 'miu/socket'
11
+ autoload :SubSocket, 'miu/socket'
12
+ autoload :XPubSocket, 'miu/socket'
13
+ autoload :XSubSocket, 'miu/socket'
14
+
7
15
  autoload :Server, 'miu/server'
8
16
  autoload :Packet, 'miu/packet'
9
- autoload :Socket, 'miu/socket'
17
+ autoload :Publishable, 'miu/publishable'
18
+ autoload :Subscribable, 'miu/subscribable'
10
19
  autoload :Publisher, 'miu/publisher'
11
20
  autoload :Subscriber, 'miu/subscriber'
21
+ autoload :Proxy, 'miu/proxy'
22
+ autoload :Forwarder, 'miu/forwarder'
23
+
12
24
  autoload :Command, 'miu/command'
13
- autoload :Plugin, 'miu/plugin'
14
- autoload :Plugins, 'miu/plugins'
25
+ autoload :Node, 'miu/node'
26
+ autoload :Type, 'miu/type'
15
27
  autoload :Resources, 'miu/resources'
16
28
  autoload :Messages, 'miu/messages'
17
29
 
18
30
  class << self
19
31
  def root
20
- @root ||= find_root 'Gemfile'
32
+ require 'pathname'
33
+ @root ||= Pathname.new(Dir.pwd)
21
34
  end
22
35
 
23
36
  def default_port
24
- 22200
37
+ Integer(ENV['MIU_DEFAULT_PORT']) rescue 22200
25
38
  end
26
39
 
27
40
  def default_god_port
@@ -40,34 +53,38 @@ module Miu
40
53
  'config/miu.god'
41
54
  end
42
55
 
43
- def find_root(flag, base = nil)
44
- require 'pathname'
45
- path = base || Dir.pwd
46
- while path && File.directory?(path) && !File.exist?("#{path}/#{flag}")
47
- parent = File.dirname path
48
- path = path != parent && parent
49
- end
50
- raise 'Could not find root path' unless path
51
- Pathname.new File.realpath(path)
52
- end
53
-
54
56
  def context
55
57
  require 'ffi-rzmq'
56
58
  @context ||= ZMQ::Context.new
57
59
  end
58
60
 
59
- def plugins
60
- @plugins ||= {}
61
+ def nodes
62
+ @nodes ||= {}
61
63
  end
62
64
 
63
- def register(name, plugin, options = {}, &block)
64
- Miu.plugins[name] = plugin
65
- if block
66
- usage = options[:usage] || "#{name} [COMMAND]"
67
- desc = options[:desc] || plugin.to_s
68
- command = Miu::Command.new name, plugin, &block
69
- Miu::CLI.register command, name, usage, desc
65
+ def load_nodes
66
+ gems.each do |spec|
67
+ @current_spec = spec
68
+ require spec.name
70
69
  end
71
70
  end
71
+
72
+ def register(name, node, options = {}, &block)
73
+ node.spec = @current_spec
74
+ Miu.nodes[name] = node
75
+ usage = options[:usage] || "#{name} [COMMAND]"
76
+ desc = node.description
77
+ command = Miu::Command.new name, node, &block
78
+ Miu::CLI.register command, name, usage, desc
79
+ command
80
+ end
81
+
82
+ def gems
83
+ @gems ||= find_gems
84
+ end
85
+
86
+ def find_gems
87
+ Gem::Specification.find_all.select { |spec| spec.name =~ /^miu-/ }
88
+ end
72
89
  end
73
90
  end
data/miu.gemspec CHANGED
@@ -17,9 +17,11 @@ Gem::Specification.new do |gem|
17
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
18
  gem.require_paths = ["lib"]
19
19
 
20
- gem.add_dependency 'bundler', '>= 1.2.0'
21
- gem.add_dependency 'thor', '>= 0.17.0'
20
+ gem.add_dependency 'thor', '>= 0.18.1'
21
+ gem.add_dependency 'god', '>= 0.13.2'
22
22
  gem.add_dependency 'ffi-rzmq', '>= 1.0.0'
23
- gem.add_dependency 'msgpack', '>= 0.5.3'
24
- gem.add_development_dependency 'rake', '>= 10.0.3'
23
+ gem.add_dependency 'msgpack', '>= 0.5.4'
24
+ gem.add_development_dependency 'rake', '>= 10.0.4'
25
+ gem.add_development_dependency 'rspec', '>= 2.13.0'
26
+ gem.add_development_dependency 'celluloid-zmq', '>= 0.13.0'
25
27
  end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Miu::Command do
4
+ describe 'new' do
5
+ before do
6
+ node = double('node')
7
+ node.stub_chain(:spec, :full_gem_path).and_return('path/to/node')
8
+ Miu.stub(:root).and_return ('path/to/root')
9
+ @command = Miu::Command.new 'test_node', node do
10
+ def self.hello
11
+ end
12
+ end
13
+ end
14
+ subject { @command }
15
+
16
+ it { should be_instance_of Class }
17
+ its(:ancestors) { should include Thor }
18
+ its(:source_root) { should eq 'path/to/node' }
19
+ its(:destination_root) { should eq 'path/to/root' }
20
+ its(:namespace) { should eq 'test_node' }
21
+ it { should respond_to :add_miu_pub_options }
22
+ it { should respond_to :add_miu_sub_options }
23
+ it { should respond_to :add_miu_pub_sub_options }
24
+ it { should respond_to :hello }
25
+ end
26
+ end