lita-markov 0.0.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +11 -0
- data/lib/lita/handlers/markov/engine.rb +172 -0
- data/lib/lita/handlers/markov.rb +19 -14
- data/lib/lita-markov.rb +0 -1
- data/lita-markov.gemspec +5 -4
- data/spec/lita/handlers/markov/engine_spec.rb +54 -0
- data/spec/lita/handlers/markov_spec.rb +1 -11
- metadata +26 -12
- data/lib/marky_markov/persistent_json_dictionary.rb +0 -34
- data/spec/marky_markov/persistent_json_dictionary_spec.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 627ba42fa0054e9a6cd2d46bbde5cb6510f1b985
|
4
|
+
data.tar.gz: c53c2b0f565930850bd0a60085d6d02c69128eee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b634fa615bb4960166da153db753ea5c8f05f9dece26a856626fbdd8b73be6ee887ce559a55fdfa17d215085e9d61b9e62c40d65a89630ef813fba33a756c7d5
|
7
|
+
data.tar.gz: 885beb293a6f002273be56e374700e56d2739bd451076ccffc5114d7a261cc9aee76c4e6fabb379dc564d62301fe9ab7c0a15d9c9cf6cf3659ba5fe0c1ea682f
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -11,6 +11,17 @@ Add `lita-markov` to your Lita instance's Gemfile:
|
|
11
11
|
gem 'lita-markov'
|
12
12
|
```
|
13
13
|
|
14
|
+
Configure the database URL for your SQL database
|
15
|
+
([Sequel](http://sequel.jeremyevans.net/) is used for
|
16
|
+
communicating with databases):
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
# lita_config.rb
|
20
|
+
Lita.configure do |config|
|
21
|
+
config.handlers.markov.database_url = ENV['DATABASE_URL']
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
14
25
|
## Usage
|
15
26
|
|
16
27
|
The bot will automatically ingest all messages into the Redis-backed Markov
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
|
3
|
+
class Lita::Handlers::Markov
|
4
|
+
class Engine
|
5
|
+
class EmptyDictionaryError < StandardError; end
|
6
|
+
|
7
|
+
# Default development database URL
|
8
|
+
DEFAULT_DATABASE_URL = 'mysql2://root@localhost/lita-markov'
|
9
|
+
|
10
|
+
attr_accessor :handler
|
11
|
+
attr_reader :db
|
12
|
+
|
13
|
+
def initialize(handler = nil)
|
14
|
+
@handler = handler
|
15
|
+
@depth = 2
|
16
|
+
|
17
|
+
database_url = DEFAULT_DATABASE_URL
|
18
|
+
database_url ||= handler.config.database_url if handler
|
19
|
+
|
20
|
+
@db = Sequel.connect database_url
|
21
|
+
|
22
|
+
@db.create_table?(:dictionary) do
|
23
|
+
column :user, String, null: false # The user the states are associated with
|
24
|
+
column :current_state, String, null: false # Word(s) the user has "said"
|
25
|
+
column :next_state, String, null: false # Word that follows that word
|
26
|
+
column :frequency, Integer, null: false # Frequency that the next word follows the current state/word
|
27
|
+
|
28
|
+
primary_key [:user, :current_state, :next_state]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# user - Username of the user
|
33
|
+
# string - String of words that the user has just said (ideally a sentence)
|
34
|
+
def ingest user, string
|
35
|
+
string = sanitize_string string
|
36
|
+
words = separate_string string
|
37
|
+
|
38
|
+
return if words.length == 0
|
39
|
+
|
40
|
+
# Capitalize the first word
|
41
|
+
words = [words[0].capitalize] + words.slice(1..-1)
|
42
|
+
|
43
|
+
# Iterate over it one step at a time in sets of `@depth + 1`
|
44
|
+
words.each_cons(@depth + 1) do |words|
|
45
|
+
current_state = words[0]+' '+words[1]
|
46
|
+
next_state = words[2]
|
47
|
+
|
48
|
+
add_entry user, current_state, next_state
|
49
|
+
end # words.each_cons
|
50
|
+
end # def ingest
|
51
|
+
|
52
|
+
def add_entry user, current_state, next_state
|
53
|
+
dictionary = @db[:dictionary]
|
54
|
+
|
55
|
+
@db.transaction do
|
56
|
+
entry = {
|
57
|
+
user: user,
|
58
|
+
current_state: current_state,
|
59
|
+
next_state: next_state
|
60
|
+
}
|
61
|
+
|
62
|
+
if dictionary.where(entry).any?
|
63
|
+
# Entry is already present, so increment its frequency
|
64
|
+
frequency = dictionary.where(entry).get(:frequency)
|
65
|
+
|
66
|
+
dictionary.where(entry).update frequency: frequency + 1
|
67
|
+
else
|
68
|
+
dictionary.insert entry.merge(frequency: 1)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def random_capitalized_word
|
74
|
+
states = @db[:dictionary].map(:current_state)
|
75
|
+
|
76
|
+
capitalized_states = states.select do |state|
|
77
|
+
/^[A-Z]/ =~ state
|
78
|
+
end
|
79
|
+
|
80
|
+
if capitalized_states.length > 0
|
81
|
+
state = capitalized_states.sample
|
82
|
+
else
|
83
|
+
state = states.sample
|
84
|
+
end
|
85
|
+
|
86
|
+
raise EmptyDictionaryError, 'No data for user' if state.nil?
|
87
|
+
|
88
|
+
return state.split(' ').first
|
89
|
+
end
|
90
|
+
|
91
|
+
def random_second_word(first_word)
|
92
|
+
states = @db[:dictionary]
|
93
|
+
.where(Sequel.like(:current_state, first_word+'%'))
|
94
|
+
.map(:current_state)
|
95
|
+
|
96
|
+
state = states.sample
|
97
|
+
state.split(' ').last
|
98
|
+
end
|
99
|
+
|
100
|
+
def is_punctuation?(string)
|
101
|
+
PUNCTUATION.any? { |p| string.end_with? p }
|
102
|
+
end
|
103
|
+
|
104
|
+
def get_next_state(user, current_state)
|
105
|
+
states = @db[:dictionary]
|
106
|
+
.where(user: user, current_state: current_state)
|
107
|
+
.select(:next_state, :frequency)
|
108
|
+
.all
|
109
|
+
|
110
|
+
distribution = states.flat_map do |state|
|
111
|
+
Array.new(state[:frequency]) { state[:next_state] }
|
112
|
+
end
|
113
|
+
|
114
|
+
distribution.sample
|
115
|
+
end
|
116
|
+
|
117
|
+
def generate_sentence_for(user, length = 30)
|
118
|
+
first_word = random_capitalized_word
|
119
|
+
second_word = random_second_word first_word
|
120
|
+
|
121
|
+
sentence = [first_word, second_word]
|
122
|
+
|
123
|
+
while sentence.length < length
|
124
|
+
current_state = sentence.slice(sentence.length - @depth, @depth).join ' '
|
125
|
+
|
126
|
+
next_state = get_next_state user, current_state
|
127
|
+
|
128
|
+
# Stop if we failed to find a next state
|
129
|
+
break if next_state.nil?
|
130
|
+
|
131
|
+
sentence << next_state
|
132
|
+
|
133
|
+
break if is_punctuation? next_state
|
134
|
+
end
|
135
|
+
|
136
|
+
sentence.slice(0..-2).join(' ') + sentence.last
|
137
|
+
end
|
138
|
+
|
139
|
+
def separate_string string
|
140
|
+
# Including the punctuation in group so they'll be included in the
|
141
|
+
# split results
|
142
|
+
string
|
143
|
+
.split(/([.!?])|\s+/)
|
144
|
+
.map { |w| w.strip }
|
145
|
+
.select { |w| !w.empty? }
|
146
|
+
end
|
147
|
+
|
148
|
+
PUNCTUATION = ['.', '!', '?']
|
149
|
+
|
150
|
+
# Don't allow anything besides letters, digits, whitespace, and puncutation
|
151
|
+
ILLEGAL_CHARACTERS = /[^\w\d\s:;,.!?#@]/
|
152
|
+
|
153
|
+
SIMPLE_CODE_BLOCK = /`[^`]+`/
|
154
|
+
EXTENDED_CODE_BLOCK = /```.+```/m
|
155
|
+
|
156
|
+
def sanitize_string string
|
157
|
+
string = string
|
158
|
+
.strip()
|
159
|
+
.gsub(/http[^\s]+/, '') # Remove any hyperlinks
|
160
|
+
.gsub(SIMPLE_CODE_BLOCK, '') # Remove code blocks and illegal characters
|
161
|
+
.gsub(EXTENDED_CODE_BLOCK, '')
|
162
|
+
.gsub(ILLEGAL_CHARACTERS, '')
|
163
|
+
.gsub(/([:;,.!?])/, '\1 ') # Put whitespace after punctuation for proper separation
|
164
|
+
.strip()
|
165
|
+
|
166
|
+
ends_with_punctuation = PUNCTUATION.any? { |p| string.end_with? p }
|
167
|
+
string = string+'.' unless ends_with_punctuation
|
168
|
+
|
169
|
+
string
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
data/lib/lita/handlers/markov.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
|
+
# Forward definition of Markov handler class
|
2
|
+
class Lita::Handlers::Markov < Lita::Handler; end
|
3
|
+
|
4
|
+
require 'lita/handlers/markov/engine'
|
5
|
+
|
1
6
|
module Lita::Handlers
|
2
|
-
class Markov
|
3
|
-
|
7
|
+
class Markov
|
8
|
+
attr_reader :engine
|
4
9
|
|
5
|
-
|
10
|
+
config :database_url
|
6
11
|
|
7
12
|
route(/.+/, :ingest, command: false)
|
8
13
|
|
@@ -10,6 +15,12 @@ module Lita::Handlers
|
|
10
15
|
'markov USER' => 'Generate a markov chain from the given user.'
|
11
16
|
})
|
12
17
|
|
18
|
+
def initialize(robot)
|
19
|
+
super(robot)
|
20
|
+
|
21
|
+
@engine = Engine.new self
|
22
|
+
end
|
23
|
+
|
13
24
|
def ingest(chat)
|
14
25
|
# Don't ingest messages addressed to ourselves
|
15
26
|
return if chat.command?
|
@@ -17,26 +28,20 @@ module Lita::Handlers
|
|
17
28
|
message = chat.matches[0].strip
|
18
29
|
|
19
30
|
# Get the mention name (ie. 'dirk') of the user
|
20
|
-
|
21
|
-
dictionary = dictionary_for_user name
|
22
|
-
|
23
|
-
# Passing `false` to indicate it's a string and not a file name
|
24
|
-
dictionary.parse_source message, false
|
31
|
+
id = chat.user.id
|
25
32
|
|
26
|
-
|
33
|
+
@engine.ingest id, message
|
27
34
|
end
|
28
35
|
|
29
36
|
def generate(chat)
|
30
37
|
name = chat.matches[0][0].strip
|
31
|
-
|
32
|
-
dictionary = dictionary_for_user name
|
33
|
-
generator = MarkovSentenceGenerator.new dictionary
|
38
|
+
id = Lita::User.fuzzy_find(name).id
|
34
39
|
|
35
40
|
begin
|
36
|
-
sentence =
|
41
|
+
sentence = @engine.generate_sentence_for id
|
37
42
|
|
38
43
|
chat.reply sentence
|
39
|
-
rescue EmptyDictionaryError
|
44
|
+
rescue Engine::EmptyDictionaryError
|
40
45
|
chat.reply "Looks like #{name} hasn't said anything!"
|
41
46
|
end
|
42
47
|
end
|
data/lib/lita-markov.rb
CHANGED
data/lita-markov.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |spec|
|
2
2
|
spec.name = "lita-markov"
|
3
|
-
spec.version = "0.0
|
3
|
+
spec.version = "1.0.0"
|
4
4
|
spec.authors = ["Dirk Gadsden"]
|
5
5
|
spec.email = ["dirk@dirk.to"]
|
6
6
|
spec.description = "Markov chains for Lita."
|
@@ -13,9 +13,10 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
14
14
|
spec.require_paths = ["lib"]
|
15
15
|
|
16
|
-
spec.add_runtime_dependency "lita",
|
17
|
-
spec.add_runtime_dependency "
|
18
|
-
spec.add_runtime_dependency "
|
16
|
+
spec.add_runtime_dependency "lita", ">= 4.6"
|
17
|
+
spec.add_runtime_dependency "sequel", "~> 4.28.0"
|
18
|
+
spec.add_runtime_dependency "mysql2", "~> 0.4.1"
|
19
|
+
spec.add_runtime_dependency "pg", "~> 0.18.4"
|
19
20
|
|
20
21
|
spec.add_development_dependency "bundler", "~> 1.3"
|
21
22
|
spec.add_development_dependency "pry-byebug"
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pry'
|
3
|
+
|
4
|
+
describe Lita::Handlers::Markov::Engine do
|
5
|
+
before(:each) do
|
6
|
+
subject.db[:dictionary].delete
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'will sanitize links from a message' do
|
10
|
+
message = 'hello https://www.example.com world!'
|
11
|
+
|
12
|
+
expect(subject.sanitize_string(message)).to eql 'hello world!'
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'will remove code blocks from a message' do
|
16
|
+
message = 'I have `code in` me.'
|
17
|
+
|
18
|
+
expect(subject.sanitize_string(message)).to eql 'I have me.'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'will remove illegal characters from a message' do
|
22
|
+
message = 'I have a bad % character.'
|
23
|
+
|
24
|
+
expect(subject.sanitize_string(message)).to eql 'I have a bad character.'
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'will separate a string into words' do
|
28
|
+
string = "I am\n so totally\tseparated."
|
29
|
+
|
30
|
+
expect(subject.separate_string(string)).to eql ['I', 'am', 'so', 'totally', 'separated', '.']
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'will ingest messages' do
|
34
|
+
dictionary = subject.db[:dictionary]
|
35
|
+
|
36
|
+
subject.ingest('user', 'hello big, fun world!')
|
37
|
+
|
38
|
+
# Check that the first state made it in and is capitalized
|
39
|
+
expect(dictionary.where(current_state: 'Hello big,').count).to eql 1
|
40
|
+
# Check that the last state made it in
|
41
|
+
expect(dictionary.where(current_state: 'fun world', next_state: '!').count).to eql 1
|
42
|
+
|
43
|
+
subject.ingest('user', 'Hello big, fun planet!')
|
44
|
+
|
45
|
+
# Check that the frequency of the "Hello big," -> "fun" state went up
|
46
|
+
expect(dictionary.where(current_state: 'Hello big,', next_state: 'fun').get(:frequency)).to eql 2
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'will generate a sentence' do
|
50
|
+
subject.ingest('user', 'Hello cruel world.')
|
51
|
+
|
52
|
+
expect(subject.generate_sentence_for 'user').to eql 'Hello cruel world.'
|
53
|
+
end
|
54
|
+
end
|
@@ -3,7 +3,7 @@ require 'pry'
|
|
3
3
|
|
4
4
|
describe Lita::Handlers::Markov, lita_handler: true do
|
5
5
|
before(:each) do
|
6
|
-
|
6
|
+
subject.engine.db[:dictionary].delete
|
7
7
|
end
|
8
8
|
|
9
9
|
it "won't call #ingest for non-command messages" do
|
@@ -13,16 +13,6 @@ describe Lita::Handlers::Markov, lita_handler: true do
|
|
13
13
|
send_command 'bar'
|
14
14
|
end
|
15
15
|
|
16
|
-
it "will ingest a message into that person's dictionary" do
|
17
|
-
send_message 'hello markov world'
|
18
|
-
send_message 'hello markov planet'
|
19
|
-
|
20
|
-
dictionary = subject.dictionary_for_user user.mention_name
|
21
|
-
|
22
|
-
# Check that the messages made it into the dictionary
|
23
|
-
expect(dictionary.dictionary[['hello', 'markov']]).to eql ['world', 'planet']
|
24
|
-
end
|
25
|
-
|
26
16
|
it 'will build a sentence' do
|
27
17
|
send_message 'I love cookies!'
|
28
18
|
send_message 'I love pancakes!'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lita-markov
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dirk Gadsden
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-11-
|
11
|
+
date: 2015-11-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: lita
|
@@ -25,33 +25,47 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '4.6'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: sequel
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 4.28.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 4.28.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: mysql2
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 0.4.1
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 0.4.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pg
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.18.4
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.18.4
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: bundler
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,11 +150,11 @@ files:
|
|
136
150
|
- Rakefile
|
137
151
|
- lib/lita-markov.rb
|
138
152
|
- lib/lita/handlers/markov.rb
|
139
|
-
- lib/
|
153
|
+
- lib/lita/handlers/markov/engine.rb
|
140
154
|
- lita-markov.gemspec
|
141
155
|
- locales/en.yml
|
156
|
+
- spec/lita/handlers/markov/engine_spec.rb
|
142
157
|
- spec/lita/handlers/markov_spec.rb
|
143
|
-
- spec/marky_markov/persistent_json_dictionary_spec.rb
|
144
158
|
- spec/spec_helper.rb
|
145
159
|
- templates/.gitkeep
|
146
160
|
homepage: http://github.com/dirk/lita-markov
|
@@ -163,11 +177,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
163
177
|
version: '0'
|
164
178
|
requirements: []
|
165
179
|
rubyforge_project:
|
166
|
-
rubygems_version: 2.4.5
|
180
|
+
rubygems_version: 2.4.5
|
167
181
|
signing_key:
|
168
182
|
specification_version: 4
|
169
183
|
summary: Markov chains for Lita.
|
170
184
|
test_files:
|
185
|
+
- spec/lita/handlers/markov/engine_spec.rb
|
171
186
|
- spec/lita/handlers/markov_spec.rb
|
172
|
-
- spec/marky_markov/persistent_json_dictionary_spec.rb
|
173
187
|
- spec/spec_helper.rb
|
@@ -1,34 +0,0 @@
|
|
1
|
-
require 'marky_markov'
|
2
|
-
require 'oj'
|
3
|
-
|
4
|
-
module MarkyMarkov
|
5
|
-
class PersistentJSONDictionary < ::PersistentDictionary
|
6
|
-
def initialize(*args)
|
7
|
-
super(*args)
|
8
|
-
|
9
|
-
@dictionary = {}
|
10
|
-
@capitalized_words = []
|
11
|
-
end
|
12
|
-
|
13
|
-
# No-op instead of reading from the filesystem
|
14
|
-
def open_dictionary
|
15
|
-
nil
|
16
|
-
end
|
17
|
-
|
18
|
-
def load_json(json)
|
19
|
-
data = Oj.load json
|
20
|
-
|
21
|
-
@depth = data['depth']
|
22
|
-
@dictionary = data['dictionary']
|
23
|
-
@capitalized_words = data['capitalized_words']
|
24
|
-
end
|
25
|
-
|
26
|
-
def to_json
|
27
|
-
Oj.dump(
|
28
|
-
'depth' => @depth,
|
29
|
-
'dictionary' => @dictionary,
|
30
|
-
'capitalized_words' => @capitalized_words
|
31
|
-
)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe MarkyMarkov::PersistentJSONDictionary do
|
4
|
-
subject { MarkyMarkov::PersistentJSONDictionary.new 'whoa' }
|
5
|
-
|
6
|
-
it 'initializes with sensible defaults' do
|
7
|
-
expect(subject.dictionary).to eq({})
|
8
|
-
end
|
9
|
-
|
10
|
-
it 'saves a dictionary' do
|
11
|
-
subject.add_word ['a', 'b'], 'c'
|
12
|
-
|
13
|
-
json = subject.to_json
|
14
|
-
|
15
|
-
expect(json).to eql '{"depth":2,"dictionary":{"^#1":[["a","b"],["c"]]},"capitalized_words":[]}'
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'saves and loads a dictionary' do
|
19
|
-
subject.add_word ['a', 'b'], 'c'
|
20
|
-
|
21
|
-
json = subject.to_json
|
22
|
-
|
23
|
-
new_dictionary = MarkyMarkov::PersistentJSONDictionary.new 'whoa-another-one'
|
24
|
-
new_dictionary.load_json json
|
25
|
-
|
26
|
-
expect(new_dictionary.dictionary).to eql(['a', 'b'] => ['c'])
|
27
|
-
end
|
28
|
-
end
|