15 Commits

  1. 1
      .gitignore
  2. 18
      candidates.py
  3. 56
      dictionary.py
  4. 127
      main.py
  5. 2
      requirements.txt
  6. 77
      word_remove_dialog.py

1
.gitignore

@ -1 +1,2 @@
__pycache__
.#*

18
candidates.py

@ -0,0 +1,18 @@
import itertools
import re
from dictionary import Dictionary
def candidates(letters, dictionary: Dictionary, excludes, min=2):
possibilities = []
for length in range(min, len(letters) + 1):
for comb in itertools.combinations(letters, length):
for perm in itertools.permutations(comb):
word = "".join(perm)
possibilities.append(word)
return dictionary.filter(possibilities, excludes)
def filter_possibilities(possibilities, regex):
return set(filter(regex.match, possibilities))

56
dictionary.py

@ -0,0 +1,56 @@
import spellchecker
import pathlib
import concurrent
from concurrent.futures import ThreadPoolExecutor
EXECUTOR = ThreadPoolExecutor()
DEFAULT = spellchecker.WordFrequency()
ALTERNATE = spellchecker.WordFrequency()
package_root = pathlib.Path(spellchecker.__file__).parent
english = package_root / "resources" / "en.json.gz"
extra_words = pathlib.Path(__file__, "..", "extra_words.txt")
def do_operation(bound_method, *args, **kwargs):
bound_method(*args, **kwargs)
obj = bound_method.__self__
return obj
default_load_future = EXECUTOR.submit(do_operation, DEFAULT.load_dictionary, english)
alternate_load_future = EXECUTOR.submit(
do_operation, ALTERNATE.load_text_file, extra_words
)
class Dictionary:
def __init__(self, loader_future: concurrent.futures._base.Future):
self.future = loader_future
self._word_frequency = None
@property
def word_frequency(self) -> spellchecker.WordFrequency:
if self._word_frequency is None:
self._word_frequency = self.future.result()
return self._word_frequency
def filter(self, wordlist, excludes=None):
"""Finds words that are in the dictionary"""
if excludes is None:
def filter_func(word):
return word in self.word_frequency
else:
def filter_func(word):
return word not in excludes and word in self.word_frequency
return set(filter(filter_func, wordlist))
DEFAULT = Dictionary(default_load_future)
ALTERNATE = Dictionary(alternate_load_future)

127
main.py

@ -1,63 +1,88 @@
#!/usr/bin/python
#!/usr/bin/python3
import argparse
from string import ascii_lowercase
import cmd2
import re
import itertools
from spellchecker import SpellChecker
from concurrent.futures import ThreadPoolExecutor
POOL = ThreadPoolExecutor()
SPELL = None
import os
from candidates import candidates, filter_possibilities
from dictionary import DEFAULT, ALTERNATE
from word_remove_dialog import RemoveWordsActivity
def init_spell_checker():
global SPELL
SPELL = SpellChecker()
SPELL.word_frequency.load_text_file('extra_words.txt')
LOWERCASE = set(ascii_lowercase)
DICTS = {"default": DEFAULT, "alt": ALTERNATE}
# argparsers
dictionary_manage_parser = argparse.ArgumentParser()
commands = dictionary_manage_parser.add_mutually_exclusive_group()
commands.add_argument(
"-l", "--list", action="store_true", help="List available dictionaries"
)
commands.add_argument("-s", "--switch", help="Dictionary to swap to")
future = POOL.submit(init_spell_checker)
letter_set_manage = argparse.ArgumentParser()
letter_set_manage.add_argument("letter", nargs="+")
guessing = argparse.ArgumentParser()
guessing.add_argument("pattern", help="Pattern to match")
def canidates(letters, min=2):
pos = []
for length in range(min, len(letters)+1):
for comb in itertools.combinations(letters, length):
for perm in itertools.permutations(comb):
word = ''.join(perm)
pos.append(word)
return set(SPELL.known(pos))
class MainLoop(cmd2.Cmd):
"""Loop for wordscape commands
def filter_pos(pos, regex):
pat = re.compile(regex + '$')
return set(filter(pat.match, pos))
"""
prompt = "<{}> $ "
def __init__(self):
self.dict = "default"
self.excludes = set()
self.init_letters(input("Enter letters: "))
super().__init__()
def init_letters(self, letters):
if not isinstance(letters, str):
letters = "".join(letters)
letters = letters.lower()
self.letters = [l for l in letters if l in LOWERCASE]
self.prompt = MainLoop.prompt.format(", ".join(self.letters))
self.candidates = candidates(self.letters, DICTS[self.dict], self.excludes)
@cmd2.with_argparser(dictionary_manage_parser)
def do_dict(self, args):
"""list/switch dict"""
if args.list:
print(
"\n".join(
(
"{} {}".format("*" if key == self.dict else " ", key)
for key in DICTS.keys()
)
)
)
else:
DICTS[args.switch]
self.dict = args.switch
@cmd2.with_argparser(letter_set_manage)
def do_change_letters(self, args):
"""Change the letters on the board"""
self.init_letters(args.letter)
@cmd2.with_argparser(guessing)
def do_find(self, args):
"""Find words that match a pattern"""
pattern = re.compile(args.pattern + "$")
matching_words = filter_possibilities(self.candidates, pattern)
app = RemoveWordsActivity(matching_words, self.excludes)
if os.name == "nt":
app.run(fork=False)
else:
app.run()
was_canceled, new_exludes = app.get_results()
if not was_canceled:
self.excludes.update(new_exludes)
if __name__ == "__main__":
while True:
letters = input('Enter letters: ')
if letters == ":quit:":
break
if SPELL is None:
future.result()
pos = canidates(letters)
inp = None
guessed = set()
while inp not in (":quit:", ":new:"):
if inp == ":enter:":
inp = None
while inp != ":done:":
if inp is not None:
guessed.add(inp)
inp = input("Enter guessed word, :done: to exit: ")
elif inp is not None:
fpos = filter_pos(pos, inp)
for word in fpos-guessed:
print('>>>', word)
print('Letters: ', ','.join(letters))
prompt = """\
Enter pattern,
':new:' for new letters,
':enter:' to fill in guessed words, or
':quit:' to exit: """
inp = input(prompt)
if inp == ":quit:":
break
MainLoop().cmdloop()

2
requirements.txt

@ -1 +1,3 @@
pyspellchecker
cmd2
npyscreen

77
word_remove_dialog.py

@ -0,0 +1,77 @@
import npyscreen
import bisect
class RemovableMulti(npyscreen.MultiLineAction):
def actionHighlighted(self, act_on_this, keypress):
self.remove_item()
def register_oppisite(self, opposite):
self.opposite = opposite
opposite.opposite = self
def remove_item(self):
selected_value = self.values[self.cursor_line]
del self.values[self.cursor_line]
bisect.insort(self.opposite.values, selected_value)
self.opposite.display()
self.display()
class BoxedRemovableMulti(npyscreen.BoxTitle):
_contained_widget = RemovableMulti
class AddRemoveForm(npyscreen.ActionFormV2):
def create(self):
self.keepers = self.add(
BoxedRemovableMulti,
values=sorted(self.parentApp.starting_values),
name="Words to keep",
max_height=10,
)
self.to_remove = self.add(
BoxedRemovableMulti,
values=sorted(self.parentApp.starting_excludes),
name="Words to remove",
max_height=10,
)
self.keepers.entry_widget.register_oppisite(self.to_remove.entry_widget)
def on_cancel(self):
self.canceled = True
def on_ok(self):
self.canceled = False
def afterEditing(self):
self.parentApp.setNextForm(None)
class RemoveWordsActivity(npyscreen.NPSAppManaged):
def __init__(self, words, excludes, *args, **kwargs):
super().__init__(*args, **kwargs)
self.starting_values = words
self.starting_excludes = excludes
def onStart(self):
self.addForm(
"MAIN",
AddRemoveForm,
name="Exclude Words",
minimum_lines=0,
minimum_columns=0,
)
def get_results(self):
form = self.getForm("MAIN")
vals = form.to_remove.entry_widget.values
canceled = form.canceled
return canceled, vals
if __name__ == "__main__":
values = ["hello", "return", "butt", "fuck", "abcdefghijklmnopqrstuvwxyz"]
app = RemoveWordsActivity(values)
app.run()
Loading…
Cancel
Save