Class: RiveScript

Inherits:
Object
  • Object
show all
Defined in:
lib/rivescript.rb,
lib/rivescript/brain.rb,
lib/rivescript/utils.rb,
lib/rivescript/parser.rb,
lib/rivescript/sorting.rb,
lib/rivescript/version.rb,
lib/rivescript/sessions.rb,
lib/rivescript/lang/ruby.rb,
lib/rivescript/inheritance.rb

Overview

RiveScript Ruby port Byte byte@jvmlab.org, https://jvmlab.org/ MIT License

Defined Under Namespace

Modules: Inheritance, Lang, Sorting, Utils Classes: Brain, MemorySessionManager, NullSessionManager, Parser, SessionManager

Constant Summary collapse

RS_VERSION =

The version of the RiveScript language we support.

"2.0"
VERSION =
"0.1.2"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ RiveScript

Returns a new instance of RiveScript.

Parameters:

  • opts (Hash) (defaults to: {})

    configuration options

Options Hash (opts):

  • :debug (Boolean)

    debug mode (default false)

  • :depth (Integer)

    recursion depth limit (default 50)

  • :strict (Boolean)

    strict mode (default true)

  • :utf8 (Boolean)

    enable UTF-8 mode (default false)

  • :force_case (Boolean)

    force-lowercase triggers (default false)

  • :on_debug (Proc)

    custom debug log handler

  • :concat (String)

    global concatenation mode override

  • :session_manager (RiveScript::SessionManager)

    custom session store

  • :case_sensitive (Boolean)

    preserve user message capitalization

  • :enable_object_macros (Boolean)

    register the default Ruby object macro handler (default true). Set to false to disable executing Ruby object macros defined in RiveScript source.

  • :unicode_punctuation (Regexp)

    punctuation regexp for UTF-8 mode

  • :errors (Hash)

    customized error messages



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/rivescript.rb', line 62

def initialize(opts = {})
  opts = {} if opts.nil?

  @_debug = opts.fetch(:debug, false)
  @_strict = opts.fetch(:strict, true)
  @_depth = opts.key?(:depth) ? opts[:depth].to_i : 50
  @_utf8 = opts.fetch(:utf8, false)
  @_force_case = opts.fetch(:force_case, false)
  @on_debug = opts[:on_debug]
  @_concat = opts[:concat]
  @_case_sensitive = opts.fetch(:case_sensitive, false)

  @unicode_punctuation = opts.fetch(:unicode_punctuation, /[.,!?;:]/)

  @errors = {
    "replyNotMatched" => "ERR: No Reply Matched",
    "replyNotFound" => "ERR: No Reply Found",
    "objectNotFound" => "[ERR: Object Not Found]",
    "deepRecursion" => "ERR: Deep Recursion Detected"
  }
  if opts[:errors].is_a?(Hash)
    opts[:errors].each do |key, value|
      @errors[key.to_s] = value
    end
  end

  @runtime = runtime

  @parser = Parser.new(self)
  @brain = Brain.new(self)

  @_pending = []
  @_load_count = 0

  @_global = {}
  @_var = {}
  @_sub = {}
  @_submax = 1
  @_person = {}
  @_personmax = 1
  @_array = {}
  @_session = opts[:session_manager]
  @_includes = {}
  @_inherits = {}
  @_handlers = {}
  @_objlangs = {}
  @_topics = {}
  @_thats = {}
  @_sorted = {}

  @_session = MemorySessionManager.new if @_session.nil?

  @_handlers["ruby"] = Lang::RubyHandler.new(self) unless opts[:enable_object_macros] == false
  say("RiveScript Interpreter v#{VERSION} Initialized.")
  say("Runtime Environment: #{@runtime}")
end

Instance Attribute Details

#_arrayObject (readonly)

Returns the value of attribute _array.



26
27
28
# File 'lib/rivescript.rb', line 26

def _array
  @_array
end

#_case_sensitiveObject (readonly)

Returns the value of attribute _case_sensitive.



26
27
28
# File 'lib/rivescript.rb', line 26

def _case_sensitive
  @_case_sensitive
end

#_concatObject (readonly)

Returns the value of attribute _concat.



26
27
28
# File 'lib/rivescript.rb', line 26

def _concat
  @_concat
end

#_depthObject (readonly)

Returns the value of attribute _depth.



26
27
28
# File 'lib/rivescript.rb', line 26

def _depth
  @_depth
end

#_force_caseObject (readonly) Also known as: _forceCase

Returns the value of attribute _force_case.



26
27
28
# File 'lib/rivescript.rb', line 26

def _force_case
  @_force_case
end

#_globalObject (readonly)

Returns the value of attribute _global.



26
27
28
# File 'lib/rivescript.rb', line 26

def _global
  @_global
end

#_handlersObject (readonly)

Returns the value of attribute _handlers.



26
27
28
# File 'lib/rivescript.rb', line 26

def _handlers
  @_handlers
end

#_includesObject (readonly)

Returns the value of attribute _includes.



26
27
28
# File 'lib/rivescript.rb', line 26

def _includes
  @_includes
end

#_inheritsObject (readonly)

Returns the value of attribute _inherits.



26
27
28
# File 'lib/rivescript.rb', line 26

def _inherits
  @_inherits
end

#_objlangsObject (readonly)

Returns the value of attribute _objlangs.



26
27
28
# File 'lib/rivescript.rb', line 26

def _objlangs
  @_objlangs
end

#_personObject (readonly)

Returns the value of attribute _person.



26
27
28
# File 'lib/rivescript.rb', line 26

def _person
  @_person
end

#_personmaxObject (readonly)

Returns the value of attribute _personmax.



26
27
28
# File 'lib/rivescript.rb', line 26

def _personmax
  @_personmax
end

#_sessionObject (readonly)

Returns the value of attribute _session.



26
27
28
# File 'lib/rivescript.rb', line 26

def _session
  @_session
end

#_sortedObject (readonly)

Returns the value of attribute _sorted.



26
27
28
# File 'lib/rivescript.rb', line 26

def _sorted
  @_sorted
end

#_strictObject (readonly)

Returns the value of attribute _strict.



26
27
28
# File 'lib/rivescript.rb', line 26

def _strict
  @_strict
end

#_subObject (readonly)

Returns the value of attribute _sub.



26
27
28
# File 'lib/rivescript.rb', line 26

def _sub
  @_sub
end

#_submaxObject (readonly)

Returns the value of attribute _submax.



26
27
28
# File 'lib/rivescript.rb', line 26

def _submax
  @_submax
end

#_thatsObject (readonly)

Returns the value of attribute _thats.



26
27
28
# File 'lib/rivescript.rb', line 26

def _thats
  @_thats
end

#_topicsObject (readonly)

Returns the value of attribute _topics.



26
27
28
# File 'lib/rivescript.rb', line 26

def _topics
  @_topics
end

#_utf8Object (readonly)

Returns the value of attribute _utf8.



26
27
28
# File 'lib/rivescript.rb', line 26

def _utf8
  @_utf8
end

#_varObject (readonly)

Returns the value of attribute _var.



26
27
28
# File 'lib/rivescript.rb', line 26

def _var
  @_var
end

#brainObject (readonly)

Returns the value of attribute brain.



26
27
28
# File 'lib/rivescript.rb', line 26

def brain
  @brain
end

#errorsObject

Returns the value of attribute errors.



25
26
27
# File 'lib/rivescript.rb', line 25

def errors
  @errors
end

#parserObject (readonly)

Returns the value of attribute parser.



26
27
28
# File 'lib/rivescript.rb', line 26

def parser
  @parser
end

#unicode_punctuationObject

Returns the value of attribute unicode_punctuation.



25
26
27
# File 'lib/rivescript.rb', line 25

def unicode_punctuation
  @unicode_punctuation
end

Class Method Details

.brain_pathObject

Path to the bundled sample brain shipped with the gem.

bot = RiveScript.new
bot.load_directory(RiveScript.brain_path)
bot.sort_replies


43
44
45
# File 'lib/rivescript.rb', line 43

def self.brain_path
  File.join(root, "eg", "brain")
end

.rootObject

Gem / checkout root (directory that contains lib/ and eg/).



34
35
36
# File 'lib/rivescript.rb', line 34

def self.root
  File.expand_path("..", __dir__)
end

Instance Method Details

#clear_uservars(user = nil) ⇒ Object

Clear user variables for one user, or all users if user is omitted.



372
373
374
375
376
377
378
# File 'lib/rivescript.rb', line 372

def clear_uservars(user = nil)
  if user.nil?
    @_session.reset_all
  else
    @_session.reset(user)
  end
end

#current_userObject

Retrieve the current user's ID (only valid inside object macros).



412
413
414
415
416
417
# File 'lib/rivescript.rb', line 412

def current_user
  if @brain.current_user.nil?
    warn("currentUser() is intended to be called from within a Ruby object macro!")
  end
  @brain.current_user
end

#deparseObject

Translate the in-memory brain into a JSON-serializable structure.



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/rivescript.rb', line 238

def deparse
  result = {
    "begin" => {
      "global" => Utils.clone(@_global),
      "var" => Utils.clone(@_var),
      "sub" => Utils.clone(@_sub),
      "person" => Utils.clone(@_person),
      "array" => Utils.clone(@_array),
      "triggers" => []
    },
    "topics" => Utils.clone(@_topics),
    "inherits" => Utils.clone(@_inherits),
    "includes" => Utils.clone(@_includes),
    "objects" => {}
  }

  @_handlers.each do |key, handler|
    next unless handler.respond_to?(:objects)

    entry = { "_objects" => Utils.clone(handler.objects) }
    entry["_sources"] = Utils.clone(handler.sources) if handler.respond_to?(:sources)
    result["objects"][key] = entry
  end

  if result["topics"]["__begin__"]
    result["begin"]["triggers"] = result["topics"].delete("__begin__")
  end

  result["begin"]["global"]["debug"] = @_debug if @_debug
  result["begin"]["global"]["depth"] = @_depth if @_depth != 50

  result
end

#freeze_uservars(user) ⇒ Object

Freeze the variable state of a user.



381
382
383
# File 'lib/rivescript.rb', line 381

def freeze_uservars(user)
  @_session.freeze(user)
end

#get_user_topic_triggers(user) ⇒ Object

Retrieve triggers in the current topic for the specified user.



406
407
408
409
# File 'lib/rivescript.rb', line 406

def get_user_topic_triggers(user)
  topic = @_session.get(user, "topic")
  Inheritance.get_topic_triggers(self, topic)
end

#get_uservar(user, name) ⇒ Object

Get a user variable.



358
359
360
# File 'lib/rivescript.rb', line 358

def get_uservar(user, name)
  @_session.get(user, name)
end

#get_uservars(user = nil) ⇒ Object

Get all variables about a user, or all users if user is omitted.



363
364
365
366
367
368
369
# File 'lib/rivescript.rb', line 363

def get_uservars(user = nil)
  if user.nil?
    @_session.get_all
  else
    @_session.get_any(user)
  end
end

#get_variable(name) ⇒ Object

Get a bot variable ().



353
354
355
# File 'lib/rivescript.rb', line 353

def get_variable(name)
  @_var.key?(name) ? @_var[name] : "undefined"
end

#initial_match(user) ⇒ Object

Retrieve the trigger the user matched initially for the last reply.



396
397
398
# File 'lib/rivescript.rb', line 396

def initial_match(user)
  @_session.get(user, "__initialmatch__")
end

#last_match(user) ⇒ Object

Retrieve the trigger the user matched most recently.



391
392
393
# File 'lib/rivescript.rb', line 391

def last_match(user)
  @_session.get(user, "__lastmatch__")
end

#last_triggers(user) ⇒ Object

Retrieve all triggers matched for the last reply.



401
402
403
# File 'lib/rivescript.rb', line 401

def last_triggers(user)
  @_session.get(user, "__last_triggers__")
end

#load_directory(path) ⇒ Object

Load RiveScript documents from a directory recursively.



139
140
141
142
143
144
145
# File 'lib/rivescript.rb', line 139

def load_directory(path)
  raise "#{path} is not a directory" unless File.directory?(path)

  say("Loading from directory #{path}")
  files = Dir.glob(File.join(path, "**", "*.{rive,rs}"), File::FNM_CASEFOLD)
  load_file(files)
end

#load_file(path) ⇒ Object

Load a RiveScript document from one or more files.



125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/rivescript.rb', line 125

def load_file(path)
  paths = path.is_a?(Array) ? path : [path]

  paths.each do |file|
    say("Request to load file: #{file}")
    data = File.read(file)
    ok = parse(file, data)
    raise "parser error" unless ok
  end

  true
end

#parse(filename, code, on_error = nil) ⇒ Object

Parse RiveScript code and load it into memory.



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/rivescript.rb', line 154

def parse(filename, code, on_error = nil)
  say("Parsing code!")

  ok = true
  error_handler = lambda do |err, fn, ln|
    on_error&.call(err, fn, ln)
    ok = false
  end
  ast = @parser.parse(filename, code, error_handler)

  ast["begin"].each do |type, vars|
    internal = :"@_#{type}"
    max_key = :"@_#{type}max"

    vars.each do |name, value|
      if %w[sub person].include?(type)
        instance_variable_set(max_key, [instance_variable_get(max_key), name.split(" ").length].max)
      end

      target = instance_variable_get(internal)
      if value == "<undef>"
        target.delete(name)
      else
        target[name] = value
      end
    end
  end

  @_debug = @_global["debug"] == "true" if @_global.key?("debug")
  if @_global.key?("depth")
    parsed_depth = @_global["depth"].to_i
    @_depth = parsed_depth.zero? ? 50 : parsed_depth
  end

  ast["topics"].each do |topic, data|
    @_includes[topic] ||= {}
    @_inherits[topic] ||= {}
    Utils.extend(@_includes[topic], data["includes"])
    Utils.extend(@_inherits[topic], data["inherits"])

    @_topics[topic] ||= []
    data["triggers"].each do |trigger|
      @_topics[topic] << trigger

      next if trigger["previous"].nil?

      @_thats[topic] ||= {}
      @_thats[topic][trigger["trigger"]] ||= {}
      @_thats[topic][trigger["trigger"]][trigger["previous"]] = trigger
    end
  end

  ast["objects"].each do |object|
    language = object["language"]
    next unless @_handlers[language]

    @_objlangs[object["name"]] = language
    @_handlers[language].load(object["name"], object["code"])
  end

  ok
end

#reply(user, msg, scope = nil) ⇒ Object

Fetch a reply from the RiveScript brain.



420
421
422
# File 'lib/rivescript.rb', line 420

def reply(user, msg, scope = nil)
  @brain.reply(user, msg, scope)
end

#reply_async(user, msg, scope = nil, &block) ⇒ Object

Deprecated alias for reply.



425
426
427
428
429
430
# File 'lib/rivescript.rb', line 425

def reply_async(user, msg, scope = nil, &block)
  warn("DEPRECATED FUNCTION: RiveScript#reply_async is deprecated; use reply instead")
  result = reply(user, msg, scope)
  block&.call(nil, result)
  result
end

#say(message) ⇒ Object

Debug logger.



433
434
435
436
437
438
439
440
441
# File 'lib/rivescript.rb', line 433

def say(message)
  return unless @_debug

  if @on_debug
    @on_debug.call(message)
  else
    $stdout.puts(message)
  end
end

#set_global(name, value = :__unset__) ⇒ Object

Set a global variable (! global).



303
304
305
306
307
308
309
# File 'lib/rivescript.rb', line 303

def set_global(name, value = :__unset__)
  if value == :__unset__
    @_global.delete(name)
  else
    @_global[name] = value
  end
end

#set_handler(lang, obj = :__unset__) ⇒ Object

Set a custom language handler for object macros. Omit obj to remove the handler; pass nil to disable it explicitly.



284
285
286
287
288
289
290
# File 'lib/rivescript.rb', line 284

def set_handler(lang, obj = :__unset__)
  if obj == :__unset__
    @_handlers.delete(lang)
  else
    @_handlers[lang] = obj
  end
end

#set_person(name, value = :__unset__) ⇒ Object

Set a person substitution (! person).



331
332
333
334
335
336
337
338
# File 'lib/rivescript.rb', line 331

def set_person(name, value = :__unset__)
  if value == :__unset__
    @_person.delete(name)
  else
    @_personmax = [name.split(" ").length, @_personmax].max
    @_person[name] = value
  end
end

#set_subroutine(name, code = nil, &block) ⇒ Object

Define a Ruby object macro from your program.



293
294
295
296
297
298
299
300
# File 'lib/rivescript.rb', line 293

def set_subroutine(name, code = nil, &block)
  code ||= block
  return unless @_handlers["ruby"]
  return if code.nil?

  @_objlangs[name] = "ruby"
  @_handlers["ruby"].load(name, code)
end

#set_substitution(name, value = :__unset__) ⇒ Object

Set a substitution (! sub).



321
322
323
324
325
326
327
328
# File 'lib/rivescript.rb', line 321

def set_substitution(name, value = :__unset__)
  if value == :__unset__
    @_sub.delete(name)
  else
    @_submax = [name.split(" ").length, @_submax].max
    @_sub[name] = value
  end
end

#set_uservar(user, name, value) ⇒ Object

Set a user variable.



341
342
343
344
345
# File 'lib/rivescript.rb', line 341

def set_uservar(user, name, value)
  value = value.downcase if name == "topic" && @_force_case

  @_session.set(user, { name => value })
end

#set_uservars(user, data) ⇒ Object

Set multiple user variables at once.



348
349
350
# File 'lib/rivescript.rb', line 348

def set_uservars(user, data)
  @_session.set(user, data)
end

#set_variable(name, value = :__unset__) ⇒ Object

Set a bot variable (! var).



312
313
314
315
316
317
318
# File 'lib/rivescript.rb', line 312

def set_variable(name, value = :__unset__)
  if value == :__unset__
    @_var.delete(name)
  else
    @_var[name] = value
  end
end

#sort_repliesObject

Populate sort buffers after loading RiveScript code.



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/rivescript.rb', line 218

def sort_replies
  @_sorted = { "topics" => {}, "thats" => {} }
  say("Sorting triggers...")

  @_topics.each_key do |topic|
    say("Analyzing topic #{topic}...")

    all_triggers = Inheritance.get_topic_triggers(self, topic)
    @_sorted["topics"][topic] = Sorting.sort_trigger_set(all_triggers, true)

    that_triggers = Inheritance.get_topic_triggers(self, topic, true)
    @_sorted["thats"][topic] = Sorting.sort_trigger_set(that_triggers, false)
  end

  @_sorted["sub"] = Sorting.sort_list(@_sub.keys)
  @_sorted["person"] = Sorting.sort_list(@_person.keys)
  @_sorted
end

#stream(code, on_error = nil) ⇒ Object

Load RiveScript source code from a string. Returns true if the code parsed with no error.



149
150
151
# File 'lib/rivescript.rb', line 149

def stream(code, on_error = nil)
  parse("stream()", code, on_error)
end

#stringify(deparsed = nil) ⇒ Object

Translate the in-memory brain back into RiveScript source code.



273
274
275
# File 'lib/rivescript.rb', line 273

def stringify(deparsed = nil)
  @parser.stringify(deparsed || deparse)
end

#thaw_uservars(user, action = "thaw") ⇒ Object

Thaw a user's frozen variables.



386
387
388
# File 'lib/rivescript.rb', line 386

def thaw_uservars(user, action = "thaw")
  @_session.thaw(user, action)
end

#versionObject

Returns the version number of the RiveScript Ruby library.



120
121
122
# File 'lib/rivescript.rb', line 120

def version
  VERSION
end

#warn(message, filename = nil, lineno = nil) ⇒ Object

Warning/error logger.



444
445
446
447
448
449
450
451
452
453
# File 'lib/rivescript.rb', line 444

def warn(message, filename = nil, lineno = nil)
  message = "#{message} at #{filename} line #{lineno}" if filename && lineno
  formatted = "[WARNING] #{message}"

  if @on_debug
    @on_debug.call(formatted)
  else
    $stderr.puts(formatted)
  end
end

#write(filename, deparsed = nil) ⇒ Object

Write the in-memory RiveScript data into a text file.



278
279
280
# File 'lib/rivescript.rb', line 278

def write(filename, deparsed = nil)
  File.write(filename, stringify(deparsed))
end