rambling-trie-opal 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +26 -0
  3. data/Guardfile +10 -0
  4. data/LICENSE +26 -0
  5. data/README.md +301 -0
  6. data/Rakefile +15 -0
  7. data/lib/rambling-trie.rb +3 -0
  8. data/lib/rambling/trie.rb +119 -0
  9. data/lib/rambling/trie/comparable.rb +19 -0
  10. data/lib/rambling/trie/compressible.rb +16 -0
  11. data/lib/rambling/trie/compressor.rb +64 -0
  12. data/lib/rambling/trie/configuration.rb +16 -0
  13. data/lib/rambling/trie/configuration/properties.rb +75 -0
  14. data/lib/rambling/trie/configuration/provider_collection.rb +122 -0
  15. data/lib/rambling/trie/container.rb +226 -0
  16. data/lib/rambling/trie/enumerable.rb +29 -0
  17. data/lib/rambling/trie/inspectable.rb +39 -0
  18. data/lib/rambling/trie/invalid_operation.rb +15 -0
  19. data/lib/rambling/trie/nodes.rb +18 -0
  20. data/lib/rambling/trie/nodes/compressed.rb +98 -0
  21. data/lib/rambling/trie/nodes/missing.rb +12 -0
  22. data/lib/rambling/trie/nodes/node.rb +183 -0
  23. data/lib/rambling/trie/nodes/raw.rb +82 -0
  24. data/lib/rambling/trie/readers.rb +15 -0
  25. data/lib/rambling/trie/readers/plain_text.rb +18 -0
  26. data/lib/rambling/trie/serializers.rb +18 -0
  27. data/lib/rambling/trie/serializers/file.rb +27 -0
  28. data/lib/rambling/trie/serializers/marshal.rb +48 -0
  29. data/lib/rambling/trie/serializers/yaml.rb +55 -0
  30. data/lib/rambling/trie/serializers/zip.rb +74 -0
  31. data/lib/rambling/trie/stringifyable.rb +26 -0
  32. data/lib/rambling/trie/version.rb +8 -0
  33. data/rambling-trie-opal.gemspec +36 -0
  34. data/spec/assets/test_words.en_US.txt +23 -0
  35. data/spec/assets/test_words.es_DO.txt +24 -0
  36. data/spec/integration/rambling/trie_spec.rb +87 -0
  37. data/spec/lib/rambling/trie/comparable_spec.rb +97 -0
  38. data/spec/lib/rambling/trie/compressor_spec.rb +108 -0
  39. data/spec/lib/rambling/trie/configuration/properties_spec.rb +57 -0
  40. data/spec/lib/rambling/trie/configuration/provider_collection_spec.rb +149 -0
  41. data/spec/lib/rambling/trie/container_spec.rb +591 -0
  42. data/spec/lib/rambling/trie/enumerable_spec.rb +42 -0
  43. data/spec/lib/rambling/trie/inspectable_spec.rb +56 -0
  44. data/spec/lib/rambling/trie/nodes/compressed_spec.rb +37 -0
  45. data/spec/lib/rambling/trie/nodes/node_spec.rb +9 -0
  46. data/spec/lib/rambling/trie/nodes/raw_spec.rb +179 -0
  47. data/spec/lib/rambling/trie/readers/plain_text_spec.rb +16 -0
  48. data/spec/lib/rambling/trie/serializers/file_spec.rb +13 -0
  49. data/spec/lib/rambling/trie/serializers/marshal_spec.rb +12 -0
  50. data/spec/lib/rambling/trie/serializers/yaml_spec.rb +12 -0
  51. data/spec/lib/rambling/trie/serializers/zip_spec.rb +28 -0
  52. data/spec/lib/rambling/trie/stringifyable_spec.rb +85 -0
  53. data/spec/lib/rambling/trie_spec.rb +182 -0
  54. data/spec/spec_helper.rb +37 -0
  55. data/spec/support/config.rb +15 -0
  56. data/spec/support/helpers/add_word.rb +20 -0
  57. data/spec/support/helpers/one_line_heredoc.rb +11 -0
  58. data/spec/support/shared_examples/a_compressible_trie.rb +40 -0
  59. data/spec/support/shared_examples/a_serializable_trie.rb +30 -0
  60. data/spec/support/shared_examples/a_serializer.rb +37 -0
  61. data/spec/support/shared_examples/a_trie_data_structure.rb +31 -0
  62. data/spec/support/shared_examples/a_trie_node.rb +127 -0
  63. data/spec/support/shared_examples/a_trie_node_implementation.rb +152 -0
  64. data/spec/tmp/.gitkeep +0 -0
  65. metadata +179 -0
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Rambling::Trie do
6
+ describe '.create' do
7
+ let(:root) { Rambling::Trie::Nodes::Raw.new }
8
+ let(:compressor) { Rambling::Trie::Compressor.new }
9
+ let!(:container) { Rambling::Trie::Container.new root, compressor }
10
+
11
+ before do
12
+ allow(Rambling::Trie::Container).to receive(:new)
13
+ .and_yield(container)
14
+ .and_return container
15
+ end
16
+
17
+ it 'returns a new instance of the trie container' do
18
+ expect(Rambling::Trie.create).to eq container
19
+ end
20
+
21
+ context 'with a block' do
22
+ it 'yields the new container' do
23
+ yielded = nil
24
+ Rambling::Trie.create { |trie| yielded = trie }
25
+ expect(yielded).to eq container
26
+ end
27
+ end
28
+
29
+ context 'with a filepath' do
30
+ let(:filepath) { 'a test filepath' }
31
+ let(:reader) { double :reader }
32
+ let(:words) { %w(a couple of test words over here) }
33
+
34
+ before do
35
+ receive_and_yield = receive(:each_word)
36
+ words.inject(receive_and_yield) do |yielder, word|
37
+ yielder.and_yield word
38
+ end
39
+
40
+ allow(reader).to receive_and_yield
41
+ allow(container).to receive :<<
42
+ end
43
+
44
+ it 'loads every word' do
45
+ Rambling::Trie.create filepath, reader
46
+
47
+ words.each do |word|
48
+ expect(container).to have_received(:<<).with word
49
+ end
50
+ end
51
+ end
52
+
53
+ context 'without any reader' do
54
+ let(:filepath) { 'a test filepath' }
55
+ let(:reader) { double :reader, each_word: nil }
56
+
57
+ before do
58
+ Rambling::Trie.config do |c|
59
+ c.readers.add :default, reader
60
+ c.readers.default = reader
61
+ end
62
+ end
63
+
64
+ it 'defaults to a plain text reader' do
65
+ Rambling::Trie.create filepath, nil
66
+
67
+ expect(reader).to have_received(:each_word).with filepath
68
+ end
69
+ end
70
+ end
71
+
72
+ describe '.load' do
73
+ let(:root) { Rambling::Trie::Nodes::Raw.new }
74
+ let(:compressor) { Rambling::Trie::Compressor.new }
75
+ let(:container) { Rambling::Trie::Container.new root, compressor }
76
+ let(:serializer) { double :serializer, load: root }
77
+ let(:filepath) { 'a path to a file' }
78
+
79
+ it 'returns a new container with the loaded root node' do
80
+ trie = Rambling::Trie.load filepath, serializer
81
+ expect(trie).to eq container
82
+ end
83
+
84
+ it 'uses the serializer to load the root node from the given filepath' do
85
+ Rambling::Trie.load filepath, serializer
86
+ expect(serializer).to have_received(:load).with filepath
87
+ end
88
+
89
+ context 'without a serializer' do
90
+ let(:marshal_serializer) { double :marshal_serializer, load: nil }
91
+ let(:default_serializer) { double :default_serializer, load: nil }
92
+ let(:yaml_serializer) { double :yaml_serializer, load: nil }
93
+
94
+ before do
95
+ Rambling::Trie.config do |c|
96
+ c.serializers.add :default, default_serializer
97
+ c.serializers.add :marshal, marshal_serializer
98
+ c.serializers.add :yml, yaml_serializer
99
+ c.serializers.add :yaml, yaml_serializer
100
+
101
+ c.serializers.default = default_serializer
102
+ end
103
+ end
104
+
105
+ it 'determines the serializer based on the file extension' do
106
+ Rambling::Trie.load 'test.marshal'
107
+ expect(marshal_serializer).to have_received(:load).with 'test.marshal'
108
+
109
+ Rambling::Trie.load 'test.yml'
110
+ expect(yaml_serializer).to have_received(:load).with 'test.yml'
111
+
112
+ Rambling::Trie.load 'test.yaml'
113
+ expect(yaml_serializer).to have_received(:load).with 'test.yaml'
114
+
115
+ Rambling::Trie.load 'test'
116
+ expect(default_serializer).to have_received(:load).with 'test'
117
+ end
118
+ end
119
+
120
+ context 'with a block' do
121
+ it 'yields the new container' do
122
+ yielded = nil
123
+
124
+ Rambling::Trie.load filepath, serializer do |trie|
125
+ yielded = trie
126
+ end
127
+
128
+ expect(yielded).to eq container
129
+ end
130
+ end
131
+ end
132
+
133
+ describe '.dump' do
134
+ let(:filename) { 'a trie' }
135
+ let(:root) { double :root }
136
+ let(:compressor) { double :compressor }
137
+ let(:trie) { Rambling::Trie::Container.new root, compressor }
138
+
139
+ let(:marshal_serializer) { double :marshal_serializer, dump: nil }
140
+ let(:yaml_serializer) { double :yaml_serializer, dump: nil }
141
+ let(:default_serializer) { double :default_serializer, dump: nil }
142
+
143
+ before do
144
+ Rambling::Trie.config do |c|
145
+ c.serializers.add :default, default_serializer
146
+ c.serializers.add :marshal, marshal_serializer
147
+ c.serializers.add :yml, yaml_serializer
148
+
149
+ c.serializers.default = default_serializer
150
+ end
151
+ end
152
+
153
+ it 'uses the configured default serializer by default' do
154
+ Rambling::Trie.dump trie, filename
155
+ expect(default_serializer).to have_received(:dump).with root, filename
156
+ end
157
+
158
+ context 'when provided with a format' do
159
+ it 'uses the corresponding serializer' do
160
+ Rambling::Trie.dump trie, "#{filename}.marshal"
161
+ expect(marshal_serializer).to have_received(:dump)
162
+ .with root, "#{filename}.marshal"
163
+
164
+ Rambling::Trie.dump trie, "#{filename}.yml"
165
+ expect(yaml_serializer).to have_received(:dump)
166
+ .with root, "#{filename}.yml"
167
+ end
168
+ end
169
+ end
170
+
171
+ describe '.config' do
172
+ it 'returns the properties' do
173
+ expect(Rambling::Trie.config).to eq Rambling::Trie.send :properties
174
+ end
175
+
176
+ it 'yields the properties' do
177
+ yielded = nil
178
+ Rambling::Trie.config { |c| yielded = c }
179
+ expect(yielded).to eq Rambling::Trie.send :properties
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'simplecov'
4
+ require 'coveralls'
5
+
6
+ Coveralls.wear!
7
+
8
+ SimpleCov.formatters = [
9
+ SimpleCov::Formatter::HTMLFormatter,
10
+ Coveralls::SimpleCov::Formatter,
11
+ ]
12
+
13
+ SimpleCov.start do
14
+ add_filter '/spec/'
15
+ end
16
+
17
+ require 'rspec'
18
+ require 'rambling-trie'
19
+ ::SPEC_ROOT = File.dirname __FILE__
20
+
21
+ RSpec.configure do |config|
22
+ config.color = true
23
+ config.tty = true
24
+ config.formatter = :documentation
25
+ config.order = :random
26
+ config.run_all_when_everything_filtered = true
27
+ config.raise_errors_for_deprecations!
28
+ end
29
+
30
+ require 'support/config'
31
+
32
+ %w(
33
+ a_compressible_trie a_serializable_trie a_serializer a_trie_data_structure
34
+ a_trie_node a_trie_node_implementation
35
+ ).each do |name|
36
+ require File.join('support', 'shared_examples', name)
37
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helpers/add_word'
4
+ require_relative 'helpers/one_line_heredoc'
5
+
6
+ RSpec.configure do |c|
7
+ c.before do
8
+ Rambling::Trie.config.reset
9
+ end
10
+
11
+ c.include Support::Helpers::AddWord
12
+ c.include Support::Helpers::OneLineHeredoc
13
+
14
+ RSpec::Matchers.define_negated_matcher :not_change, :change
15
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Support
4
+ module Helpers
5
+ module AddWord
6
+ def add_words node, words
7
+ words.each { |word| add_word node, word }
8
+ end
9
+
10
+ def add_word node, word
11
+ case node
12
+ when Rambling::Trie::Container
13
+ node.add word
14
+ else
15
+ node.add word.chars.reverse.map(&:to_sym)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Support
4
+ module Helpers
5
+ module OneLineHeredoc
6
+ def one_line heredoc
7
+ heredoc.strip.tr "\n", ' '
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples_for 'a compressible trie' do
4
+ context 'and the trie is not compressed' do
5
+ it_behaves_like 'a trie data structure'
6
+
7
+ it 'does not alter the input' do
8
+ word = 'string'
9
+ add_word trie, word
10
+
11
+ expect(word).to eq 'string'
12
+ end
13
+
14
+ it 'is marked as not compressed' do
15
+ expect(trie).not_to be_compressed
16
+ end
17
+ end
18
+
19
+ context 'and the trie is compressed' do
20
+ let!(:original_root) { trie.root }
21
+ let!(:original_keys) { original_root.children_tree.keys }
22
+ let!(:original_values) { original_root.children_tree.values }
23
+
24
+ before do
25
+ trie.compress!
26
+ end
27
+
28
+ it_behaves_like 'a trie data structure'
29
+
30
+ it 'is marked as compressed' do
31
+ expect(trie).to be_compressed
32
+ end
33
+
34
+ it 'leaves the original root intact' do
35
+ expect(original_root.children_tree.keys).to eq original_keys
36
+ expect(trie.children_tree.keys).to eq original_keys
37
+ expect(trie.children_tree.values).not_to eq original_values
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples_for 'a serializable trie' do
4
+ let(:tmp_path) { File.join ::SPEC_ROOT, 'tmp' }
5
+ let(:filepath) { File.join tmp_path, "trie-root.#{format}" }
6
+
7
+ context 'and the trie is not compressed' do
8
+ before do
9
+ Rambling::Trie.dump trie_to_serialize, filepath
10
+ end
11
+
12
+ it_behaves_like 'a compressible trie' do
13
+ let(:trie) { Rambling::Trie.load filepath }
14
+ end
15
+ end
16
+
17
+ context 'and the trie is compressed' do
18
+ let(:trie) { Rambling::Trie.load filepath }
19
+
20
+ before do
21
+ Rambling::Trie.dump trie_to_serialize.compress!, filepath
22
+ end
23
+
24
+ it_behaves_like 'a trie data structure'
25
+
26
+ it 'is marked as compressed' do
27
+ expect(trie).to be_compressed
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples_for 'a serializer' do
4
+ let(:trie) { Rambling::Trie.create }
5
+ let(:tmp_path) { File.join ::SPEC_ROOT, 'tmp' }
6
+ let(:filepath) { File.join tmp_path, "trie-root.#{format}" }
7
+ let(:content) { trie.root }
8
+
9
+ before do
10
+ trie.concat %w(a few words to validate that load and dump are working)
11
+ FileUtils.rm_f filepath
12
+ end
13
+
14
+ describe '#dump' do
15
+ before do
16
+ serializer.dump content, filepath
17
+ end
18
+
19
+ it 'creates the file with the provided path' do
20
+ expect(File.exist? filepath).to be true
21
+ end
22
+
23
+ it 'converts the contents to the appropriate format' do
24
+ expect(File.read(filepath).size).to be_within(10).of formatted_content.size
25
+ end
26
+ end
27
+
28
+ describe '#load' do
29
+ before do
30
+ serializer.dump content, filepath
31
+ end
32
+
33
+ it 'loads the dumped object back into memory' do
34
+ expect(serializer.load filepath).to eq content
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples_for 'a trie data structure' do
4
+ it 'contains all the words previously provided' do
5
+ words.each do |word|
6
+ expect(trie).to include word
7
+ expect(trie.word? word).to be true
8
+ end
9
+ end
10
+
11
+ it 'matches the start of all the words from the file' do
12
+ words.each do |word|
13
+ expect(trie.match? word).to be true
14
+ expect(trie.match? word[0..-2]).to be true
15
+ expect(trie.partial_word? word).to be true
16
+ expect(trie.partial_word? word[0..-2]).to be true
17
+ end
18
+ end
19
+
20
+ it 'identifies words within larger strings' do
21
+ words.each do |word|
22
+ phrase = "x#{word}y"
23
+ expect(trie.words_within phrase).to include word
24
+ expect(trie.words_within? phrase).to be true
25
+ end
26
+ end
27
+
28
+ it 'allows iterating over all the words' do
29
+ expect(trie.to_a.sort).to eq words.sort
30
+ end
31
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples_for 'a trie node' do
4
+ let(:node_class) { node.class }
5
+
6
+ describe '.new' do
7
+ it 'has no letter' do
8
+ expect(node.letter).to be_nil
9
+ end
10
+
11
+ it 'has no children' do
12
+ expect(node.children.size).to eq 0
13
+ end
14
+
15
+ it 'is not terminal' do
16
+ expect(node).not_to be_terminal
17
+ end
18
+
19
+ it 'returns empty string as its word' do
20
+ expect(node.as_word).to be_empty
21
+ end
22
+
23
+ context 'with a letter and a parent' do
24
+ let(:parent) { node.class.new }
25
+ let(:node_with_parent) { node_class.new :a, parent }
26
+
27
+ it 'does not have any letter' do
28
+ expect(node_with_parent.letter).to eq :a
29
+ end
30
+
31
+ it 'has no children' do
32
+ expect(node_with_parent.children.size).to eq 0
33
+ end
34
+
35
+ it 'is not terminal' do
36
+ expect(node_with_parent).not_to be_terminal
37
+ end
38
+ end
39
+ end
40
+
41
+ describe '#root?' do
42
+ context 'when the node has a parent' do
43
+ before do
44
+ node.parent = node
45
+ end
46
+
47
+ it 'returns false' do
48
+ expect(node).not_to be_root
49
+ end
50
+ end
51
+
52
+ context 'when the node does not have a parent' do
53
+ before do
54
+ node.parent = nil
55
+ end
56
+
57
+ it 'returns true' do
58
+ expect(node).to be_root
59
+ end
60
+ end
61
+ end
62
+
63
+ describe '#terminal!' do
64
+ it 'forces the node to be terminal' do
65
+ expect(node).not_to be_terminal
66
+ node.terminal!
67
+
68
+ expect(node).to be_terminal
69
+ end
70
+
71
+ it 'returns the node' do
72
+ expect(node.terminal!).to eq node
73
+ end
74
+ end
75
+
76
+ describe 'delegates and aliases' do
77
+ let(:children_tree) do
78
+ double(
79
+ :children_tree,
80
+ :[] => 'value',
81
+ :[]= => nil,
82
+ key?: false,
83
+ delete: true,
84
+ )
85
+ end
86
+
87
+ before do
88
+ node.children_tree = children_tree
89
+ end
90
+
91
+ it 'delegates `#[]` to its children tree' do
92
+ expect(node[:key]).to eq 'value'
93
+ expect(children_tree).to have_received(:[]).with :key
94
+ end
95
+
96
+ it 'delegates `#[]=` to its children tree' do
97
+ node[:key] = 'value'
98
+ expect(children_tree).to have_received(:[]=).with(:key, 'value')
99
+ end
100
+
101
+ it 'delegates `#key?` to its children tree' do
102
+ allow(children_tree).to receive(:key?)
103
+ .with(:present_key)
104
+ .and_return true
105
+
106
+ expect(node).to have_key(:present_key)
107
+ expect(node).not_to have_key(:absent_key)
108
+ end
109
+
110
+ it 'delegates `#delete` to its children tree' do
111
+ expect(node.delete :key).to be true
112
+ expect(children_tree).to have_received(:delete).with :key
113
+ end
114
+
115
+ it 'delegates `#children` to its children tree values' do
116
+ children = [double(:child_1), double(:child_2)]
117
+ allow(children_tree).to receive(:values).and_return children
118
+
119
+ expect(node.children).to eq children
120
+ end
121
+
122
+ it 'aliases `#has_key?` to `#key?`' do
123
+ node.has_key? :nope
124
+ expect(children_tree).to have_received(:key?).with :nope
125
+ end
126
+ end
127
+ end