├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── lib ├── ruby-dictionary.rb └── ruby-dictionary │ ├── dictionary.rb │ ├── version.rb │ └── word_path.rb ├── ruby-dictionary.gemspec ├── spec ├── fixtures │ ├── compressed_lined.txt.gz │ ├── compressed_piped.txt.gz │ ├── uncompressed_lined.txt │ └── uncompressed_piped.txt ├── ruby-dictionary │ ├── dictionary_spec.rb │ └── word_path_spec.rb └── spec_helper.rb └── tasks ├── debug.rake └── rspec.rake /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .idea 6 | .yardoc 7 | Gemfile.lock 8 | InstalledFiles 9 | _yardoc 10 | coverage 11 | doc/ 12 | lib/bundler/man 13 | pkg 14 | rdoc 15 | spec/reports 16 | test/tmp 17 | test/version_tmp 18 | tmp 19 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | rvm: 4 | - 1.9.3 5 | - 2.0.0 6 | - ruby-head 7 | 8 | matrix: 9 | allow_failures: 10 | - rvm: ruby-head 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in ruby-dictionary.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Matt Huggins 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dictionary 2 | 3 | [![Build Status](https://secure.travis-ci.org/mhuggins/ruby-dictionary.png)](http://travis-ci.org/mhuggins/ruby-dictionary) 4 | [![Code Climate](https://codeclimate.com/github/mhuggins/ruby-dictionary.png)](https://codeclimate.com/github/mhuggins/ruby-dictionary) 5 | 6 | [ruby-dictionary](https://github.com/mhuggins/ruby-dictionary) provides a 7 | simple dictionary that allows for checking existence of words and finding a 8 | subset of words given a prefix. 9 | 10 | ## Installation 11 | 12 | Add this line to your application's Gemfile: 13 | 14 | gem 'ruby-dictionary' 15 | 16 | And then execute: 17 | 18 | $ bundle 19 | 20 | Or install it yourself as: 21 | 22 | $ gem install ruby-dictionary 23 | 24 | ## Usage 25 | 26 | A dictionary is created by passing an array of strings to the initializer. 27 | 28 | dictionary = Dictionary.new(%w(a ab abs absolute absolutes absolutely 29 | absolve be bee been bees bend bent best)) 30 | 31 | Alternatively, words can be read in from a file (raw or gzip compressed) as 32 | well. 33 | 34 | dictionary = Dictionary.from_file('path/to/uncompressed.txt') 35 | dictionary = Dictionary.from_file('path/to/compressed.txt.gz') 36 | 37 | It is assumed that the file contains one word per line. However, a separator 38 | can be passed to the method as an optional second parameter if that's not the 39 | case. 40 | 41 | dictionary = Dictionary.from_file('path/to/uncompressed.txt', ' ') 42 | dictionary = Dictionary.from_file('path/to/compressed.txt.gz', ',') 43 | 44 | Once a dictionary is loaded, the `#exists?` method can be used to determine if 45 | a word exists. 46 | 47 | dictionary.exists?('bees') # => true 48 | dictionary.exists?('wasps') # => false 49 | 50 | The `#starting_with` method returns a sorted array of all words starting with 51 | the provided string. 52 | 53 | dictionary.starting_with('bee') # => ["bee", "been", "bees"] 54 | dictionary.starting_with('foo') # => [] 55 | 56 | The `#prefixes` method returns a sorted array of all the words appearing in the 57 | beginning of the provided string. 58 | 59 | dictionary.prefixes('abstract') # => ["a", "ab", "abs"] 60 | dictionary.prefixes('bend') # => ["be", "bend"] 61 | 62 | 63 | ### Case Sensitivity 64 | 65 | By default, a new `Dictionary` is case-insensitive, meaning "bee", "Bee", and 66 | "BEE" are all considered to be the same, regardless of adding to the dictionary 67 | or searching within it. 68 | 69 | However, you can choose to use case-sensitive dictionary by passing an optional 70 | `true` parameter to both the `#new` and `#from_file` methods. 71 | 72 | dictionary = Dictionary.new(%w(Alpha Beta), true) 73 | dictionary.exists?('Alpha') # => true 74 | dictionary.exists?('alpha') # => false 75 | 76 | dictionary = Dictionary.from_file('restaurants.txt', "\n", true) 77 | dictionary.starting_with('Mc') # => ["McDonald's"] 78 | dictionary.starting_with('mc') # => [] 79 | 80 | Additionally, you can determine whether a dictionary is case-sensitive via the 81 | `#case_sensitive?` method. 82 | 83 | dictionary = Dictionary.new([]) 84 | dictionary.case_sensitive? # => false 85 | 86 | dictionary = Dictionary.new([], true) 87 | dictionary.case_sensitive? # => true 88 | 89 | ## Contributing 90 | 91 | 1. Fork it 92 | 2. Create your feature branch (`git checkout -b my-new-feature`) 93 | 3. Commit your changes (`git commit -am 'Add some feature'`) 94 | 4. Push to the branch (`git push origin my-new-feature`) 95 | 5. Create new Pull Request 96 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | 3 | Dir.glob('tasks/**/*.rake').each(&method(:import)) 4 | 5 | desc 'Default: run unit specs' 6 | task default: :spec 7 | -------------------------------------------------------------------------------- /lib/ruby-dictionary.rb: -------------------------------------------------------------------------------- 1 | require 'ruby-dictionary/dictionary' 2 | require 'ruby-dictionary/version' 3 | -------------------------------------------------------------------------------- /lib/ruby-dictionary/dictionary.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'zlib' 4 | require 'ruby-dictionary/word_path' 5 | 6 | class Dictionary 7 | def initialize(word_list, case_sensitive = false) 8 | @word_path = parse_words(word_list, case_sensitive) 9 | end 10 | 11 | def case_sensitive? 12 | @word_path.case_sensitive? 13 | end 14 | 15 | def exists?(word) 16 | path = word_path(word) 17 | !!(path && path.leaf?) 18 | end 19 | 20 | def starting_with(prefix) 21 | prefix = prefix.to_s.strip 22 | prefix = prefix.downcase unless case_sensitive? 23 | 24 | path = word_path(prefix) 25 | return [] if path.nil? 26 | 27 | words = [].tap do |words| 28 | words << prefix if path.leaf? 29 | words.concat(path.suffixes.collect! { |suffix| "#{prefix}#{suffix}" }) 30 | end 31 | 32 | words.sort! 33 | end 34 | 35 | def prefixes(string) 36 | string = string.to_s.strip 37 | string = string.downcase unless case_sensitive? 38 | 39 | @word_path.find_prefixes(string).sort 40 | end 41 | 42 | 43 | def hash 44 | self.class.hash ^ @word_path.hash 45 | end 46 | 47 | def ==(obj) 48 | obj.class == self.class && obj.hash == self.hash 49 | end 50 | 51 | def inspect 52 | "#<#{self.class.name}>" 53 | end 54 | 55 | def to_s 56 | inspect 57 | end 58 | 59 | def self.from_file(path, separator = "\n", case_sensitive = false) 60 | contents = case path 61 | when String then File.read(path) 62 | when File then path.read 63 | else raise ArgumentError, 'path must be a String or File' 64 | end 65 | 66 | if contents.start_with?("\x1F\x8B") 67 | gz = Zlib::GzipReader.new(StringIO.new(contents)) 68 | contents = gz.read 69 | end 70 | 71 | new(contents.split(separator), case_sensitive) 72 | end 73 | 74 | private 75 | 76 | def parse_words(word_list, case_sensitive) 77 | raise ArgumentError, 'word_list should be an array of strings' unless word_list.kind_of?(Array) 78 | 79 | WordPath.new(case_sensitive).tap do |word_path| 80 | word_list.each { |word| word_path << word.to_s } 81 | end 82 | end 83 | 84 | def word_path(str) 85 | @word_path.find(str) 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/ruby-dictionary/version.rb: -------------------------------------------------------------------------------- 1 | class Dictionary 2 | VERSION = '1.1.1' 3 | end 4 | -------------------------------------------------------------------------------- /lib/ruby-dictionary/word_path.rb: -------------------------------------------------------------------------------- 1 | class Dictionary 2 | class WordPath 3 | def initialize(case_sensitive) 4 | @case_sensitive = !!case_sensitive 5 | @is_leaf = false 6 | @word_paths = {} 7 | end 8 | 9 | def case_sensitive? 10 | @case_sensitive 11 | end 12 | 13 | def leaf? 14 | @is_leaf 15 | end 16 | 17 | def leaf=(is_leaf) 18 | @is_leaf = !!is_leaf 19 | end 20 | 21 | def <<(word) 22 | raise ArgumentError, 'must be a string' unless word.kind_of?(String) 23 | word = word.downcase unless @case_sensitive 24 | _append(word.strip) 25 | end 26 | 27 | def find(word) 28 | raise ArgumentError, 'must be a string' unless word.kind_of?(String) 29 | word = word.downcase unless @case_sensitive 30 | _find(word.strip) 31 | end 32 | 33 | def suffixes 34 | [].tap do |suffixes| 35 | @word_paths.each do |letter, path| 36 | suffixes << letter if path.leaf? 37 | suffixes.concat(path.suffixes.collect { |suffix| "#{letter}#{suffix}" }) 38 | end 39 | end 40 | end 41 | 42 | def find_prefixes(string) 43 | raise ArgumentError, 'must be a string' unless string.kind_of?(String) 44 | string = string.downcase unless @case_sensitive 45 | _find_prefixes(string.strip) 46 | end 47 | 48 | 49 | def hash 50 | self.class.hash ^ @is_leaf.hash ^ @word_paths.hash ^ @case_sensitive.hash 51 | end 52 | 53 | def ==(obj) 54 | obj.class == self.class && obj.hash == self.hash 55 | end 56 | 57 | def inspect 58 | "#" 59 | end 60 | 61 | def to_s 62 | inspect 63 | end 64 | 65 | protected 66 | 67 | def _find(word) 68 | word_path = @word_paths[word[0]] 69 | return nil unless word_path 70 | 71 | if word.size == 1 72 | word_path 73 | else 74 | word_path._find(word[1, word.size]) 75 | end 76 | end 77 | 78 | def _find_prefixes(str) 79 | char = str[0] 80 | 81 | word_path = @word_paths[char] 82 | return [] unless word_path 83 | 84 | [].tap do |prefixes| 85 | prefixes << char if word_path.leaf? 86 | prefixes.concat(word_path._find_prefixes(str[1, str.size]).collect { |prefix| "#{char}#{prefix}" }) 87 | end 88 | end 89 | 90 | def _append(word) 91 | return if word.empty? 92 | 93 | char = word[0] 94 | word_path = @word_paths[char] ||= self.class.new(@case_sensitive) 95 | 96 | if word.size == 1 97 | word_path.leaf = true 98 | else 99 | word_path._append(word[1, word.size]) 100 | end 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /ruby-dictionary.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'ruby-dictionary/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = 'ruby-dictionary' 8 | gem.version = Dictionary::VERSION 9 | gem.authors = ['Matt Huggins'] 10 | gem.email = ['matt.huggins@gmail.com'] 11 | gem.description = %q{Dictionary class for ruby that allows for checking 12 | existence and finding words starting with a given 13 | prefix.} 14 | gem.summary = 'Simple dictionary class for checking existence of words' 15 | gem.homepage = 'https://github.com/mhuggins/ruby-dictionary' 16 | gem.license = 'MIT' 17 | 18 | gem.files = `git ls-files`.split($/) 19 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 20 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 21 | gem.require_paths = ['lib'] 22 | 23 | gem.add_development_dependency 'rake' 24 | gem.add_development_dependency 'rspec' 25 | end 26 | -------------------------------------------------------------------------------- /spec/fixtures/compressed_lined.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhuggins/ruby-dictionary/e7f897cb6e0e769b5a9e52f9c7c3dfb019575959/spec/fixtures/compressed_lined.txt.gz -------------------------------------------------------------------------------- /spec/fixtures/compressed_piped.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhuggins/ruby-dictionary/e7f897cb6e0e769b5a9e52f9c7c3dfb019575959/spec/fixtures/compressed_piped.txt.gz -------------------------------------------------------------------------------- /spec/fixtures/uncompressed_lined.txt: -------------------------------------------------------------------------------- 1 | a 2 | ab 3 | abs 4 | absolute 5 | absolutely 6 | 7 | be 8 | bee 9 | bees 10 | been 11 | 12 | zoo 13 | zoos 14 | zebra 15 | 16 | yuck 17 | -------------------------------------------------------------------------------- /spec/fixtures/uncompressed_piped.txt: -------------------------------------------------------------------------------- 1 | a|ab|abs|absolute|absolutely||be|bee|bees|been||zoo|zoos|zebra||yuck 2 | -------------------------------------------------------------------------------- /spec/ruby-dictionary/dictionary_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Dictionary do 4 | subject { Dictionary.new(word_list, case_sensitive) } 5 | 6 | let(:word_list) { %w(a ab abs absolute absolutely be bee bees been zoo zoos zebra yuck) } 7 | let(:case_sensitive) { false } 8 | 9 | describe '#case_sensitive?' do 10 | describe 'when case-insensitive' do 11 | it { should_not be_case_sensitive } 12 | end 13 | 14 | describe 'when case-sensitive' do 15 | let(:case_sensitive) { true } 16 | 17 | it { should be_case_sensitive } 18 | end 19 | end 20 | 21 | describe '#exists?' do 22 | describe 'when case-insensitive' do 23 | it 'finds existing single-letter words regardless of casing' do 24 | expect(subject.exists?('a')).to be_truthy 25 | expect(subject.exists?('A')).to be_truthy 26 | end 27 | 28 | it 'finds existing multi-letter words regardless of casing' do 29 | expect(subject.exists?('ab')).to be_truthy 30 | expect(subject.exists?('AB')).to be_truthy 31 | end 32 | 33 | it "doesn't find non-existing single-letter words regardless of casing" do 34 | expect(subject.exists?('e')).to be_falsey 35 | expect(subject.exists?('E')).to be_falsey 36 | end 37 | 38 | it "doesn't find non-existing multi-letter words regardless of casing" do 39 | expect(subject.exists?('eggsactly')).to be_falsey 40 | expect(subject.exists?('EGGSACTLY')).to be_falsey 41 | end 42 | end 43 | 44 | describe 'when case-sensitive' do 45 | let(:case_sensitive) { true } 46 | 47 | it 'finds existing single-letter words of the same casing' do 48 | expect(subject.exists?('a')).to be_truthy 49 | end 50 | 51 | it 'finds existing multi-letter words of the same casing' do 52 | expect(subject.exists?('ab')).to be_truthy 53 | end 54 | 55 | it "doesn't find existing single-letter words of a different casing" do 56 | expect(subject.exists?('A')).to be_falsey 57 | end 58 | 59 | it "doesn't find existing multi-letter words of a different casing" do 60 | expect(subject.exists?('AB')).to be_falsey 61 | end 62 | 63 | it "doesn't find non-existing single-letter words of any casing" do 64 | expect(subject.exists?('e')).to be_falsey 65 | expect(subject.exists?('E')).to be_falsey 66 | end 67 | 68 | it "doesn't find non-existing multi-letter words of any casing" do 69 | expect(subject.exists?('eggsactly')).to be_falsey 70 | expect(subject.exists?('EGGSACTLY')).to be_falsey 71 | end 72 | end 73 | end 74 | 75 | describe '#starting_with' do 76 | describe 'when case-insensitive' do 77 | it 'finds all words starting with a single letter regardless of casing' do 78 | %w(a A).each do |prefix| 79 | expect(subject.starting_with(prefix)).to eq %w(a ab abs absolute absolutely) 80 | end 81 | end 82 | 83 | it 'finds all words starting with multiple letters regardless of casing' do 84 | %w(abso ABSO).each do |prefix| 85 | expect(subject.starting_with(prefix)).to eq %w(absolute absolutely) 86 | end 87 | end 88 | 89 | it 'finds no words starting with unmatched single letter regardless of casing' do 90 | %w(e E).each do |prefix| 91 | expect(subject.starting_with(prefix)).to be_empty 92 | end 93 | end 94 | 95 | it 'finds no words starting with unmatched multiple letters regardless of casing' do 96 | %w(absolutetastic ABSOLUTETASTIC).each do |prefix| 97 | expect(subject.starting_with(prefix)).to be_empty 98 | end 99 | end 100 | end 101 | 102 | describe 'when case-sensitive' do 103 | let(:case_sensitive) { true } 104 | 105 | it 'finds all words starting with a single letter of the same casing' do 106 | expect(subject.starting_with('a')).to eq %w(a ab abs absolute absolutely) 107 | expect(subject.starting_with('A')).to be_empty 108 | end 109 | 110 | it 'finds all words starting with multiple letters of the same casing' do 111 | expect(subject.starting_with('abso')).to eq %w(absolute absolutely) 112 | expect(subject.starting_with('ABSO')).to be_empty 113 | end 114 | 115 | it 'finds no words starting with unmatched single letter of the same casing' do 116 | expect(subject.starting_with('e')).to be_empty 117 | expect(subject.starting_with('E')).to be_empty 118 | end 119 | 120 | it 'finds no words starting with unmatched multiple letters of the same casing' do 121 | expect(subject.starting_with('absolutetastic')).to be_empty 122 | expect(subject.starting_with('ABSOLUTETASTIC')).to be_empty 123 | end 124 | end 125 | end 126 | 127 | describe '#prefixes' do 128 | describe "for exceptional input" do 129 | it "returns empty string for empty string" do 130 | expect(subject.prefixes('')).to be_empty 131 | end 132 | 133 | it "finds the prefix of a single letter (the letter itself)" do 134 | expect(subject.prefixes('a')).to eq %w(a) 135 | end 136 | end 137 | 138 | describe 'when case-insensitive' do 139 | it 'finds all the prefixes of a string regardless of casing' do 140 | %w(abstract Abstract).each do |string| 141 | expect(subject.prefixes(string)).to eq %w(a ab abs) 142 | end 143 | end 144 | end 145 | 146 | describe 'when case-sensitive' do 147 | let(:case_sensitive) { true } 148 | 149 | it 'finds all the prefixes of a string, of the same casing' do 150 | expect(subject.prefixes('abstract')).to eq %w(a ab abs) 151 | expect(subject.prefixes('aBstract')).to eq %w(a) 152 | expect(subject.prefixes('Abstract')).to be_empty 153 | end 154 | end 155 | end 156 | 157 | 158 | describe '#inspect' do 159 | specify { expect(subject.inspect).to eq '#' } 160 | end 161 | 162 | describe '#to_s' do 163 | specify { expect(subject.to_s).to eq '#' } 164 | end 165 | 166 | describe '.from_file' do 167 | subject { Dictionary.from_file(dictionary_path, separator, case_sensitive) } 168 | 169 | let(:separator) { "\n" } 170 | 171 | shared_examples 'loaded dictionary' do 172 | it 'loads all words' do 173 | expect(subject.starting_with('a').size).to eq 5 174 | expect(subject.starting_with('b').size).to eq 4 175 | expect(subject.starting_with('y').size).to eq 1 176 | expect(subject.starting_with('z').size).to eq 3 177 | end 178 | 179 | it 'does not load nonexistent words' do 180 | ('c'..'x').each do |letter| 181 | expect(subject.starting_with(letter)).to be_empty 182 | end 183 | end 184 | 185 | describe 'when case-insensitive' do 186 | let(:case_sensitive) { false } 187 | it { should_not be_case_sensitive } 188 | end 189 | 190 | describe 'when case-sensitive' do 191 | let(:case_sensitive) { true } 192 | it { should be_case_sensitive } 193 | end 194 | end 195 | 196 | describe 'with compressed file' do 197 | describe 'separated by line' do 198 | let(:dictionary_path) { 'spec/fixtures/compressed_lined.txt.gz' } 199 | it_behaves_like 'loaded dictionary' 200 | end 201 | 202 | describe 'separated by pipe' do 203 | let(:dictionary_path) { 'spec/fixtures/compressed_piped.txt.gz' } 204 | let(:separator) { '|' } 205 | it_behaves_like 'loaded dictionary' 206 | end 207 | end 208 | 209 | describe 'with uncompressed file' do 210 | describe 'separated by line' do 211 | let(:dictionary_path) { 'spec/fixtures/uncompressed_lined.txt' } 212 | it_behaves_like 'loaded dictionary' 213 | end 214 | 215 | describe 'separated by pipe' do 216 | let(:dictionary_path) { 'spec/fixtures/uncompressed_piped.txt' } 217 | let(:separator) { '|' } 218 | it_behaves_like 'loaded dictionary' 219 | end 220 | end 221 | end 222 | end 223 | -------------------------------------------------------------------------------- /spec/ruby-dictionary/word_path_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Dictionary::WordPath do 4 | subject { Dictionary::WordPath.new(case_sensitive) } 5 | 6 | let(:case_sensitive) { false } 7 | 8 | describe '#case_sensitive?' do 9 | describe 'when case-insensitive' do 10 | it { should_not be_case_sensitive } 11 | end 12 | 13 | describe 'when case-sensitive' do 14 | let(:case_sensitive) { true } 15 | 16 | it { should be_case_sensitive } 17 | end 18 | end 19 | 20 | describe '#leaf=' do 21 | it 'should set to false' do 22 | subject.leaf = false 23 | expect(subject).not_to be_leaf 24 | end 25 | 26 | it 'should set to true' do 27 | subject.leaf = true 28 | expect(subject).to be_leaf 29 | end 30 | end 31 | 32 | describe '#find' do 33 | describe 'when case-insensitive' do 34 | before do 35 | subject << 'potato' 36 | end 37 | 38 | it 'finds existing word paths of matching case-sensitivity' do 39 | word_path = subject.find('potat') 40 | expect(word_path).to be_a Dictionary::WordPath 41 | expect(word_path).to eq Dictionary::WordPath.new(case_sensitive).tap { |wp| wp << 'o' } 42 | end 43 | 44 | it 'finds existing word paths of unmatching case-sensitivity' do 45 | word_path = subject.find('poTAt') 46 | expect(word_path).to be_a Dictionary::WordPath 47 | expect(word_path).to eq Dictionary::WordPath.new(case_sensitive).tap { |wp| wp << 'o' } 48 | end 49 | 50 | it 'does not find nonexistent word paths' do 51 | expect(subject.find('potable')).to be_nil 52 | end 53 | end 54 | 55 | describe 'when case-sensitive' do 56 | before do 57 | subject << 'poTAto' 58 | end 59 | 60 | let(:case_sensitive) { true } 61 | 62 | it 'finds existing word paths of matching case-sensitivity' do 63 | word_path = subject.find('poTAt') 64 | expect(word_path).to be_a Dictionary::WordPath 65 | expect(word_path).to eq Dictionary::WordPath.new(case_sensitive).tap { |wp| wp << 'o' } 66 | end 67 | 68 | it 'does not find existing word paths of unmatching case-sensitivity' do 69 | expect(subject.find('potat')).to be_nil 70 | end 71 | 72 | it 'does not find nonexistent word paths' do 73 | expect(subject.find('potat')).to be_nil 74 | end 75 | end 76 | end 77 | 78 | describe '#<<' do 79 | before do 80 | subject << 'potato' 81 | end 82 | 83 | describe 'when case-insensitive' do 84 | it 'appends case-insensitive word to word path' do 85 | expect(subject.find('potato')).to be_a Dictionary::WordPath 86 | expect(subject.find('poTAto')).to eq subject.find('potato') 87 | end 88 | end 89 | 90 | describe 'when case-sensitive' do 91 | let(:case_sensitive) { true } 92 | 93 | it 'appends case-sensitive word to word path' do 94 | expect(subject.find('potato')).to be_a Dictionary::WordPath 95 | expect(subject.find('poTAto')).to be_nil 96 | end 97 | end 98 | end 99 | 100 | describe '#suffixes' do 101 | before do 102 | subject << 'pot' 103 | subject << 'poTAto' 104 | subject << 'potABle' 105 | subject << 'POTTY' 106 | subject << 'pert' 107 | subject << 'jar' 108 | end 109 | 110 | describe 'when case-insensitive' do 111 | it 'finds all words with the given suffix' do 112 | word = subject.find('pot') 113 | expect(word.suffixes).to eq %w(ato able ty) 114 | end 115 | end 116 | 117 | describe 'when case-sensitive' do 118 | let(:case_sensitive) { true } 119 | 120 | it 'finds all words with the given suffix' do 121 | word = subject.find('pot') 122 | expect(word.suffixes).to eq %w(ABle) 123 | end 124 | end 125 | end 126 | end 127 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'ruby-dictionary' 2 | -------------------------------------------------------------------------------- /tasks/debug.rake: -------------------------------------------------------------------------------- 1 | desc 'Open an irb session preloaded with this library' 2 | task :console do 3 | sh 'irb -rubygems -I lib -r ruby-dictionary.rb' 4 | end 5 | -------------------------------------------------------------------------------- /tasks/rspec.rake: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | 3 | desc 'Run all examples' 4 | RSpec::Core::RakeTask.new(:spec) do |t| 5 | t.rspec_opts = %w(--color) 6 | end 7 | --------------------------------------------------------------------------------