colloquy 1.0.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 (65) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +160 -0
  3. data/TODO.md +256 -0
  4. data/bin/colloquy +14 -0
  5. data/examples/config/flows.yaml +22 -0
  6. data/examples/config/logger.yaml +2 -0
  7. data/examples/config/messages.yaml +3 -0
  8. data/examples/config/mysql.yaml +18 -0
  9. data/examples/config/redis.yaml +9 -0
  10. data/examples/config/scribe.yaml +2 -0
  11. data/examples/config/settings.yaml +2 -0
  12. data/examples/config/test.yaml +8 -0
  13. data/examples/config/urls.yaml +18 -0
  14. data/examples/flows/active_record_flow.rb +42 -0
  15. data/examples/flows/art_of_war_flow.rb +27 -0
  16. data/examples/flows/calculator_flow.rb +71 -0
  17. data/examples/flows/crossover_flow.rb +17 -0
  18. data/examples/flows/database_flow.rb +33 -0
  19. data/examples/flows/hangman_flow.rb +82 -0
  20. data/examples/flows/metadata_flow.rb +11 -0
  21. data/examples/flows/pagination_flow.rb +23 -0
  22. data/examples/flows/pass_flow.rb +29 -0
  23. data/examples/flows/prefix_menu_flow.rb +24 -0
  24. data/examples/flows/scribe_flow.rb +26 -0
  25. data/examples/flows/settings_flow.rb +23 -0
  26. data/examples/flows/special/special_redis_flow.rb +28 -0
  27. data/examples/flows/url_flow.rb +27 -0
  28. data/examples/log/renderer.log +198381 -0
  29. data/examples/log/urls.log +3269 -0
  30. data/examples/messages/active_record.yaml +2 -0
  31. data/examples/messages/art_of_war.yaml +1 -0
  32. data/examples/messages/calculator.yaml +2 -0
  33. data/examples/messages/database.yaml +1 -0
  34. data/examples/messages/hangman.yaml +0 -0
  35. data/examples/messages/prefix_menu.yaml +3 -0
  36. data/examples/models/activations.rb +5 -0
  37. data/lib/colloquy.rb +39 -0
  38. data/lib/colloquy/exceptions.rb +64 -0
  39. data/lib/colloquy/flow_parser.rb +315 -0
  40. data/lib/colloquy/flow_pool.rb +21 -0
  41. data/lib/colloquy/helpers.rb +15 -0
  42. data/lib/colloquy/helpers/mysql.rb +110 -0
  43. data/lib/colloquy/helpers/redis.rb +103 -0
  44. data/lib/colloquy/helpers/scribe.rb +103 -0
  45. data/lib/colloquy/helpers/settings.rb +111 -0
  46. data/lib/colloquy/helpers/url.rb +10 -0
  47. data/lib/colloquy/input.rb +14 -0
  48. data/lib/colloquy/logger.rb +11 -0
  49. data/lib/colloquy/menu.rb +128 -0
  50. data/lib/colloquy/message_builder.rb +54 -0
  51. data/lib/colloquy/node.rb +67 -0
  52. data/lib/colloquy/paginator.rb +59 -0
  53. data/lib/colloquy/paginator/menu.rb +79 -0
  54. data/lib/colloquy/paginator/prompt.rb +32 -0
  55. data/lib/colloquy/prompt.rb +35 -0
  56. data/lib/colloquy/renderer.rb +423 -0
  57. data/lib/colloquy/response.rb +4 -0
  58. data/lib/colloquy/runner.rb +93 -0
  59. data/lib/colloquy/server.rb +157 -0
  60. data/lib/colloquy/session_store.rb +46 -0
  61. data/lib/colloquy/session_store/memory.rb +14 -0
  62. data/lib/colloquy/session_store/redis.rb +24 -0
  63. data/lib/colloquy/simulator.rb +114 -0
  64. data/lib/colloquy/spec_helpers.rb +21 -0
  65. metadata +459 -0
@@ -0,0 +1,14 @@
1
+
2
+ class Colloquy::Input < String
3
+ attr_accessor :direct
4
+
5
+ def initialize(input)
6
+ @direct = input.respond_to?(:direct) ? input.direct : false
7
+
8
+ super(input.to_s)
9
+ end
10
+
11
+ def direct?
12
+ @direct
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ require 'active_support/logger'
2
+ require 'active_support/core_ext/date_time'
3
+
4
+ class Colloquy::Logger < ActiveSupport::Logger
5
+
6
+ class ActiveSupport::Logger::SimpleFormatter
7
+ def call(severity, time, progname, msg)
8
+ msg = "#{Time.now.to_formatted_s(:db)}, #{severity} #{msg.strip}\n"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,128 @@
1
+
2
+ class Colloquy::Menu
3
+ include Colloquy::Paginator
4
+
5
+ attr_accessor :keys, :flow
6
+
7
+ def initialize(options = {})
8
+ @flow = options[:flow] || nil
9
+ @page = options[:page] || 1
10
+ @prefix = options[:prefix]
11
+ @suffix = options[:suffix]
12
+ @keys = []
13
+ end
14
+
15
+ def render(page = @page)
16
+ paginate unless @pages
17
+
18
+ assemble_prefix
19
+ assemble_suffix
20
+
21
+ execute_before_page_hook
22
+ "#{render_prefix}#{render_body(page)}#{render_suffix}"
23
+ end
24
+
25
+ def key(input, page = @page)
26
+ state = @flow.send(:state)
27
+
28
+ return false unless input.to_i.to_s == input
29
+ return false if input.to_i > @pages[page - 1].size
30
+
31
+ key = if @pages
32
+ @pages[page - 1][input.to_i - 1]
33
+ end
34
+
35
+ if key.respond_to? :push
36
+ key.first
37
+ else
38
+ key
39
+ end
40
+ end
41
+
42
+ def push(*args)
43
+ @keys.push(*args)
44
+ end
45
+
46
+ def <<(symbol)
47
+ @keys << symbol
48
+
49
+ self
50
+ end
51
+
52
+ def empty?
53
+ @keys.empty?
54
+ end
55
+
56
+ def [](symbol)
57
+ @keys[symbol]
58
+ end
59
+
60
+ def freeze
61
+ @keys.freeze
62
+ end
63
+
64
+ def prefix(&block)
65
+ @prefix_block = block
66
+ end
67
+
68
+ def suffix(&block)
69
+ @suffix_block = block
70
+ end
71
+
72
+ def prefix=(string)
73
+ @prefix = string
74
+ end
75
+
76
+ def suffix=(string)
77
+ @suffix = string
78
+ end
79
+
80
+ def reset!
81
+ @keys = []
82
+ @pages = nil
83
+ @page = 1
84
+ @prefix = nil
85
+ @suffix = nil
86
+ end
87
+
88
+ private
89
+ def assemble_prefix(options = {})
90
+ @flow.tap do |f|
91
+ current_page_for_menu = f.headers[:page]
92
+ f.headers[:page] = options[:page] if options[:page]
93
+
94
+ @prefix = f.instance_eval &@prefix_block if @prefix_block
95
+
96
+ f.headers[:page] = current_page_for_menu
97
+ end
98
+ end
99
+
100
+ def assemble_suffix(options = {})
101
+ @flow.tap do |f|
102
+ current_page_for_menu = f.headers[:page]
103
+ f.headers[:page] = options[:page] if options[:page]
104
+
105
+ @suffix = @flow.instance_eval &@suffix_block if @suffix_block
106
+
107
+ f.headers[:page] = current_page_for_menu
108
+ end
109
+ end
110
+
111
+ def assemble
112
+ @assembled_strings = @keys.collect do |item|
113
+ render_each(item)
114
+ end
115
+ end
116
+
117
+ def render_each(item)
118
+ "#{Colloquy::MessageBuilder.to_message(item, :flow => @flow)}"
119
+ end
120
+
121
+ def render_prefix
122
+ @prefix ? "#{@prefix}\n" : ""
123
+ end
124
+
125
+ def render_suffix
126
+ @suffix ? "\n#{@suffix}" : ""
127
+ end
128
+ end
@@ -0,0 +1,54 @@
1
+
2
+ class Colloquy::MessageBuilder
3
+ class << self
4
+ def to_message(message_emergent, options = {})
5
+ message_emergent, parameters = extract_parameters(message_emergent)
6
+ message = replace_from_options(message_emergent, options)
7
+ message = substitute_parameters(message, parameters)
8
+ message.to_s
9
+ end
10
+
11
+ private
12
+ def extract_parameters(message_emergent)
13
+ parameters = nil
14
+
15
+ if message_emergent.respond_to? :push
16
+ parameters = message_emergent.last
17
+ message_emergent = message_emergent.first
18
+ end
19
+
20
+ [message_emergent, parameters]
21
+ end
22
+
23
+ def replace_from_options(message_emergent, options)
24
+ messages = if options[:flow]
25
+ options[:flow].messages
26
+ else
27
+ options[:messages]
28
+ end
29
+
30
+ # try a direct match
31
+ message = messages["#{message_emergent}".to_sym] if messages
32
+
33
+ # try a match converting underscores to hash indices
34
+ unless message
35
+ message =
36
+ begin
37
+ message_emergent_as_keys = message_emergent.to_s.split('_').map(&:to_sym)
38
+
39
+ message_emergent_as_keys.inject(messages) { |message_substituted, key| message_substituted[key] }
40
+ rescue TypeError
41
+ nil
42
+ rescue NoMethodError
43
+ nil
44
+ end
45
+ end
46
+
47
+ message || "#{message_emergent}"
48
+ end
49
+
50
+ def substitute_parameters(message, parameters)
51
+ message % parameters
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,67 @@
1
+ class Colloquy::Node
2
+ attr_reader :identifier, :options
3
+ attr_writer :flow
4
+
5
+ def initialize(identifier = :index, options = {}, &block)
6
+ @identifier = identifier.to_sym
7
+ @flow = options[:flow] || nil
8
+ @options = options.delete(:flow)
9
+
10
+ instance_eval &block if block
11
+ end
12
+
13
+ def prompt(message)
14
+ @prompt = Colloquy::Prompt.new(:message => message, :flow => @flow)
15
+ end
16
+
17
+ def prompt?
18
+ @prompt && !@prompt.blank?
19
+ end
20
+
21
+ def menu?
22
+ @menu && !@menu.empty?
23
+ end
24
+
25
+ def menu
26
+ @menu ||= Colloquy::Menu.new(:flow => @flow)
27
+ end
28
+
29
+ def render
30
+ if @prompt
31
+ @prompt.render
32
+ elsif !menu.empty?
33
+ menu.render
34
+ else
35
+ ""
36
+ end.strip
37
+ end
38
+
39
+ def method_missing(method, *arguments, &block)
40
+ if @flow and @flow.respond_to? method
41
+ @flow.send(method, *arguments, &block)
42
+ else
43
+ raise NoMethodError, "#{method} is missing in the flow"
44
+ end
45
+ end
46
+
47
+ def request(&block)
48
+ @request_block = block
49
+ end
50
+
51
+ def request!(input = nil)
52
+ @request_block.call(input) if @request_block
53
+ end
54
+
55
+ def process(&block)
56
+ @process_block = block
57
+ end
58
+
59
+ def process!(input = nil)
60
+ @process_block.call(input) if @process_block
61
+ end
62
+
63
+ def reset!
64
+ @prompt = nil
65
+ menu.reset!
66
+ end
67
+ end
@@ -0,0 +1,59 @@
1
+
2
+ module Colloquy::Paginator
3
+ def self.included(klass)
4
+ if defined? Colloquy::Menu and klass == Colloquy::Menu
5
+ require_relative 'paginator/menu'
6
+
7
+ klass.class_eval do
8
+ include Colloquy::Paginator::Menu
9
+ end
10
+ elsif defined? Colloquy::Prompt and klass == Colloquy::Prompt
11
+ require_relative 'paginator/prompt'
12
+
13
+ klass.class_eval do
14
+ include Colloquy::Paginator::Prompt
15
+ end
16
+ end
17
+
18
+ klass.class_eval do
19
+ attr_accessor :page, :pages
20
+
21
+ include Colloquy::Paginator::Common
22
+ end
23
+ end
24
+
25
+ module Common
26
+ def total_pages
27
+ paginate unless @pages
28
+
29
+ @pages.length
30
+ end
31
+
32
+ def page_available?(page)
33
+ paginate unless @pages
34
+
35
+ page <= @pages.length && page > 0
36
+ end
37
+
38
+ def paginate!
39
+ paginate
40
+ end
41
+
42
+ def before_page(&block)
43
+ @before_page_block = block
44
+ end
45
+
46
+ def maximum_response_length(message)
47
+ if message.to_s.ascii_only?
48
+ Colloquy.maximum_message_length
49
+ else
50
+ Colloquy.maximum_unicode_length
51
+ end
52
+ end
53
+
54
+ private
55
+ def execute_before_page_hook(&block)
56
+ @flow.instance_eval &@before_page_block if @before_page_block
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,79 @@
1
+
2
+ module Colloquy::Paginator::Menu
3
+ private
4
+ def paginate
5
+ assemble unless @assembled_strings
6
+
7
+ @pages = []
8
+
9
+ if @assembled_strings.join("\n").length + (3 * @assembled_strings.length) < allowed_menu_length(:without_more => true)
10
+ @pages << @keys.compact
11
+ else
12
+ accumulator = []
13
+ accumulator_keys = []
14
+
15
+ @assembled_strings.zip(@keys).each do |string, key|
16
+ accumulator << string
17
+ accumulator_keys << key
18
+
19
+ if accumulator.join("\n").length + (3 * accumulator.length) > allowed_menu_length(:page => @pages.length + 1)
20
+ if @pages.empty? or render_previous.empty?
21
+ @pages << accumulator_keys[0..-2].compact.push(:more)
22
+ else
23
+ @pages << accumulator_keys[0..-2].compact.push(:previous).push(:more)
24
+ end
25
+ accumulator = [accumulator[-1]]
26
+ accumulator_keys = [accumulator_keys[-1]]
27
+ end
28
+ end
29
+
30
+ total_options_in_menu = @pages.flatten.length - (@pages.length * 2) + 1
31
+
32
+ if total_options_in_menu < @assembled_strings.length
33
+ @pages << @keys[total_options_in_menu..-1]
34
+ @pages.last << :previous unless render_previous.empty?
35
+ end
36
+ end
37
+
38
+ @assembled_strings = nil
39
+
40
+ @pages
41
+ end
42
+
43
+ def allowed_menu_length(options = {})
44
+ # This comes last, and is of the form:
45
+ # "\nX. <More Text>"
46
+ more_length = 1 + 3 + "#{render_more}".length
47
+ previous_length = 1 + 3 + "#{render_previous}".length
48
+
49
+ more_length = 0 if options[:without_more]
50
+ previous_length = 0 if options[:without_more]
51
+ previous_length = 0 if options[:page] == 1
52
+
53
+
54
+ # Assemble the prefix and suffix again because they can change based on the page
55
+ assemble_prefix(:page => options[:page] || 1)
56
+ assemble_suffix(:page => options[:page] || 1)
57
+
58
+ # maximum_message_length is from the renderer!
59
+ maximum_response_length(@assembled_strings) - (render_prefix.length + render_suffix.length + more_length + previous_length)
60
+ end
61
+
62
+ def render_body(page = 1)
63
+ menu = @pages[page - 1].dup
64
+
65
+ menu = menu.each_with_index.map do |option, index|
66
+ "#{index + 1}. #{render_each(option)}"
67
+ end
68
+
69
+ menu.join("\n")
70
+ end
71
+
72
+ def render_more
73
+ @rendered_more ||= Colloquy::MessageBuilder.to_message(:more, :flow => @flow)
74
+ end
75
+
76
+ def render_previous
77
+ @rendered_previous ||= Colloquy::MessageBuilder.to_message(:previous, :flow => @flow)
78
+ end
79
+ end
@@ -0,0 +1,32 @@
1
+
2
+ module Colloquy::Paginator::Prompt
3
+ private
4
+ def paginate
5
+
6
+ message_body = Colloquy::MessageBuilder.to_message(@message, flow: @flow)
7
+
8
+ allowed_length = maximum_response_length(message_body)
9
+
10
+ @pages = []
11
+
12
+ if message_body.length < allowed_length
13
+ @pages << message_body
14
+ else
15
+ # allowed message length is from renderer; depends on character encoding
16
+ length = allowed_length - (render_more.length + 3 + 1)
17
+ @pages = message_body.squeeze(' ').scan(/\b.{1,#{length}}(?:$|\b)/m)
18
+
19
+ @pages[0..-2].map! { |page| page << "\n1. " << render_more }
20
+
21
+ @pages
22
+ end
23
+ end
24
+
25
+ def render_body(page)
26
+ @pages[page - 1]
27
+ end
28
+
29
+ def render_more
30
+ @rendered_more ||= Colloquy::MessageBuilder.to_message(:more, flow: @flow)
31
+ end
32
+ end