carbonmu 0.0.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.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +14 -0
  7. data/Gemfile +3 -0
  8. data/Gemfile.lock +161 -0
  9. data/Guardfile +50 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +66 -0
  12. data/Rakefile +10 -0
  13. data/carbonmu.gemspec +64 -0
  14. data/config/i18n-tasks.yml +89 -0
  15. data/config/locales/en.yml +6 -0
  16. data/config/locales/nl.yml +6 -0
  17. data/console +9 -0
  18. data/doc/architecture.png +0 -0
  19. data/lib/carbonmu.rb +42 -0
  20. data/lib/commands/locale_command.rb +12 -0
  21. data/lib/commands/ping_command.rb +9 -0
  22. data/lib/commands/reboot_command.rb +9 -0
  23. data/lib/commands/say_command.rb +10 -0
  24. data/lib/commands/unknown_command.rb +7 -0
  25. data/lib/core/command.rb +26 -0
  26. data/lib/core/command_context.rb +12 -0
  27. data/lib/core/configuration.rb +22 -0
  28. data/lib/core/connection.rb +19 -0
  29. data/lib/core/internationalization.rb +16 -0
  30. data/lib/core/parser.rb +29 -0
  31. data/lib/core/server.rb +118 -0
  32. data/lib/core/server_supervision_group.rb +5 -0
  33. data/lib/core_ext/match_data.rb +7 -0
  34. data/lib/core_ext/string.rb +5 -0
  35. data/lib/edge_router/edge_connection.rb +39 -0
  36. data/lib/edge_router/edge_router.rb +104 -0
  37. data/lib/edge_router/edge_router_supervision_group.rb +5 -0
  38. data/lib/edge_router/telnet_connection.rb +35 -0
  39. data/lib/edge_router/telnet_receptor.rb +30 -0
  40. data/lib/errors.rb +4 -0
  41. data/lib/game_objects/container.rb +10 -0
  42. data/lib/game_objects/exit.rb +8 -0
  43. data/lib/game_objects/game_object.rb +13 -0
  44. data/lib/game_objects/movable.rb +12 -0
  45. data/lib/game_objects/player.rb +17 -0
  46. data/lib/game_objects/room.rb +16 -0
  47. data/lib/game_objects/thing.rb +7 -0
  48. data/lib/interactions/notify.rb +11 -0
  49. data/lib/interactions/reboot.rb +8 -0
  50. data/lib/ipc/carbon_ipc_socket.rb +21 -0
  51. data/lib/ipc/ipc_message.rb +54 -0
  52. data/lib/ipc/read_socket.rb +18 -0
  53. data/lib/ipc/write_socket.rb +12 -0
  54. data/lib/version.rb +7 -0
  55. data/mongoid.yml +12 -0
  56. data/spec/carbonmu_spec.rb +14 -0
  57. data/spec/i18n_spec.rb +18 -0
  58. data/spec/lib/commands/say_command_spec.rb +11 -0
  59. data/spec/lib/core/command_context_spec.rb +16 -0
  60. data/spec/lib/core/command_spec.rb +37 -0
  61. data/spec/lib/core/configuration_spec.rb +37 -0
  62. data/spec/lib/core/connection_spec.rb +46 -0
  63. data/spec/lib/core/internationalization_spec.rb +24 -0
  64. data/spec/lib/core/parser_spec.rb +30 -0
  65. data/spec/lib/core/server_spec.rb +35 -0
  66. data/spec/lib/edge_router/edge_connection_spec.rb +9 -0
  67. data/spec/lib/game_objects/container_spec.rb +9 -0
  68. data/spec/lib/game_objects/exit_spec.rb +7 -0
  69. data/spec/lib/game_objects/game_object_spec.rb +12 -0
  70. data/spec/lib/game_objects/movable_spec.rb +9 -0
  71. data/spec/lib/game_objects/player_spec.rb +24 -0
  72. data/spec/lib/game_objects/room_spec.rb +20 -0
  73. data/spec/lib/game_objects/thing_spec.rb +7 -0
  74. data/spec/lib/interactions/notify_spec.rb +26 -0
  75. data/spec/lib/interactions/reboot_spec.rb +9 -0
  76. data/spec/lib/ipc/carbon_ipc_socket_spec.rb +12 -0
  77. data/spec/lib/ipc/ipc_message_spec.rb +39 -0
  78. data/spec/lib/ipc/read_socket_spec.bak +51 -0
  79. data/spec/lib/ipc/write_socket_spec.bak +25 -0
  80. data/spec/spec_helper.rb +118 -0
  81. data/spec/support/helpers.rb +15 -0
  82. data/spec/support/matchers/be_boolean.rb +5 -0
  83. data/spec/support/shared_examples/carbon_ipc_socket.rb +3 -0
  84. data/spec/support/shared_examples/container.rb +3 -0
  85. data/spec/support/shared_examples/game_object.rb +6 -0
  86. data/spec/support/shared_examples/movable.rb +4 -0
  87. data/start +5 -0
  88. data/start-server-only +8 -0
  89. metadata +416 -0
@@ -0,0 +1,5 @@
1
+ module CarbonMU
2
+ class EdgeRouterSupervisionGroup < Celluloid::SupervisionGroup
3
+ supervise EdgeRouter, as: :edge_router, args: ["0.0.0.0", 8421]
4
+ end
5
+ end
@@ -0,0 +1,35 @@
1
+ require 'forwardable'
2
+
3
+ module CarbonMU
4
+ class TelnetConnection < EdgeConnection
5
+ extend Forwardable
6
+ def_delegators :@socket, :close, :write
7
+
8
+ attr_reader :socket
9
+
10
+ def after_initialize(socket)
11
+ @socket = socket
12
+ async.run
13
+ end
14
+
15
+ def run
16
+ info "*** Received telnet connection #{id} from #{socket.addr[2]}"
17
+ write "Connected. Your ID is #{id}\n"
18
+ loop do
19
+ async.handle_input(read)
20
+ end
21
+ rescue EOFError, Errno::ECONNRESET
22
+ info "*** Telnet connection #{id} disconnected"
23
+ close
24
+ terminate
25
+ end
26
+
27
+ def before_shutdown
28
+ @socket.close unless @socket.closed?
29
+ end
30
+
31
+ def read
32
+ @socket.readpartial(4096)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,30 @@
1
+ require 'celluloid/autostart'
2
+ require 'celluloid/io'
3
+
4
+ module CarbonMU
5
+ class TelnetReceptor
6
+ include Celluloid::IO
7
+ include Celluloid::Logger
8
+
9
+ finalizer :shutdown
10
+
11
+ def initialize(host, port)
12
+ info "*** Starting Telnet receptor on #{host} #{port}."
13
+ @server = Celluloid::IO::TCPServer.new(host, port)
14
+ async.run
15
+ end
16
+
17
+ def shutdown
18
+ @server.close if @server
19
+ end
20
+
21
+ def run
22
+ loop { async.handle_connection @server.accept }
23
+ end
24
+
25
+ def handle_connection(socket)
26
+ tc = TelnetConnection.new(socket)
27
+ Actor[:edge_router].add_connection(tc)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,4 @@
1
+ module CarbonMU
2
+ class ObjectNotFoundError < StandardError; end
3
+ class InvalidObjectError < StandardError; end
4
+ end
@@ -0,0 +1,10 @@
1
+ module CarbonMU
2
+ module Container
3
+ extend ActiveSupport::Concern
4
+ included do
5
+ include Mongoid::Document
6
+
7
+ has_many :contents, class_name: "CarbonMU::GameObject", foreign_key: :location
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ module CarbonMU
2
+ class Exit < GameObject
3
+ include Mongoid::Document
4
+ include Movable
5
+
6
+ belongs_to :destination, class_name: "CarbonMU::Exit", inverse_of: :incoming_exits, index: true
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ module CarbonMU
2
+ class GameObject
3
+ include Mongoid::Document
4
+
5
+ field :name, type: String
6
+ field :description, type: String, default: "You see nothing special."
7
+ field :_special, type: Symbol
8
+
9
+ index(_special: 1)
10
+
11
+ validates_uniqueness_of :_special, allow_blank: true
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module CarbonMU
2
+ module Movable
3
+ extend ActiveSupport::Concern
4
+ included do
5
+ include Mongoid::Document
6
+
7
+ belongs_to :location, class_name: "CarbonMU::GameObject", inverse_of: :contents, index: true
8
+
9
+ validates_presence_of :location
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ module CarbonMU
2
+ class Player < GameObject
3
+ include Mongoid::Document
4
+ include Movable
5
+ include Container
6
+
7
+ before_validation :default_location
8
+
9
+ def default_location
10
+ self.location ||= Room.starting
11
+ end
12
+
13
+ def self.superadmin
14
+ find_by(_special: :superadmin_player)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ module CarbonMU
2
+ class Room < GameObject
3
+ include Mongoid::Document
4
+ include Container
5
+
6
+ has_many :incoming_exits, class_name: "CarbonMU::Exit", foreign_key: :destination
7
+
8
+ def self.starting
9
+ find_by(_special: :starting_room)
10
+ end
11
+
12
+ def self.lostandfound
13
+ find_by(_special: :lostandfound_room)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ module CarbonMU
2
+ class Thing < GameObject
3
+ include Mongoid::Document
4
+ include Movable
5
+ include Container
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module CarbonMU
2
+ class Notify
3
+ def self.all(text, opts = {})
4
+ CarbonMU.server.connections.each { |c| one(c, text, opts) }
5
+ end
6
+
7
+ def self.one(connection, text, opts = {})
8
+ connection.write_translated(text, opts)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ module CarbonMU
2
+ class Reboot
3
+ def self.reboot
4
+ Notify.all("SERVER: ".white.on_red + "Rebooting, please wait...")
5
+ Server.trigger_reboot
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,21 @@
1
+ require 'celluloid/zmq'
2
+
3
+ module CarbonMU
4
+ class CarbonIPCSocket
5
+ include Celluloid::ZMQ
6
+
7
+ attr_accessor :zmq_socket
8
+
9
+ def read
10
+ raise NotImplementedError
11
+ end
12
+
13
+ def send(message)
14
+ raise NotImplementedError
15
+ end
16
+
17
+ def close
18
+ @zmq_socket.close if @zmq_socket
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,54 @@
1
+ require 'multi_json'
2
+
3
+ module CarbonMU
4
+ class IPCMessage
5
+ def self.valid_ops
6
+ [:started, :command, :connect, :disconnect, :write, :reboot, :retrieve_existing_connections]
7
+ end
8
+
9
+ def self.required_parameters(op)
10
+ {
11
+ write: [:connection_id, :output],
12
+ connect: [:connection_id],
13
+ disconnect: [:connection_id],
14
+ command: [:command, :connection_id],
15
+ }[op]
16
+ end
17
+
18
+ def self.unserialize(text)
19
+ hash_with_symbol_keys = Hash[MultiJson.load(text).map{|(k,v)| [k.to_sym,v]}]
20
+ self.new(hash_with_symbol_keys[:op].to_sym, hash_with_symbol_keys)
21
+ end
22
+
23
+ attr_reader :params
24
+
25
+ def initialize(op, options={})
26
+ raise ArgumentError, "invalid op specified: #{op}" unless IPCMessage.valid_ops.include?(op)
27
+ required_parameters = IPCMessage.required_parameters(op) || []
28
+ provided_parameters = options.keys
29
+ raise ArgumentError, "missing parameters for #{op} op: #{required_parameters - provided_parameters}" unless (required_parameters & provided_parameters).size == required_parameters.size
30
+ @params = options.merge(op: op)
31
+ end
32
+
33
+ def method_missing(method_sym, *args, &block)
34
+ @params.has_key?(method_sym) ? @params[method_sym] : super
35
+ end
36
+
37
+ def respond_to?(method_sym, include_private = false)
38
+ @params.has_key?(method_sym) ? true : super
39
+ end
40
+
41
+ def serialize
42
+ MultiJson.dump(params)
43
+ end
44
+
45
+ def to_s
46
+ "<IPCMessage: #{params}>"
47
+ end
48
+
49
+ def ==(o)
50
+ o.class == self.class && o.params == self.params
51
+ end
52
+ alias_method :eql?, :==
53
+ end
54
+ end
@@ -0,0 +1,18 @@
1
+ module CarbonMU
2
+ class ReadSocket < CarbonIPCSocket
3
+ def initialize(port = '*')
4
+ @zmq_socket = Celluloid::ZMQ::PullSocket.new
5
+ @zmq_socket.bind("tcp://127.0.0.1:#{port}")
6
+ end
7
+
8
+ def read
9
+ @zmq_socket.read
10
+ end
11
+
12
+ def port_number
13
+ raw_endpoint = @zmq_socket.get(::ZMQ::LAST_ENDPOINT) || nil
14
+ return nil if raw_endpoint.nil?
15
+ raw_endpoint.match(/\:(\d+)/)[1].to_i
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ module CarbonMU
2
+ class WriteSocket < CarbonIPCSocket
3
+ def initialize(port = '15000')
4
+ @zmq_socket = Celluloid::ZMQ::PushSocket.new
5
+ @zmq_socket.connect("tcp://127.0.0.1:#{port}")
6
+ end
7
+
8
+ def send(message)
9
+ @zmq_socket.send(message)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ module CarbonMU
2
+ VERSION_MAJOR = 0
3
+ VERSION_MINOR = 0
4
+ VERSION_TINY = 1
5
+
6
+ VERSION = "#{VERSION_MAJOR}.#{VERSION_MINOR}.#{VERSION_TINY}"
7
+ end
@@ -0,0 +1,12 @@
1
+ production:
2
+ sessions:
3
+ default:
4
+ hosts:
5
+ - localhost
6
+ database: carbonmu
7
+ testing:
8
+ sessions:
9
+ default:
10
+ hosts:
11
+ - localhost
12
+ database: carbonmu_test
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe CarbonMU do
4
+ it "has color methods available" do
5
+ expect("string").to respond_to(:white)
6
+ end
7
+
8
+ it "has a translate method" do
9
+ str = "Hi there"
10
+ opts = {foo: "bar"}
11
+ expect(CarbonMU::Internationalization).to receive(:translate).with(str, [opts])
12
+ CarbonMU.t(str, opts)
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+ require 'i18n/tasks'
3
+
4
+ describe 'I18n', i18n: true do
5
+ let(:i18n) { I18n::Tasks::BaseTask.new }
6
+ let(:missing_keys) { i18n.missing_keys }
7
+ let(:unused_keys) { i18n.unused_keys }
8
+
9
+ skip 'does not have missing keys' do
10
+ expect(missing_keys).to be_empty,
11
+ "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them"
12
+ end
13
+
14
+ skip 'does not have unused keys' do
15
+ expect(unused_keys).to be_empty,
16
+ "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them"
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe SayCommand do
4
+ context "syntax" do
5
+ it { expect(command_parse_results("say foo")).to eq(parse_to(SayCommand, {text: "foo"})) }
6
+
7
+ it "requires an argument" do
8
+ expect(command_parse_results("say")).to eq(parse_to(UnknownCommand))
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe CommandContext do
4
+ it "should store its attributes" do
5
+ connection = double(Connection)
6
+ player = double(Player)
7
+ allow(connection).to receive(:player) { player }
8
+ command = "FOO"
9
+ params = {boo: "bot"}
10
+ cc = CommandContext.new(enacting_connection: connection, raw_command: command, params: params)
11
+ expect(cc.enacting_connection).to eq(connection)
12
+ expect(cc.enactor).to eq(player)
13
+ expect(cc.raw_command).to eq(command)
14
+ expect(cc.params).to eq(params)
15
+ end
16
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ class TestCommand < Command
4
+ syntax "bar"
5
+ syntax /foo/
6
+ end
7
+
8
+ describe Command do
9
+ it "keeps track of defined syntaxes" do
10
+ expect(TestCommand.syntaxes).to eq(["bar", /foo/])
11
+ end
12
+
13
+ it "sends syntaxes to parser" do
14
+ expect(Parser).to receive(:register_syntax).with("baz", duck_type(:syntax))
15
+ expect(Parser).to receive(:register_syntax).with("bat", duck_type(:syntax))
16
+
17
+ class ParseTestCommand < Command
18
+ syntax "baz"
19
+ syntax "bat"
20
+ end
21
+ end
22
+
23
+ context "command contexts" do
24
+ before(:each) do
25
+ @context = double("Context").as_null_object
26
+ end
27
+
28
+ it "stores its context" do
29
+ my_command = Command.new(@context)
30
+ expect(my_command.context).to eq(@context)
31
+ end
32
+
33
+ it "raises if .execute called directly on Command" do
34
+ expect { Command.new(@context).execute}.to raise_exception(NotImplementedError)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe CarbonMU do
4
+ describe 'global configuration' do
5
+ it 'maintains app-wide configurations' do
6
+ mock_config = Struct.new(:test_setting).new
7
+ mock_setting = Object.new
8
+ CarbonMU.configuration = mock_config
9
+
10
+ CarbonMU.configure do |config|
11
+ config.test_setting = mock_setting
12
+ end
13
+
14
+ expect(CarbonMU.configuration.test_setting).to be(mock_setting)
15
+ end
16
+ end
17
+ end
18
+
19
+ describe Configuration do
20
+ let(:config) { Configuration.new }
21
+
22
+ describe '#logger' do
23
+ it 'has a logger' do
24
+ expect(config.logger).to be_a(Logger)
25
+ end
26
+
27
+ it "doesn't allow an invalid logger" do
28
+ expect { config.logger = Object.new }.to raise_error(ArgumentError)
29
+ end
30
+ end
31
+
32
+ describe '#log_ipc_traffic' do
33
+ it 'has a log_ipc_traffic setting' do
34
+ expect(config.log_ipc_traffic).to be_boolean
35
+ end
36
+ end
37
+ end