mithril 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 (41) hide show
  1. data/CHANGELOG.md +31 -0
  2. data/README.md +0 -0
  3. data/bin/mithril +5 -0
  4. data/lib/mithril.rb +13 -0
  5. data/lib/mithril/controllers.rb +7 -0
  6. data/lib/mithril/controllers/abstract_controller.rb +130 -0
  7. data/lib/mithril/controllers/mixins.rb +7 -0
  8. data/lib/mithril/controllers/mixins/actions_base.rb +114 -0
  9. data/lib/mithril/controllers/mixins/help_actions.rb +46 -0
  10. data/lib/mithril/controllers/mixins/mixin_with_actions.rb +27 -0
  11. data/lib/mithril/controllers/proxy_controller.rb +89 -0
  12. data/lib/mithril/mixin.rb +33 -0
  13. data/lib/mithril/parsers.rb +7 -0
  14. data/lib/mithril/parsers/simple_parser.rb +57 -0
  15. data/lib/mithril/request.rb +11 -0
  16. data/lib/mithril/version.rb +5 -0
  17. data/spec/matchers/be_kind_of_spec.rb +50 -0
  18. data/spec/matchers/construct_spec.rb +49 -0
  19. data/spec/matchers/respond_to_spec.rb +158 -0
  20. data/spec/mithril/controllers/_text_controller_helper.rb +81 -0
  21. data/spec/mithril/controllers/abstract_controller_helper.rb +118 -0
  22. data/spec/mithril/controllers/abstract_controller_spec.rb +15 -0
  23. data/spec/mithril/controllers/mixins/actions_base_helper.rb +121 -0
  24. data/spec/mithril/controllers/mixins/actions_base_spec.rb +18 -0
  25. data/spec/mithril/controllers/mixins/help_actions_helper.rb +111 -0
  26. data/spec/mithril/controllers/mixins/help_actions_spec.rb +19 -0
  27. data/spec/mithril/controllers/mixins/mixin_with_actions_spec.rb +44 -0
  28. data/spec/mithril/controllers/proxy_controller_helper.rb +111 -0
  29. data/spec/mithril/controllers/proxy_controller_spec.rb +14 -0
  30. data/spec/mithril/mixin_helper.rb +54 -0
  31. data/spec/mithril/mixin_spec.rb +17 -0
  32. data/spec/mithril/parsers/simple_parser_spec.rb +85 -0
  33. data/spec/mithril/request_spec.rb +72 -0
  34. data/spec/mithril_spec.rb +25 -0
  35. data/spec/spec_helper.rb +15 -0
  36. data/spec/support/factories/action_factory.rb +7 -0
  37. data/spec/support/factories/request_factory.rb +11 -0
  38. data/spec/support/matchers/be_kind_of.rb +23 -0
  39. data/spec/support/matchers/construct.rb +49 -0
  40. data/spec/support/matchers/respond_to.rb +52 -0
  41. metadata +142 -0
@@ -0,0 +1,33 @@
1
+ # lib/mithril/mixin.rb
2
+
3
+ module Mithril
4
+ # Implements module-based inheritance of both class- and instance-level
5
+ # methods.
6
+ module Mixin
7
+ def mixins
8
+ @mixins ||= []
9
+ end # accessor mixins
10
+
11
+ def mixins=(ary)
12
+ @mixins = ary
13
+ end # mutator mixins
14
+
15
+ private
16
+ # Alternative to Module.extend that also provides inheritance of class-level
17
+ # methods defined through an (optional) ClassMethods module.
18
+ def mixin(source_module) # :doc:
19
+ include source_module
20
+
21
+ return unless source_module.respond_to? :mixins
22
+
23
+ self.mixins = source_module.mixins.dup || []
24
+ self.mixins << source_module
25
+
26
+ self.mixins.each do |mixin|
27
+ if mixin.const_defined? :ClassMethods
28
+ extend mixin::ClassMethods
29
+ end # if
30
+ end # each
31
+ end # method mixin
32
+ end # module Mixin
33
+ end # module
@@ -0,0 +1,7 @@
1
+ # lib/mithril/parsers.rb
2
+
3
+ require 'mithril'
4
+
5
+ module Mithril
6
+ module Parsers; end
7
+ end # module
@@ -0,0 +1,57 @@
1
+ # lib/mithril/parsers/simple_parser.rb
2
+
3
+ require 'mithril/parsers'
4
+
5
+ module Mithril::Parsers
6
+ class SimpleParser
7
+ def initialize(actions)
8
+ @actions = actions
9
+ end # method initialize
10
+
11
+ # Takes a string input and separates into words, then identifies a matching
12
+ # action (if any) and remaining arguments. Returns both the command and the
13
+ # arguments array, so usage can be as follows:
14
+ #
15
+ # @example With a "say" command:
16
+ # command, args = parse_command("say Greetings programs!")
17
+ # #=> command = :say, args = ["greetings", "programs"]
18
+ # @example With no "hello" command:
19
+ # command, args = parse_command("Hello world")
20
+ # #=> command = nil, args = ["hello", "world"]
21
+ #
22
+ # @param [String] text Expects a string composed of one or more words,
23
+ # separated by whitespace or hyphens.
24
+ # @return [Array] A two-element array consisting of the command and an
25
+ # array of the remaining text arguments (if any), or [nil, args] if no
26
+ # matching action was found.
27
+ def parse_command(text)
28
+ words = wordify preprocess_input text
29
+
30
+ key = nil
31
+ args = []
32
+
33
+ while 0 < words.count
34
+ key = words.join('_').intern
35
+
36
+ return key, args if @actions.has_action? key
37
+
38
+ args.unshift words.pop
39
+ end # while
40
+
41
+ return nil, args
42
+ end # method parse_command
43
+
44
+ private
45
+ # @!visibility public
46
+ def preprocess_input(text)
47
+ text.downcase.
48
+ gsub(/[\"?!\-',.:\(\)\[\]\;]/, ' ').
49
+ gsub(/(\s+)/, ' ').strip
50
+ end # method preprocess_input
51
+
52
+ # @!visibility public
53
+ def wordify(text)
54
+ text.split(/\s+/)
55
+ end # method wordify
56
+ end # class
57
+ end # module
@@ -0,0 +1,11 @@
1
+ # lib/mithril/request.rb
2
+
3
+ module Mithril
4
+ class Request
5
+ def initialize(session = {})
6
+ @session = session
7
+ end # constructor
8
+
9
+ attr_accessor :session, :input, :output
10
+ end # class Request
11
+ end # module
@@ -0,0 +1,5 @@
1
+ # lib/mithril/version.rb
2
+
3
+ module Mithril
4
+ VERSION = '0.2.0'
5
+ end # module Mithril
@@ -0,0 +1,50 @@
1
+ # spec/matchers/be_kind_of_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe RSpec::Matchers::BuiltIn::BeAKindOf do
6
+ let :custom_module do Module.new; end
7
+ let :custom_class do Class.new.tap { |c| c.send :include, custom_module }; end
8
+ let :custom_subclass do Class.new custom_class; end
9
+
10
+ let :string do "string"; end
11
+ let :module_instance do Object.new.extend custom_module; end
12
+ let :class_instance do custom_class.new; end
13
+ let :subclass_instance do custom_subclass.new; end
14
+
15
+ describe "with type" do
16
+ specify { expect(nil).to be_a NilClass }
17
+ specify { expect(nil).not_to be_a String }
18
+
19
+ specify { expect(string).to be_a String }
20
+ specify { expect(string).not_to be_a custom_class }
21
+
22
+ specify { expect(module_instance).to be_a custom_module }
23
+ specify { expect(module_instance).not_to be_a custom_class }
24
+
25
+ specify { expect(class_instance).to be_a custom_module }
26
+ specify { expect(class_instance).to be_a custom_class }
27
+ specify { expect(class_instance).not_to be_a String }
28
+
29
+ specify { expect(subclass_instance).to be_a custom_module }
30
+ specify { expect(subclass_instance).to be_a custom_class }
31
+ specify { expect(subclass_instance).to be_a custom_subclass }
32
+ specify { expect(subclass_instance).not_to be_a String }
33
+ end # describe
34
+
35
+ describe "with nil" do
36
+ specify { expect(nil).to be_a nil }
37
+ specify { expect(string).not_to be_a nil }
38
+ end # describe
39
+
40
+ describe "with array of types" do
41
+ specify { expect(nil).to be_a [String, nil] }
42
+ specify { expect(nil).not_to be_a [custom_class, String] }
43
+
44
+ specify { expect(string).to be_a [custom_module, String, nil] }
45
+ specify { expect(string).not_to be_a [custom_subclass, nil] }
46
+
47
+ specify { expect(subclass_instance).to be_a [custom_class, String] }
48
+ specify { expect(subclass_instance).not_to be_a [nil, String] }
49
+ end # describe
50
+ end # describe
@@ -0,0 +1,49 @@
1
+ # spec/matchers/construct_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe "construct matcher" do
6
+ let :class_with_no_arguments do Class.new; end
7
+ let :class_with_arguments do
8
+ Class.new do def initialize(a, b, c = nil); end; end
9
+ end # let
10
+ let :not_a_class do Object.new; end
11
+
12
+ specify { expect(class_with_no_arguments).to construct }
13
+ specify { expect(class_with_arguments).to construct }
14
+ specify { expect(not_a_class).not_to construct }
15
+
16
+ describe "with a fixed number of arguments" do
17
+ specify { expect(class_with_no_arguments).to construct.
18
+ with(0).arguments }
19
+ specify { expect(class_with_no_arguments).not_to construct.
20
+ with(1).arguments }
21
+
22
+ specify { expect(class_with_arguments).not_to construct.
23
+ with(1).arguments }
24
+ specify { expect(class_with_arguments).to construct.
25
+ with(2).arguments }
26
+ specify { expect(class_with_arguments).to construct.
27
+ with(3).arguments }
28
+ specify { expect(class_with_arguments).not_to construct.
29
+ with(4).arguments }
30
+ end # describe
31
+
32
+ describe "with a range of arguments" do
33
+ specify { expect(class_with_no_arguments).to construct.
34
+ with(0..0).arguments }
35
+ specify { expect(class_with_no_arguments).not_to construct.
36
+ with(0..1).arguments }
37
+ specify { expect(class_with_no_arguments).not_to construct.
38
+ with(1..2).arguments }
39
+
40
+ specify { expect(class_with_arguments).not_to construct.
41
+ with(1..4).arguments }
42
+ specify { expect(class_with_arguments).not_to construct.
43
+ with(1..3).arguments }
44
+ specify { expect(class_with_arguments).not_to construct.
45
+ with(2..4).arguments }
46
+ specify { expect(class_with_arguments).to construct.
47
+ with(2..3).arguments }
48
+ end # describe
49
+ end # describe
@@ -0,0 +1,158 @@
1
+ # spec/matchers/respond_to_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe RSpec::Matchers::BuiltIn::RespondTo do
6
+ let :described_class do
7
+ Class.new do
8
+ def method_with_no_arguments; end
9
+
10
+ def method_with_required_arguments(a, b, c); end
11
+
12
+ def method_with_optional_arguments(a, b, c = nil, d = nil); end
13
+
14
+ def method_with_variadic_arguments(a, b, c, *rest); end
15
+
16
+ def method_with_mixed_arguments(a, b, c = nil, d = nil, *rest); end
17
+
18
+ def method_with_yield; yield; end
19
+
20
+ def method_with_block_argument(&block); end
21
+
22
+ def method_with_block_and_mixed_arguments(a, b = nil, *rest, &block); end
23
+ end # class
24
+ end # let
25
+ let :instance do described_class.new; end
26
+
27
+ specify { expect(instance).to respond_to :method_with_no_arguments }
28
+ specify { expect(instance).to respond_to :method_with_required_arguments }
29
+ specify { expect(instance).to respond_to :method_with_optional_arguments }
30
+ specify { expect(instance).to respond_to :method_with_variadic_arguments }
31
+ specify { expect(instance).to respond_to :method_with_yield }
32
+ specify { expect(instance).to respond_to :method_with_mixed_arguments }
33
+ specify { expect(instance).to respond_to :method_with_block_argument }
34
+
35
+ specify { expect(instance).not_to respond_to :not_a_method }
36
+
37
+ describe "with a fixed number of arguments" do
38
+ specify { expect(instance).to respond_to(:method_with_no_arguments).
39
+ with(0).arguments }
40
+ specify { expect(instance).not_to respond_to(:method_with_no_arguments).
41
+ with(1).arguments }
42
+
43
+ specify { expect(instance).not_to respond_to(:method_with_required_arguments).
44
+ with(2).arguments }
45
+ specify { expect(instance).to respond_to(:method_with_required_arguments).
46
+ with(3).arguments }
47
+ specify { expect(instance).not_to respond_to(:method_with_required_arguments).
48
+ with(4).arguments }
49
+
50
+ specify { expect(instance).not_to respond_to(:method_with_optional_arguments).
51
+ with(1).arguments }
52
+ specify { expect(instance).to respond_to(:method_with_optional_arguments).
53
+ with(2).arguments }
54
+ specify { expect(instance).to respond_to(:method_with_optional_arguments).
55
+ with(3).arguments }
56
+ specify { expect(instance).to respond_to(:method_with_optional_arguments).
57
+ with(4).arguments }
58
+ specify { expect(instance).not_to respond_to(:method_with_optional_arguments).
59
+ with(5).arguments }
60
+
61
+ specify { expect(instance).not_to respond_to(:method_with_variadic_arguments).
62
+ with(2).arguments }
63
+ specify { expect(instance).to respond_to(:method_with_variadic_arguments).
64
+ with(3).arguments }
65
+ specify { expect(instance).to respond_to(:method_with_variadic_arguments).
66
+ with(4).arguments }
67
+ specify { expect(instance).to respond_to(:method_with_variadic_arguments).
68
+ with(9001).arguments } # IT'S OVER NINE THOUSAND!
69
+
70
+ specify { expect(instance).not_to respond_to(:method_with_mixed_arguments).
71
+ with(1).arguments }
72
+ specify { expect(instance).to respond_to(:method_with_mixed_arguments).
73
+ with(2).arguments }
74
+ specify { expect(instance).to respond_to(:method_with_mixed_arguments).
75
+ with(3).arguments }
76
+ specify { expect(instance).to respond_to(:method_with_mixed_arguments).
77
+ with(4).arguments }
78
+ specify { expect(instance).to respond_to(:method_with_mixed_arguments).
79
+ with(5).arguments }
80
+ specify { expect(instance).to respond_to(:method_with_mixed_arguments).
81
+ with(9001).arguments } # WHAT?! NINE THOUSAND!
82
+ end # describe
83
+
84
+ describe "with a range of arguments" do
85
+ specify { expect(instance).to respond_to(:method_with_no_arguments).
86
+ with(0..0).arguments }
87
+ specify { expect(instance).not_to respond_to(:method_with_no_arguments).
88
+ with(0..1).arguments }
89
+
90
+ specify { expect(instance).not_to respond_to(:method_with_required_arguments).
91
+ with(0..3).arguments }
92
+ specify { expect(instance).not_to respond_to(:method_with_required_arguments).
93
+ with(2..3).arguments }
94
+ specify { expect(instance).not_to respond_to(:method_with_required_arguments).
95
+ with(3..4).arguments }
96
+ specify { expect(instance).to respond_to(:method_with_required_arguments).
97
+ with(3..3).arguments }
98
+
99
+ specify { expect(instance).not_to respond_to(:method_with_optional_arguments).
100
+ with(0..4).arguments }
101
+ specify { expect(instance).not_to respond_to(:method_with_optional_arguments).
102
+ with(2..5).arguments }
103
+ specify { expect(instance).to respond_to(:method_with_optional_arguments).
104
+ with(2..2).arguments }
105
+ specify { expect(instance).to respond_to(:method_with_optional_arguments).
106
+ with(2..3).arguments }
107
+ specify { expect(instance).to respond_to(:method_with_optional_arguments).
108
+ with(2..4).arguments }
109
+ specify { expect(instance).to respond_to(:method_with_optional_arguments).
110
+ with(3..4).arguments }
111
+ specify { expect(instance).to respond_to(:method_with_optional_arguments).
112
+ with(4..4).arguments }
113
+
114
+ specify { expect(instance).not_to respond_to(:method_with_variadic_arguments).
115
+ with(0..4).arguments }
116
+ specify { expect(instance).not_to respond_to(:method_with_variadic_arguments).
117
+ with(2..4).arguments }
118
+ specify { expect(instance).to respond_to(:method_with_variadic_arguments).
119
+ with(3..4).arguments }
120
+ specify { expect(instance).to respond_to(:method_with_variadic_arguments).
121
+ with(5..9).arguments }
122
+ specify { expect(instance).to respond_to(:method_with_variadic_arguments).
123
+ with(3..9001).arguments } # VEGETA, WHAT DOES...NEVER MIND, JOKE'S OVER
124
+
125
+ specify { expect(instance).not_to respond_to(:method_with_mixed_arguments).
126
+ with(0..5).arguments }
127
+ specify { expect(instance).not_to respond_to(:method_with_mixed_arguments).
128
+ with(1..5).arguments }
129
+ specify { expect(instance).to respond_to(:method_with_mixed_arguments).
130
+ with(2..5).arguments }
131
+ specify { expect(instance).to respond_to(:method_with_mixed_arguments).
132
+ with(3..5).arguments }
133
+ specify { expect(instance).to respond_to(:method_with_mixed_arguments).
134
+ with(4..5).arguments }
135
+ specify { expect(instance).to respond_to(:method_with_mixed_arguments).
136
+ with(5..5).arguments }
137
+ specify { expect(instance).to respond_to(:method_with_mixed_arguments).
138
+ with(2..9001).arguments } # THERE'S NO WAY THAT CAN BE RIGHT. CAN IT?!?
139
+ end # describe
140
+
141
+ describe "with a block" do
142
+ specify { expect(instance).not_to respond_to(:method_with_no_arguments).
143
+ with.a_block }
144
+
145
+ specify { expect(instance).not_to respond_to(:method_with_yield).
146
+ with.a_block }
147
+
148
+ specify { expect(instance).to respond_to(:method_with_block_argument).
149
+ with.a_block }
150
+ specify { expect(instance).to respond_to(:method_with_block_argument).
151
+ with(0).arguments.and.a_block }
152
+
153
+ specify { expect(instance).to respond_to(:method_with_block_and_mixed_arguments).
154
+ with.a_block }
155
+ specify { expect(instance).to respond_to(:method_with_block_and_mixed_arguments).
156
+ with(2..9001).arguments.and.a_block } # FINE, FINE. KILLJOYS.
157
+ end # describe
158
+ end # describe
@@ -0,0 +1,81 @@
1
+ # spec/controllers/text_controller_helper.rb
2
+
3
+ require 'mithril/controllers/abstract_controller_helper'
4
+
5
+ shared_examples_for "Mithril::Controllers::_TextController" do
6
+ it_behaves_like Mithril::Controllers::AbstractController
7
+
8
+ let :input do "text input"; end
9
+
10
+ describe :parser do
11
+ specify { expect(instance.parser).to be_a Mithril::Parsers::SimpleParser }
12
+ end # describe
13
+
14
+ describe :parse_command do
15
+ specify { expect(instance.parse_command input).to be_a Array }
16
+
17
+ specify "returns nil" do
18
+ command, args = instance.parse_command input
19
+ expect(command).to be nil
20
+ end # specify
21
+ end # describe
22
+
23
+ describe :command_missing do
24
+ specify "returns the text with a helpful message" do
25
+ output = instance.command_missing input
26
+ expect(output).to match /don't know how/
27
+ expect(output).to match /#{input}/
28
+ end # specify
29
+ end # describe
30
+
31
+ describe :invoke_command do
32
+ specify "returns the text with a helpful message" do
33
+ output = instance.invoke_command input
34
+ expect(output).to match /don't know how/
35
+ expect(output).to match /#{input}/
36
+ end # specify
37
+ end # describe
38
+
39
+ context "with actions defined" do
40
+ let :command do FactoryGirl.generate :action_key; end
41
+ let :arguments do %w(arguments for command); end
42
+
43
+ before :each do
44
+ described_class.define_action command do |session, args| args.join(' '); end
45
+ end # before each
46
+
47
+ specify { expect(instance.can_invoke? command.to_s).to be true }
48
+
49
+ describe :invoke_command do
50
+ let :text do "#{command} #{arguments.join(' ')}"; end
51
+
52
+ specify "invokes matching action" do
53
+ instance.should_receive(:invoke_action).with(command, arguments).and_call_original
54
+ instance.should_receive(:"action_#{command}").with(request.session, arguments).and_call_original
55
+ instance.invoke_command text
56
+ end # specify
57
+
58
+ specify { expect(instance.invoke_command text).to eq arguments.join(' ') }
59
+
60
+ context "with a non-matching command" do
61
+ let :non_matching do FactoryGirl.generate :action_key; end
62
+
63
+ specify { expect(instance.invoke_command non_matching.to_s).to match /don't know how/i }
64
+ end # context
65
+ end # describe
66
+ end # context
67
+
68
+ describe "empty actions" do
69
+ def self.input
70
+ chars, words = [*"a".."z", *"0".."9"], [""] * 5
71
+ 5.times do |i|
72
+ word = ""
73
+ (6 + rand(10)).times do word << chars[rand(36)]; end
74
+ words[i] = word
75
+ end # times
76
+ words.join " "
77
+ end # helper input
78
+
79
+ it_behaves_like "Mithril::Controllers::AbstractController#empty_actions", input
80
+ end # describe
81
+ end # describe