#!/usr/bin/ruby -w # -*- coding: utf-8 -*- # # Copyright by Scott Severance # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # This is a little command-line tool to find words corresponding to a given # phone number. It is meant to run interactively from the command line. The # Ubuntu/Debian package 'scowl' should be installed for the best results. ################################################################################ # INITIAL SETUP: # At least one of $dictionary and $dictDir must be set properly. # Set the following to the path of a myspell/hunspell dictionary on your system. # This will also work with a plain text file containing one word per line. $dictionary = '/usr/share/hunspell/en_US.dic' # Set the following to the scowl directory. $dictDir = '/usr/share/dict/scowl' # Set the following to the encoding of the dictionary file. It is specified in # the .dic file's corresponding .aff file. $encoding = 'iso-8859-1' ################################################################################ begin require 'readline' $readline = true rescue LoadError puts "The readline library is not available on this system. Command editing will be limited.".gsub(/^[ ]+/, '') $readline = false end class NoNumberError < Exception end class PhoneWords @@wordListCounter = 0 def initialize(num=nil) @number = num makeWordList unless defined? @@WordList end def listWords @@WordList end def makeWordList startTime = Time.now.to_f @@wordListCounter += 1 puts "Building the word list. This may take a while." # Choose the files to examine for words #fileBlacklist = [ /(british|canadian|proper-names|roman-numerals|hacker|variant)/, /^[.]+$/ ] fileList = [] begin fileList = Dir.entries($dictDir).delete_if do |x| delete = false if x =~ /(british|canadian|proper-names|roman-numerals|hacker|variant)/ || File.directory?(x) delete = true end delete end rescue Errno::ENOENT $stderr.puts "The directory '#{$dictDir}' doesn't exist. Skipping..." end fileList.collect! { |x| "#{$dictDir}/#{x}" }.push($dictionary) # Make the word list wordList = [] fileList.each do |filename| begin File.open(filename) do |file| file.each do |line| line = line.encode('utf-8', $encoding).upcase.chomp line.gsub!(/^([^\/\t ]*).*/,'\1').gsub!(/[^A-Z]+/,'') wordList.push(line) if line.length > 0 end end rescue Errno::ENOENT $stderr.puts "The file '#{filename}' doesn't exist. Skipping..." end end @@WordList = wordList.sort.uniq endTime = Time.now.to_f printf "Finished building the word list. It took %.3f seconds to complete.\nThe list contains %d words.\n\n", (endTime - startTime), @@WordList.length end private def trimWordList(len,wordList) wordList.select { |word| word.length == len } end def splitNumber(numberStr) x = numberStr.gsub(/[^\d-]*/,'') x.gsub!(/-/,'1') x.split(/[01]+/) end def numToWord(numberStr,wordList) correlation = { 'A' => 2, 'B' => 2, 'C' => 2, 'D' => 3, 'E' => 3, 'F' => 3, 'G' => 4, 'H' => 4, 'I' => 4, 'J' => 5, 'K' => 5, 'L' => 5, 'M' => 6, 'N' => 6, 'O' => 6, 'P' => 7, 'R' => 7, 'S' => 7, #'Q' => 7, 'T' => 8, 'U' => 8, 'V' => 8, 'W' => 9, 'X' => 9, 'Y' => 9, #'Z' => 9 } digits = numberStr.split(//) digits.map! { |n| n.to_i } words = Array.new wordList.each do |word| i = 0 word.split(//).each do |letter| break unless correlation[letter] == digits[i] if i == word.length - 1 words.push(word) break end i += 1 end end words end public attr_reader :number def number=(n) @number = n.to_s end def generate(print_results = false) raise NoNumberError, "No number specified" unless @number result = [] phoneNumber = splitNumber(@number) phoneNumber.each do |ph| next if ph.length <= 1 wordList = trimWordList(ph.length,@@WordList) options = numToWord(ph,wordList) if print_results case options.length when 0 then printf "There are no options for the number %s.\n", ph when 1 then printf "The only option for the number %s is %s.\n", ph, options[0] else printf "The options for the number %s are: %s.\n", ph, options.join(', ') end end result.push({:number => ph, :options => options}) end result end end def parseCommand(input) case input when /^grep /i then runGrep input.gsub(/grep (.*)/i,'\1') when /^(exit|q(uit)?|bye)$/i then runExit when nil then runExit when /\d/ then runNumber input when /^l(ist)?/i list = PhoneWords.new.listWords puts list.join(', ') puts "Words in list: #{list.length}" when /^h(elp)?|\?/i then runHelp else runError input end end def runGrep(pattern) pat = pattern.gsub(/^\/?(.*)\//,'\1') # Extract the patern from any slashes begin re = Regexp.new(pat,"i") rescue RegexpError puts "ERROR: Invalid regular expression" return false end p re puts PhoneWords.new.listWords.grep(re).join(', ') end def runExit puts '' exit 0 end def runNumber(num) PhoneWords.new(num).generate(true) end def runHelp puts <<-EOT.gsub(/^ {4}/, '') Type any phone number to list the possibilities. The number is split on "0", "1", and "-". Any other non-digit characters are ignored. Other Commands: grep regex: Lists the words matching `regex`, a regular expression as defined by Ruby. The regex automatically includes the /i (case-insensitive) switch. list: Lists all the words in the word list. exit, quit, q, or bye: Exits. EOT end def runError(input=nil) puts "Unrecognized input: " + input end def main() while true begin if $readline parseCommand Readline.readline('Command ("h" for help): ', true) else print "\nCommand (\"h\" for help): " parseCommand gets.chomp end rescue Interrupt runExit end end end main if $0 == __FILE__