pressletter 0.0.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.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Gemfile +12 -0
- data/Guardfile +24 -0
- data/LICENSE.txt +22 -0
- data/README.md +55 -0
- data/Rakefile +1 -0
- data/assets/dictionary.txt +386264 -0
- data/bin/pressletter +7 -0
- data/lib/pressletter.rb +16 -0
- data/lib/pressletter/core/create_letters.rb +19 -0
- data/lib/pressletter/core/find_words.rb +23 -0
- data/lib/pressletter/core/load_dictionary.rb +5 -0
- data/lib/pressletter/core/print_words.rb +5 -0
- data/lib/pressletter/core/solve.rb +6 -0
- data/lib/pressletter/core/sort_words.rb +11 -0
- data/lib/pressletter/default_config.rb +5 -0
- data/lib/pressletter/shell/api.rb +25 -0
- data/lib/pressletter/shell/cli.rb +15 -0
- data/lib/pressletter/shell/reads_input.rb +10 -0
- data/lib/pressletter/shell/writes_output.rb +7 -0
- data/lib/pressletter/values/config.rb +3 -0
- data/lib/pressletter/values/dictionary.rb +10 -0
- data/lib/pressletter/values/letters.rb +18 -0
- data/lib/pressletter/values/words.rb +11 -0
- data/lib/pressletter/version.rb +3 -0
- data/pressletter.gemspec +32 -0
- data/spec/fixtures/simple_dict.txt +7 -0
- data/spec/lib/pressletter/core/create_letters_spec.rb +14 -0
- data/spec/lib/pressletter/core/find_words_spec.rb +20 -0
- data/spec/lib/pressletter/core/load_dictionary_spec.rb +23 -0
- data/spec/lib/pressletter/core/print_words_spec.rb +12 -0
- data/spec/lib/pressletter/core/solve_spec.rb +8 -0
- data/spec/lib/pressletter/core/sort_words_spec.rb +7 -0
- data/spec/lib/pressletter/shell/api_spec.rb +26 -0
- data/spec/lib/pressletter/shell/cli_spec.rb +22 -0
- data/spec/spec_helper.rb +28 -0
- metadata +145 -0
data/bin/pressletter
ADDED
data/lib/pressletter.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Pressletter
|
2
|
+
module Shell
|
3
|
+
# This namespace is for the minimal interoperable imperative code
|
4
|
+
end
|
5
|
+
|
6
|
+
module Core
|
7
|
+
# This namespace is for the private functional code
|
8
|
+
end
|
9
|
+
|
10
|
+
module Values
|
11
|
+
# This namespace is for behavioral-less values which
|
12
|
+
# => serve as messages between core & shell and between shells.
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Dir[File.join(File.dirname(__FILE__), "**", "*.rb")].each {|file| require file }
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Pressletter::Core
|
2
|
+
def create_letters(input)
|
3
|
+
Pressletter::Values::Letters.new(
|
4
|
+
ensure_alphabetical(
|
5
|
+
input.chomp.split('').
|
6
|
+
map { |c| c.upcase }.
|
7
|
+
reject { |c| c == ' ' }.compact.sort
|
8
|
+
)
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def ensure_alphabetical(letters)
|
15
|
+
raise "Letters only!" if letters.any? {|l| l =~ /[^A-Z]/}
|
16
|
+
letters
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Pressletter::Core
|
2
|
+
def find_words(dictionary, letters)
|
3
|
+
Pressletter::Values::Words.new(
|
4
|
+
dictionary.as_array.map do |word|
|
5
|
+
word if letters_can_buy?(letters, word)
|
6
|
+
end.compact
|
7
|
+
)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def letters_can_buy?(letters, word)
|
13
|
+
letters = letters.as_hash.dup
|
14
|
+
word.dup.split('').all? do |char|
|
15
|
+
decrement_char!(char, letters)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def decrement_char!(char, letters)
|
20
|
+
letters[char] -= 1 if letters[char] && letters[char] > 0
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Pressletter::Core
|
2
|
+
def sort_words(words)
|
3
|
+
Pressletter::Values::Words.new(words.as_array.sort(&method(:size_then_alphabet)))
|
4
|
+
end
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def size_then_alphabet(word_1, word_2)
|
9
|
+
[-word_1.size, word_1] <=> [-word_2.size, word_2]
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Pressletter
|
2
|
+
module Shell
|
3
|
+
class API
|
4
|
+
include Pressletter::Core
|
5
|
+
|
6
|
+
def initialize(config=Pressletter::default_config)
|
7
|
+
@config = config
|
8
|
+
end
|
9
|
+
|
10
|
+
def solve_letters(letters)
|
11
|
+
solve(@config, create_letters(ensure_string(letters))).as_array
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def ensure_string(input)
|
17
|
+
input.respond_to?(:join) ? input.join : input
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.solve(letters, config=Pressletter::default_config)
|
23
|
+
Pressletter::Shell::API.new(config).solve_letters(letters)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Pressletter::Shell
|
2
|
+
class CLI
|
3
|
+
include Pressletter::Core
|
4
|
+
|
5
|
+
def initialize(config = Pressletter::default_config, reads_input = ReadsInput.new, writes_output = WritesOutput.new)
|
6
|
+
@config = config
|
7
|
+
@reads_input = reads_input
|
8
|
+
@writes_output = writes_output
|
9
|
+
end
|
10
|
+
|
11
|
+
def main
|
12
|
+
@writes_output.write(print_words(solve(@config, create_letters(@reads_input.read))))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Pressletter::Values
|
2
|
+
class Letters
|
3
|
+
def initialize(letters)
|
4
|
+
@letters = letters
|
5
|
+
end
|
6
|
+
|
7
|
+
def as_array
|
8
|
+
@letters
|
9
|
+
end
|
10
|
+
|
11
|
+
def as_hash
|
12
|
+
@hash ||= @letters.inject({}) do |h,l|
|
13
|
+
h[l] = h[l] ? h[l] + 1 : 1
|
14
|
+
h
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/pressletter.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pressletter/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "pressletter"
|
8
|
+
gem.version = Pressletter::VERSION
|
9
|
+
gem.authors = ["Justin Searls"]
|
10
|
+
gem.email = ["justin@testdouble.com"]
|
11
|
+
gem.description = <<-EOF
|
12
|
+
pressletter is a tool for solving Letterpress puzzles. Using the
|
13
|
+
`pressletter` binary like this:
|
14
|
+
|
15
|
+
$ pressletter eiptctbntymeiphoxvitkmzib
|
16
|
+
|
17
|
+
The argument is a list of all the letters on a pressletter board
|
18
|
+
and will yield an ordered list (from longest to shorted) of all
|
19
|
+
legal words that can be made with the provided letters.
|
20
|
+
EOF
|
21
|
+
gem.summary = %q{A tool for solving Letterpress puzzles}
|
22
|
+
gem.homepage = "https://github.com/searls/pressletter"
|
23
|
+
|
24
|
+
gem.files = `git ls-files`.split($/)
|
25
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
26
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
27
|
+
gem.require_paths = ["lib"]
|
28
|
+
|
29
|
+
gem.add_development_dependency "rspec", "~> 2.12.0"
|
30
|
+
gem.add_development_dependency "rspec-given", "~> 2.2.0"
|
31
|
+
gem.add_development_dependency "gimme", "~> 0.3.4"
|
32
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
describe "Pressletter::Core#create_letters" do
|
2
|
+
include Pressletter::Core
|
3
|
+
|
4
|
+
context "several letters" do
|
5
|
+
When(:result) { create_letters("c a ba\n") }
|
6
|
+
Then { result.as_array.should == ["A", "A", "B", "C"] }
|
7
|
+
Then { result.as_hash.should == { "A" => 2, "B" => 1, "C" => 1 }}
|
8
|
+
end
|
9
|
+
|
10
|
+
context "containing a non-alpha character" do
|
11
|
+
Then { lambda { create_letters("1") }.should raise_error "Letters only!" }
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
describe "Pressletter::Core#find_words" do
|
2
|
+
include Pressletter::Core
|
3
|
+
|
4
|
+
context "one word dictionary" do
|
5
|
+
Given(:dictionary) { Dictionary.new(["FOOD"]) }
|
6
|
+
context "and we've got the letters" do
|
7
|
+
Given(:letters) { Pressletter::Values::Letters.new(["D","F","O","O"]) }
|
8
|
+
When(:result) { find_words(dictionary, letters) }
|
9
|
+
Then { result.as_array.should == ["FOOD"] }
|
10
|
+
end
|
11
|
+
|
12
|
+
context "and we don't got the letters" do
|
13
|
+
Given(:letters) { Pressletter::Values::Letters.new(["D","F","O"]) }
|
14
|
+
When(:result) { find_words(dictionary, letters) }
|
15
|
+
Then { result.as_array.should == [] }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
describe "Pressletter::Core#load_dictionary" do
|
2
|
+
include Pressletter::Core
|
3
|
+
|
4
|
+
Given(:content) do
|
5
|
+
<<-TXT.gsub /^\s+/, ""
|
6
|
+
A
|
7
|
+
ABC
|
8
|
+
DOG
|
9
|
+
PANDA
|
10
|
+
TXT
|
11
|
+
end
|
12
|
+
Given(:path) { tempfile_location_containing(content) }
|
13
|
+
When(:result) { load_dictionary(path) }
|
14
|
+
Then { result.as_array.should == ["A", "ABC", "DOG", "PANDA"]}
|
15
|
+
|
16
|
+
def tempfile_location_containing(content)
|
17
|
+
require 'tempfile'
|
18
|
+
f = Tempfile.new("not-unique")
|
19
|
+
f.write(content)
|
20
|
+
f.close
|
21
|
+
f.path
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
describe "Pressletter::Core#print_words" do
|
2
|
+
include Pressletter::Core
|
3
|
+
|
4
|
+
Given(:words) { Pressletter::Values::Words.new(["AL", "BAR", "ZEBRA"]) }
|
5
|
+
When(:result) { print_words(words) }
|
6
|
+
Then do
|
7
|
+
result.should == "AL\n" +
|
8
|
+
"BAR\n" +
|
9
|
+
"ZEBRA"
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
describe "Pressletter::Core#solve" do
|
2
|
+
include Pressletter::Core
|
3
|
+
|
4
|
+
Given(:config) { Pressletter::Values::Config.new(File.expand_path("../../../../fixtures/simple_dict.txt", __FILE__)) }
|
5
|
+
Given(:letters) { Pressletter::Values::Letters.new("eiptctbntymeiphoxvitkmzib".upcase.split('')) }
|
6
|
+
When(:result) { solve(config, letters) }
|
7
|
+
Then { result.as_array.should == ["EPITOME", "MEMITIC", "TENT", "TINT", "COT"] }
|
8
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Pressletter::Shell
|
2
|
+
describe "ruby API" do
|
3
|
+
shared_examples_for "API methods" do
|
4
|
+
Given(:letters) { "eiptctbntymeiphoxvitkmzib".split('') }
|
5
|
+
Then { result.should == ["EPITOME", "MEMITIC", "TENT", "TINT", "COT"] }
|
6
|
+
end
|
7
|
+
|
8
|
+
Given(:config) { Pressletter::Values::Config.new(File.expand_path("../../../../fixtures/simple_dict.txt", __FILE__)) }
|
9
|
+
|
10
|
+
describe API do
|
11
|
+
subject { API.new(config) }
|
12
|
+
|
13
|
+
describe "#solve" do
|
14
|
+
it_behaves_like "API methods" do
|
15
|
+
When(:result) { subject.solve_letters(letters) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "top-level DSL" do
|
21
|
+
it_behaves_like "API methods" do
|
22
|
+
When(:result) { Pressletter.solve(letters, config) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Pressletter::Shell
|
2
|
+
describe CLI do
|
3
|
+
Given(:config) { Pressletter::Values::Config.new(File.expand_path("../../../../fixtures/simple_dict.txt", __FILE__)) }
|
4
|
+
Given(:reads_input) { gimme(ReadsInput) }
|
5
|
+
Given(:writes_output) { gimme(WritesOutput) }
|
6
|
+
|
7
|
+
subject { CLI.new(config, reads_input, writes_output) }
|
8
|
+
|
9
|
+
describe "#main" do
|
10
|
+
Given { give(reads_input).read {"eiptctbntymeiphoxvitkmzib"} }
|
11
|
+
When { subject.main }
|
12
|
+
Then do
|
13
|
+
verify(writes_output).write contains "EPITOME\n"+
|
14
|
+
"MEMITIC\n" +
|
15
|
+
"TENT\n" +
|
16
|
+
"TINT\n" +
|
17
|
+
"COT"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'gimme'
|
3
|
+
require 'rspec/given'
|
4
|
+
require 'pressletter'
|
5
|
+
|
6
|
+
|
7
|
+
module GimmeMatchers
|
8
|
+
class Contains
|
9
|
+
def initialize(expected)
|
10
|
+
@expected = expected
|
11
|
+
end
|
12
|
+
def matches?(actual)
|
13
|
+
actual.include?(@expected)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
def contains(expected)
|
17
|
+
Contains.new(expected)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
include GimmeMatchers
|
21
|
+
|
22
|
+
RSpec.configure do |config|
|
23
|
+
config.mock_framework = Gimme::RSpecAdapter
|
24
|
+
|
25
|
+
config.after(:each) do
|
26
|
+
Gimme.reset
|
27
|
+
end
|
28
|
+
end
|