stealth 1.0.0.pre1 → 1.0.0.pre2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a447e930b543fe9125bb09baaaf12f160f5fa715e346fd6b304285f5a31f56f2
4
- data.tar.gz: f5ff084dcb475b87ff972458751b16cc05779bed6eebf67f3feb15ba97909d68
3
+ metadata.gz: 1c9ae069b8bf53745259b83be47c652d625fb4c6f0c897c72dda88aa30696032
4
+ data.tar.gz: bed60f67d25cf795dff6e469f249c5c527dd008f56df5820502492450d4b8e8d
5
5
  SHA512:
6
- metadata.gz: ad24b962addcbe10da90dc44a8b0220c7ced906c42a525f45d37f4ee9baad43600b968cae7fdabc2f5a5cad4279718203ec49680a396c6fa8b24429a6f8dc891
7
- data.tar.gz: 207b7e854c327ef050a013017da9e284d820f3cac5819af8558ea9b7a86c8f252bef1fb7fe9bd8741fb5119d56be8523ae19930f0403d33f3e7b99e11ba132ab
6
+ metadata.gz: 012b9b948b15255b2fe2d8ae068e33d41cce1a398429b32df46f5e2fd656ed73374a488e22cbbb997921cc0913c4b9df84ad707e339427777ee3652b94435f8a
7
+ data.tar.gz: ea5cf2ce7fbe4858d3fba6bc5aa3535150ab32c9955aaa95783fd3e51908655b949093223d2b2a715e47177c321a2e91c18246cf249534e4c817a7714e690343
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- stealth (0.10.6)
4
+ stealth (1.0.0.pre1)
5
5
  activesupport (~> 5.2.0)
6
6
  multi_json (~> 1.12)
7
7
  puma (~> 3.10)
@@ -20,14 +20,14 @@ GEM
20
20
  concurrent-ruby (1.0.5)
21
21
  connection_pool (2.2.1)
22
22
  diff-lcs (1.3)
23
- i18n (1.0.0)
23
+ i18n (1.0.1)
24
24
  concurrent-ruby (~> 1.0)
25
25
  minitest (5.11.3)
26
26
  mock_redis (0.17.3)
27
27
  multi_json (1.13.1)
28
28
  mustermann (1.0.2)
29
29
  oj (3.5.0)
30
- puma (3.11.3)
30
+ puma (3.11.4)
31
31
  rack (2.0.4)
32
32
  rack-protection (2.0.1)
33
33
  rack
data/README.md CHANGED
@@ -21,6 +21,9 @@ Currently, there are gems for:
21
21
  * [Facebook Messenger](https://github.com/hellostealth/stealth-facebook)
22
22
  * [Twilio SMS](https://github.com/hellostealth/stealth-twilio)
23
23
 
24
+ ### Natural Language Processing
25
+ * [AWS Comprehend](https://github.com/hellostealth/stealth-aws-comprehend)
26
+
24
27
  ### Analytics
25
28
  * [Mixpanel](https://github.com/hellostealth/stealth-mixpanel)
26
29
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0.pre1
1
+ 1.0.0.pre2
data/lib/stealth/base.rb CHANGED
@@ -22,25 +22,7 @@ module Stealth
22
22
  @root ||= File.expand_path(Pathname.new(Dir.pwd))
23
23
  end
24
24
 
25
- def self.reloader
26
- @reloader ||= begin
27
- if Stealth.env == 'development'
28
- ActiveSupport::Dependencies.mechanism = :load
29
- ActiveSupport::Dependencies.autoload_paths = Dir["bot/**/*.rb"]
30
- # @reloader = ActiveSupport::FileUpdateChecker.new(Dir["bot/**/*.rb"]) do
31
- # puts "YAAASSS"
32
- # end
33
- # ActiveSupport::Dependencies.autoload_paths = %w[bot/controllers bot/helpers bot/models]
34
- # ActiveSupport::Dependencies.autoload_paths = [File.join(Stealth.root, "bot")]
35
- end
36
- end
37
- end
38
-
39
25
  def self.boot
40
- if Stealth.env == 'development'
41
- ActiveSupport::Dependencies.mechanism = :load
42
- ActiveSupport::Dependencies.autoload_paths = Dir["bot/**/*.rb"]
43
- end
44
26
  load_environment
45
27
  end
46
28
 
@@ -76,14 +58,15 @@ module Stealth
76
58
  require File.join(Stealth.root, 'config', 'boot')
77
59
  require_directory("config/initializers")
78
60
  # Require explicitly to ensure it loads first
79
- require_dependency File.join(Stealth.root, 'bot', 'controllers', 'bot_controller')
61
+ require File.join(Stealth.root, 'bot', 'controllers', 'bot_controller')
62
+ require File.join(Stealth.root, 'config', 'flow_map')
80
63
  require_directory("bot")
81
64
  end
82
65
 
83
66
  private
84
67
 
85
68
  def self.require_directory(directory)
86
- for_each_file_in(directory) { |file| require_dependency(file) }
69
+ for_each_file_in(directory) { |file| require_relative(file) }
87
70
  end
88
71
 
89
72
  def self.for_each_file_in(directory, &blk)
data/lib/stealth/cli.rb CHANGED
@@ -30,6 +30,7 @@ module Stealth
30
30
  def generate(generator, name)
31
31
  Stealth::Generators::Generate.start([generator, name])
32
32
  end
33
+ map 'g' => 'generate'
33
34
 
34
35
  desc 'version', 'Prints stealth version'
35
36
  long_desc <<-EOS
@@ -51,24 +52,16 @@ module Stealth
51
52
  $ > stealth server -p 4500
52
53
  EOS
53
54
  method_option :port, aliases: '-p', desc: 'The port to run the server on'
54
- method_option :server, desc: 'Choose a specific Rack::Handler (webrick, thin, etc)'
55
- method_option :rackup, desc: 'A rackup configuration file path to load (config.ru)'
56
- method_option :host, desc: 'The host address to bind to'
57
- method_option :debug, desc: 'Turn on debug output'
58
- method_option :warn, desc: 'Turn on warnings'
59
- method_option :daemonize, desc: 'If true, the server will daemonize itself (fork, detach, etc)'
60
- method_option :pid, desc: 'Path to write a pid file after daemonize'
61
- method_option :environment, desc: 'Path to environment configuration (config/environment.rb)'
62
- method_option :code_reloading, desc: 'Code reloading', type: :boolean, default: true
63
55
  method_option :help, desc: 'Displays the usage message'
64
56
  def server
65
57
  if options[:help]
66
58
  invoke :help, ['server']
67
59
  else
68
60
  require 'stealth/commands/server'
69
- Stealth::Commands::Server.new(options).start
61
+ Stealth::Commands::Server.new(port: options.fetch(:port) { 5000 }).start
70
62
  end
71
63
  end
64
+ map 's' => 'server'
72
65
 
73
66
 
74
67
  desc 'console', 'Starts a stealth console'
@@ -87,6 +80,7 @@ module Stealth
87
80
  Stealth::Commands::Console.new(options).start
88
81
  end
89
82
  end
83
+ map 'c' => 'console'
90
84
 
91
85
 
92
86
  desc 'setup', 'Runs setup tasks for a specified service'
@@ -101,16 +95,16 @@ module Stealth
101
95
  service_setup_klass.trigger
102
96
  end
103
97
 
104
-
105
- desc 'clear_sessions', 'Clears all sessions in development'
98
+ desc 'sessions:clear', 'Clears all sessions in development'
106
99
  long_desc <<-EOS
107
- `stealth clear_sessions` clears all sessions from Redis in development.
100
+ `stealth sessions:clear` clears all sessions from Redis in development.
108
101
 
109
- $ > stealth clear_sessions
102
+ $ > stealth sessions:clear
110
103
  EOS
111
- def clear_sessions
104
+ define_method 'sessions:clear' do
112
105
  Stealth.load_environment
113
- $redis.flushdb if ENV['STEALTH_ENV'] == 'development'
106
+ $redis.flushdb if Stealth.env == 'development'
114
107
  end
108
+
115
109
  end
116
110
  end
@@ -7,14 +7,43 @@ require 'stealth/commands/command'
7
7
  module Stealth
8
8
  module Commands
9
9
  class Server < Command
10
- def initialize(options)
11
- super(options)
12
- Stealth.load_environment
10
+ def initialize(port:)
11
+ @port = port
12
+ $stdout.sync = true
13
13
  end
14
14
 
15
15
  def start
16
- Rack::Handler::Puma.run(Stealth::Server)
16
+ # Rack::Handler::Puma.run(Stealth::Server)
17
+ puts ascii_art
18
+ exec "foreman start -f Procfile.dev -p #{@port}"
17
19
  end
20
+
21
+ private
22
+
23
+ def ascii_art
24
+ <<~ART
25
+ --
26
+ -yooy-
27
+ -yo` `oy-
28
+ -yo` `oy-
29
+ -hh` `hh-
30
+ -yo`/y: :y/`oy-
31
+ -yo` /y: :y/ `oy-
32
+ -yo` /y::y/ `oy-
33
+ -yd+ /dd/ +dy-
34
+ -yo` :y/ :y/ /y: /y/ `oy-
35
+ -yo` :y/ :y/ /y: /y/ `oy-
36
+ -yo` :yoy/ /yoy: `oy-
37
+ -yo` :yoy/ /yoy: `oy-
38
+ -yo` :y/ :y/ /y: /y: `oy-
39
+ -yo` :y/ :y/ /y: /y: `oy-
40
+ -yh/ :yy: /hy-
41
+
42
+
43
+ Stealth v#{Stealth::VERSION}
44
+
45
+ ART
46
+ end
18
47
  end
19
48
  end
20
49
  end
@@ -80,39 +80,36 @@ module Stealth
80
80
  Stealth::Logger.l(topic: "session", message: "User #{current_user_id}: scheduled session step to #{flow}->#{state} in #{delay} seconds")
81
81
  end
82
82
 
83
- def step_to(session: nil, flow: nil, state: nil)
83
+ def step_to_at(timestamp, session: nil, flow: nil, state: nil)
84
84
  flow, state = get_flow_and_state(session: session, flow: flow, state: state)
85
- step(flow: flow, state: state)
86
- end
87
85
 
88
- def update_session_to(session: nil, flow: nil, state: nil)
89
- flow, state = get_flow_and_state(session: session, flow: flow, state: state)
90
- update_session(flow: flow, state: state)
86
+ unless timestamp.is_a?(DateTime)
87
+ raise ArgumentError, "Please specify your step_to_at `timestamp` parameter as a DateTime"
88
+ end
89
+
90
+ Stealth::ScheduledReplyJob.perform_at(timestamp, current_service, current_user_id, flow, state)
91
+ Stealth::Logger.l(topic: "session", message: "User #{current_user_id}: scheduled session step to #{flow}->#{state} at #{timestamp.iso8601}")
91
92
  end
92
93
 
93
- def step_to_next
94
- flow, state = get_next_state
94
+ def step_to(session: nil, flow: nil, state: nil)
95
+ flow, state = get_flow_and_state(session: session, flow: flow, state: state)
95
96
  step(flow: flow, state: state)
96
97
  end
97
98
 
98
- def update_session_to_next
99
- flow, state = get_next_state
99
+ def update_session_to(session: nil, flow: nil, state: nil)
100
+ flow, state = get_flow_and_state(session: session, flow: flow, state: state)
100
101
  update_session(flow: flow, state: state)
101
102
  end
102
103
 
103
104
  private
104
105
 
105
106
  def update_session(flow:, state:)
106
- Stealth::Logger.l(topic: "session", message: "User #{current_user_id}: updating session to #{flow}->#{state}")
107
-
108
107
  @current_session = Stealth::Session.new(user_id: current_user_id)
109
108
  @progressed = :updated_session
110
109
  @current_session.set(flow: flow, state: state)
111
110
  end
112
111
 
113
112
  def step(flow:, state:)
114
- Stealth::Logger.l(topic: "session", message: "User #{current_user_id}: stepping to #{flow}->#{state}")
115
-
116
113
  update_session(flow: flow, state: state)
117
114
  @progressed = :stepped
118
115
  @flow_controller = nil
@@ -132,30 +129,16 @@ module Stealth
132
129
 
133
130
  if flow.present?
134
131
  if state.blank?
135
- flow_klass = [flow, 'flow'].join('_').classify.constantize
136
- state = flow_klass.flow_spec.states.keys.first
132
+ state = FlowMap.flow_spec[flow.to_sym].states.keys.first.to_s
137
133
  end
138
134
 
139
- return flow, state
135
+ return flow.to_s, state.to_s
140
136
  end
141
137
 
142
138
  if state.present?
143
- return current_session.flow_string, state
139
+ return current_session.flow_string, state.to_s
144
140
  end
145
141
  end
146
142
 
147
- def get_next_state
148
- current_state_index = current_session.flow.states.index(current_session.state_string.to_sym)
149
- next_state = current_session.flow.states[current_state_index + 1]
150
- if next_state.nil?
151
- raise(
152
- Stealth::Errors::InvalidStateTransitions,
153
- "The next state after #{current_session.state_string} has not yet been defined"
154
- )
155
- end
156
-
157
- return current_session.flow_string, next_state
158
- end
159
-
160
143
  end
161
144
  end
@@ -27,7 +27,7 @@ module Stealth
27
27
 
28
28
  included do
29
29
  class_attribute :_helpers, default: Module.new
30
- class_attribute :helpers_path, default: []
30
+ class_attribute :helpers_path, default: ["bot/helpers"]
31
31
  class_attribute :include_all_helpers, default: true
32
32
  end
33
33
 
@@ -54,6 +54,9 @@ module Stealth
54
54
  # Allow all helpers to be included
55
55
  args += all_bot_helpers if args.delete(:all)
56
56
 
57
+ # Add each helper_path to the LOAD_PATH
58
+ Array(helpers_path).each {|path| $:.unshift(path) }
59
+
57
60
  args.flatten.map! do |arg|
58
61
  case arg
59
62
  when String, Symbol
@@ -11,41 +11,42 @@ module Stealth
11
11
  extend ActiveSupport::Concern
12
12
 
13
13
  class_methods do
14
- attr_reader :flow_spec
15
-
16
- def flow(&specification)
17
- @flow_spec = Specification.new(&specification)
14
+ def flow(flow_name, &specification)
15
+ flow_spec[flow_name.to_sym] = Specification.new(&specification)
18
16
  end
19
17
  end
20
18
 
21
19
  included do
22
- attr_accessor :flow_state, :user_id
20
+ class_attribute :flow_spec, default: {}
21
+
22
+ attr_accessor :flow, :flow_state, :user_id
23
23
 
24
24
  def current_state
25
- res = spec.states[@flow_state.to_sym] if @flow_state
26
- res || spec.initial_state
25
+ res = self.spec.states[@flow_state.to_sym] if @flow_state
26
+ res || self.spec.initial_state
27
27
  end
28
28
 
29
- def spec
30
- # check the singleton class first
31
- class << self
32
- return flow_spec if flow_spec
33
- end
29
+ def current_flow
30
+ @flow || self.class.flow_spec.keys.first
31
+ end
34
32
 
35
- self.class.flow_spec
33
+ def spec
34
+ self.class.flow_spec[current_flow]
36
35
  end
37
36
 
38
37
  def states
39
38
  self.spec.states.keys
40
39
  end
41
40
 
42
- def init_state(state)
43
- raise(ArgumentError, 'No state was specified.') if state.blank?
44
-
41
+ def init(flow:, state:)
42
+ new_flow = flow.to_sym
45
43
  new_state = state.to_sym
46
- unless states.include?(new_state)
47
- raise(Stealth::Errors::InvalidStateTransition, "Unknown state '#{state}' for #{self.class.to_s}")
44
+
45
+ unless state_exists?(potential_flow: new_flow, potential_state: new_state)
46
+ raise(Stealth::Errors::InvalidStateTransition, "Unknown state '#{new_state}' for '#{new_flow}' flow")
48
47
  end
48
+
49
+ @flow = new_flow
49
50
  @flow_state = new_state
50
51
 
51
52
  self
@@ -54,7 +55,15 @@ module Stealth
54
55
  private
55
56
 
56
57
  def flow_and_state
57
- [self.class.to_s, current_state].join("->")
58
+ [current_flow, current_state].join("->")
59
+ end
60
+
61
+ def state_exists?(potential_flow:, potential_state:)
62
+ if self.class.flow_spec[potential_flow].present?
63
+ self.class.flow_spec[potential_flow].states.include?(potential_state)
64
+ else
65
+ raise(Stealth::Errors::InvalidStateTransition, "Unknown flow '#{potential_flow}'")
66
+ end
58
67
  end
59
68
  end
60
69
 
@@ -9,3 +9,7 @@ gem 'stealth', '~> 0.10.0'
9
9
 
10
10
  # Uncomment to enable the Stealth Twilio SMS Driver
11
11
  # gem 'stealth-twilio'
12
+
13
+ group :development do
14
+ gem 'foreman'
15
+ end
@@ -1,10 +1,12 @@
1
1
  class BotController < Stealth::Controller
2
2
 
3
+ helper :all
4
+
3
5
  def route
4
6
  if current_session.present?
5
7
  step_to session: current_session
6
8
  else
7
- step_to flow: 'Hello', state: 'say_hello'
9
+ step_to flow: 'hello', state: 'say_hello'
8
10
  end
9
11
  end
10
12
 
@@ -1,4 +1,4 @@
1
- class <%= name.capitalize.underscore.classify %>Flow
1
+ class FlowMap
2
2
 
3
3
  include Stealth::Flow
4
4
 
@@ -10,7 +10,7 @@ class <%= name.capitalize.underscore.classify %>Flow
10
10
  state :say_goodbye
11
11
  end
12
12
 
13
- flow :catchall do
13
+ flow :catch_all do
14
14
  state :level1
15
15
  end
16
16
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  To boot this bot locally, we recommend the following:
4
4
 
5
- 1. `gem install foreman`
6
- 2. Start Redis
7
- 3. `foreman start -f Procfile.dev`
5
+ 1. `bundle`
6
+ 2. Start your local Redis server
7
+ 3. `stealth s`
8
8
 
9
- Using `foreman` will start the web server and Sidekiq processes together.
9
+ For more information, please check out the [Stealth documentation](https://hellostealth.org/docs).
@@ -1,3 +1,6 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
1
4
  require 'thor/group'
2
5
 
3
6
  module Stealth
@@ -1,3 +1,6 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
1
4
  require 'thor/group'
2
5
 
3
6
  module Stealth
@@ -4,9 +4,45 @@
4
4
  module Stealth
5
5
  class Logger
6
6
 
7
+ COLORS = ::Hash[
8
+ black: 30,
9
+ red: 31,
10
+ green: 32,
11
+ yellow: 33,
12
+ blue: 34,
13
+ magenta: 35,
14
+ cyan: 36,
15
+ gray: 37,
16
+ ].freeze
17
+
18
+ def self.color_code(code)
19
+ COLORS.fetch(code) { raise(ArgumentError, "Color #{code} not supported.") }
20
+ end
21
+
22
+ def self.colorize(input, color:)
23
+ "\e[#{color_code(color)}m#{input}\e[0m"
24
+ end
25
+
7
26
  def self.log(topic:, message:)
8
27
  unless ENV['STEALTH_ENV'] == 'test'
9
- puts "[#{topic.upcase}] #{message}"
28
+ puts "#{print_topic(topic)} #{message}"
29
+ end
30
+ end
31
+
32
+ def self.print_topic(topic)
33
+ topic_string = "[#{topic}]"
34
+
35
+ case topic.to_sym
36
+ when :session
37
+ colorize(topic_string, color: :green)
38
+ when :previous_session
39
+ colorize(topic_string, color: :yellow)
40
+ when :facebook, :twilio
41
+ colorize(topic_string, color: :blue)
42
+ when :catch_all
43
+ colorize(topic_string, color: :red)
44
+ else
45
+ colorize(topic_string, color: :gray)
10
46
  end
11
47
  end
12
48
 
@@ -30,11 +30,6 @@ module Stealth
30
30
  end
31
31
 
32
32
  get_or_post '/incoming/:service' do
33
- if Stealth.env == 'development'
34
- Stealth::Logger.l(topic: "ARGF", message: "RELOADING")
35
- ActiveSupport::Dependencies.clear
36
- end
37
-
38
33
  Stealth::Logger.l(topic: "incoming", message: "Received webhook from #{params[:service]}")
39
34
 
40
35
  # JSON params need to be parsed and added to the params
@@ -31,11 +31,7 @@ module Stealth
31
31
  def flow
32
32
  return nil if flow_string.blank?
33
33
 
34
- @flow ||= begin
35
- flow_klass = [flow_string, 'flow'].join('_').classify.constantize
36
- flow = flow_klass.new.init_state(state_string)
37
- flow
38
- end
34
+ @flow ||= FlowMap.new.init(flow: flow_string, state: state_string)
39
35
  end
40
36
 
41
37
  def state
@@ -63,6 +59,8 @@ module Stealth
63
59
 
64
60
  @flow = nil
65
61
  @session = canonical_session_slug(flow: flow, state: state)
62
+
63
+ Stealth::Logger.l(topic: "session", message: "User #{user_id}: setting session to #{flow}->#{state}")
66
64
  $redis.set(user_id, session)
67
65
  end
68
66
 
@@ -143,20 +143,16 @@ class OtherFlowTestersController < BotController
143
143
  end
144
144
  end
145
145
 
146
- class FlowTesterFlow
146
+ class FlowMap
147
147
  include Stealth::Flow
148
148
 
149
- flow do
149
+ flow :flow_tester do
150
150
  state :my_action
151
151
  state :my_action2
152
152
  state :my_action3
153
153
  end
154
- end
155
-
156
- class OtherFlowTesterFlow
157
- include Stealth::Flow
158
154
 
159
- flow do
155
+ flow :other_flow_tester do
160
156
  state :other_action
161
157
  state :other_action2
162
158
  state :other_action3