rambling-trie-opal 2.1.1

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.
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