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.
- checksums.yaml +15 -0
- data/README.md +160 -0
- data/TODO.md +256 -0
- data/bin/colloquy +14 -0
- data/examples/config/flows.yaml +22 -0
- data/examples/config/logger.yaml +2 -0
- data/examples/config/messages.yaml +3 -0
- data/examples/config/mysql.yaml +18 -0
- data/examples/config/redis.yaml +9 -0
- data/examples/config/scribe.yaml +2 -0
- data/examples/config/settings.yaml +2 -0
- data/examples/config/test.yaml +8 -0
- data/examples/config/urls.yaml +18 -0
- data/examples/flows/active_record_flow.rb +42 -0
- data/examples/flows/art_of_war_flow.rb +27 -0
- data/examples/flows/calculator_flow.rb +71 -0
- data/examples/flows/crossover_flow.rb +17 -0
- data/examples/flows/database_flow.rb +33 -0
- data/examples/flows/hangman_flow.rb +82 -0
- data/examples/flows/metadata_flow.rb +11 -0
- data/examples/flows/pagination_flow.rb +23 -0
- data/examples/flows/pass_flow.rb +29 -0
- data/examples/flows/prefix_menu_flow.rb +24 -0
- data/examples/flows/scribe_flow.rb +26 -0
- data/examples/flows/settings_flow.rb +23 -0
- data/examples/flows/special/special_redis_flow.rb +28 -0
- data/examples/flows/url_flow.rb +27 -0
- data/examples/log/renderer.log +198381 -0
- data/examples/log/urls.log +3269 -0
- data/examples/messages/active_record.yaml +2 -0
- data/examples/messages/art_of_war.yaml +1 -0
- data/examples/messages/calculator.yaml +2 -0
- data/examples/messages/database.yaml +1 -0
- data/examples/messages/hangman.yaml +0 -0
- data/examples/messages/prefix_menu.yaml +3 -0
- data/examples/models/activations.rb +5 -0
- data/lib/colloquy.rb +39 -0
- data/lib/colloquy/exceptions.rb +64 -0
- data/lib/colloquy/flow_parser.rb +315 -0
- data/lib/colloquy/flow_pool.rb +21 -0
- data/lib/colloquy/helpers.rb +15 -0
- data/lib/colloquy/helpers/mysql.rb +110 -0
- data/lib/colloquy/helpers/redis.rb +103 -0
- data/lib/colloquy/helpers/scribe.rb +103 -0
- data/lib/colloquy/helpers/settings.rb +111 -0
- data/lib/colloquy/helpers/url.rb +10 -0
- data/lib/colloquy/input.rb +14 -0
- data/lib/colloquy/logger.rb +11 -0
- data/lib/colloquy/menu.rb +128 -0
- data/lib/colloquy/message_builder.rb +54 -0
- data/lib/colloquy/node.rb +67 -0
- data/lib/colloquy/paginator.rb +59 -0
- data/lib/colloquy/paginator/menu.rb +79 -0
- data/lib/colloquy/paginator/prompt.rb +32 -0
- data/lib/colloquy/prompt.rb +35 -0
- data/lib/colloquy/renderer.rb +423 -0
- data/lib/colloquy/response.rb +4 -0
- data/lib/colloquy/runner.rb +93 -0
- data/lib/colloquy/server.rb +157 -0
- data/lib/colloquy/session_store.rb +46 -0
- data/lib/colloquy/session_store/memory.rb +14 -0
- data/lib/colloquy/session_store/redis.rb +24 -0
- data/lib/colloquy/simulator.rb +114 -0
- data/lib/colloquy/spec_helpers.rb +21 -0
- metadata +459 -0
@@ -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
|