Class: RiveScript
- Inherits:
-
Object
- Object
- RiveScript
- 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
-
#_array ⇒ Object
readonly
Returns the value of attribute _array.
-
#_case_sensitive ⇒ Object
readonly
Returns the value of attribute _case_sensitive.
-
#_concat ⇒ Object
readonly
Returns the value of attribute _concat.
-
#_depth ⇒ Object
readonly
Returns the value of attribute _depth.
-
#_force_case ⇒ Object
(also: #_forceCase)
readonly
Returns the value of attribute _force_case.
-
#_global ⇒ Object
readonly
Returns the value of attribute _global.
-
#_handlers ⇒ Object
readonly
Returns the value of attribute _handlers.
-
#_includes ⇒ Object
readonly
Returns the value of attribute _includes.
-
#_inherits ⇒ Object
readonly
Returns the value of attribute _inherits.
-
#_objlangs ⇒ Object
readonly
Returns the value of attribute _objlangs.
-
#_person ⇒ Object
readonly
Returns the value of attribute _person.
-
#_personmax ⇒ Object
readonly
Returns the value of attribute _personmax.
-
#_session ⇒ Object
readonly
Returns the value of attribute _session.
-
#_sorted ⇒ Object
readonly
Returns the value of attribute _sorted.
-
#_strict ⇒ Object
readonly
Returns the value of attribute _strict.
-
#_sub ⇒ Object
readonly
Returns the value of attribute _sub.
-
#_submax ⇒ Object
readonly
Returns the value of attribute _submax.
-
#_thats ⇒ Object
readonly
Returns the value of attribute _thats.
-
#_topics ⇒ Object
readonly
Returns the value of attribute _topics.
-
#_utf8 ⇒ Object
readonly
Returns the value of attribute _utf8.
-
#_var ⇒ Object
readonly
Returns the value of attribute _var.
-
#brain ⇒ Object
readonly
Returns the value of attribute brain.
-
#errors ⇒ Object
Returns the value of attribute errors.
-
#parser ⇒ Object
readonly
Returns the value of attribute parser.
-
#unicode_punctuation ⇒ Object
Returns the value of attribute unicode_punctuation.
Class Method Summary collapse
-
.brain_path ⇒ Object
Path to the bundled sample brain shipped with the gem.
-
.root ⇒ Object
Gem / checkout root (directory that contains lib/ and eg/).
Instance Method Summary collapse
-
#clear_uservars(user = nil) ⇒ Object
Clear user variables for one user, or all users if
useris omitted. -
#current_user ⇒ Object
Retrieve the current user's ID (only valid inside object macros).
-
#deparse ⇒ Object
Translate the in-memory brain into a JSON-serializable structure.
-
#freeze_uservars(user) ⇒ Object
Freeze the variable state of a user.
-
#get_user_topic_triggers(user) ⇒ Object
Retrieve triggers in the current topic for the specified user.
-
#get_uservar(user, name) ⇒ Object
Get a user variable.
-
#get_uservars(user = nil) ⇒ Object
Get all variables about a user, or all users if
useris omitted. -
#get_variable(name) ⇒ Object
Get a bot variable (
). -
#initial_match(user) ⇒ Object
Retrieve the trigger the user matched initially for the last reply.
-
#initialize(opts = {}) ⇒ RiveScript
constructor
A new instance of RiveScript.
-
#last_match(user) ⇒ Object
Retrieve the trigger the user matched most recently.
-
#last_triggers(user) ⇒ Object
Retrieve all triggers matched for the last reply.
-
#load_directory(path) ⇒ Object
Load RiveScript documents from a directory recursively.
-
#load_file(path) ⇒ Object
Load a RiveScript document from one or more files.
-
#parse(filename, code, on_error = nil) ⇒ Object
Parse RiveScript code and load it into memory.
-
#reply(user, msg, scope = nil) ⇒ Object
Fetch a reply from the RiveScript brain.
-
#reply_async(user, msg, scope = nil, &block) ⇒ Object
Deprecated alias for reply.
-
#say(message) ⇒ Object
Debug logger.
-
#set_global(name, value = :__unset__) ⇒ Object
Set a global variable (! global).
-
#set_handler(lang, obj = :__unset__) ⇒ Object
Set a custom language handler for object macros.
-
#set_person(name, value = :__unset__) ⇒ Object
Set a person substitution (! person).
-
#set_subroutine(name, code = nil, &block) ⇒ Object
Define a Ruby object macro from your program.
-
#set_substitution(name, value = :__unset__) ⇒ Object
Set a substitution (! sub).
-
#set_uservar(user, name, value) ⇒ Object
Set a user variable.
-
#set_uservars(user, data) ⇒ Object
Set multiple user variables at once.
-
#set_variable(name, value = :__unset__) ⇒ Object
Set a bot variable (! var).
-
#sort_replies ⇒ Object
Populate sort buffers after loading RiveScript code.
-
#stream(code, on_error = nil) ⇒ Object
Load RiveScript source code from a string.
-
#stringify(deparsed = nil) ⇒ Object
Translate the in-memory brain back into RiveScript source code.
-
#thaw_uservars(user, action = "thaw") ⇒ Object
Thaw a user's frozen variables.
-
#version ⇒ Object
Returns the version number of the RiveScript Ruby library.
-
#warn(message, filename = nil, lineno = nil) ⇒ Object
Warning/error logger.
-
#write(filename, deparsed = nil) ⇒ Object
Write the in-memory RiveScript data into a text file.
Constructor Details
#initialize(opts = {}) ⇒ RiveScript
Returns a new instance of RiveScript.
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
#_array ⇒ Object (readonly)
Returns the value of attribute _array.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _array @_array end |
#_case_sensitive ⇒ Object (readonly)
Returns the value of attribute _case_sensitive.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _case_sensitive @_case_sensitive end |
#_concat ⇒ Object (readonly)
Returns the value of attribute _concat.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _concat @_concat end |
#_depth ⇒ Object (readonly)
Returns the value of attribute _depth.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _depth @_depth end |
#_force_case ⇒ Object (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 |
#_global ⇒ Object (readonly)
Returns the value of attribute _global.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _global @_global end |
#_handlers ⇒ Object (readonly)
Returns the value of attribute _handlers.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _handlers @_handlers end |
#_includes ⇒ Object (readonly)
Returns the value of attribute _includes.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _includes @_includes end |
#_inherits ⇒ Object (readonly)
Returns the value of attribute _inherits.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _inherits @_inherits end |
#_objlangs ⇒ Object (readonly)
Returns the value of attribute _objlangs.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _objlangs @_objlangs end |
#_person ⇒ Object (readonly)
Returns the value of attribute _person.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _person @_person end |
#_personmax ⇒ Object (readonly)
Returns the value of attribute _personmax.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _personmax @_personmax end |
#_session ⇒ Object (readonly)
Returns the value of attribute _session.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _session @_session end |
#_sorted ⇒ Object (readonly)
Returns the value of attribute _sorted.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _sorted @_sorted end |
#_strict ⇒ Object (readonly)
Returns the value of attribute _strict.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _strict @_strict end |
#_sub ⇒ Object (readonly)
Returns the value of attribute _sub.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _sub @_sub end |
#_submax ⇒ Object (readonly)
Returns the value of attribute _submax.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _submax @_submax end |
#_thats ⇒ Object (readonly)
Returns the value of attribute _thats.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _thats @_thats end |
#_topics ⇒ Object (readonly)
Returns the value of attribute _topics.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _topics @_topics end |
#_utf8 ⇒ Object (readonly)
Returns the value of attribute _utf8.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _utf8 @_utf8 end |
#_var ⇒ Object (readonly)
Returns the value of attribute _var.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def _var @_var end |
#brain ⇒ Object (readonly)
Returns the value of attribute brain.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def brain @brain end |
#errors ⇒ Object
Returns the value of attribute errors.
25 26 27 |
# File 'lib/rivescript.rb', line 25 def errors @errors end |
#parser ⇒ Object (readonly)
Returns the value of attribute parser.
26 27 28 |
# File 'lib/rivescript.rb', line 26 def parser @parser end |
#unicode_punctuation ⇒ Object
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_path ⇒ Object
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 |
.root ⇒ Object
Gem / checkout root (directory that contains lib/ and eg/).
34 35 36 |
# File 'lib/rivescript.rb', line 34 def self.root File.("..", __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_user ⇒ Object
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 |
#deparse ⇒ Object
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() return unless @_debug if @on_debug @on_debug.call() else $stdout.puts() 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_replies ⇒ Object
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 |
#version ⇒ Object
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(, filename = nil, lineno = nil) = "#{} at #{filename} line #{lineno}" if filename && lineno formatted = "[WARNING] #{}" 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 |