colloquy 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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