"""Used to generate anagrams of a given word.
What is an anagram?
-------------------
An anagram is formed when letters in a name, word or phrase are rearranged into
another name, word or phrase.
"""
from __future__ import annotations
from collections import defaultdict
from functools import lru_cache as cache
import itertools as it
import json
from pathlib import Path
from typing import Callable, Dict, Final, Optional, Sequence
import clack
from pydantic.dataclasses import dataclass
from rich.console import Console
# dynamic globals
console = Console()
# custom types
WordContainer = Dict[str, bool]
# constants
BIG_WORD_MINIMUM: Final = 4
ENGLISH_JSON: Final = str(Path().home() / ".config/words/english.json")
SMALL_WORD_MINIMUM: Final = 3
[docs]@dataclass(frozen=True)
class Config(clack.Config):
"""Command-line arguments."""
phrase: str
minimum_word_size: Optional[int]
[docs] @classmethod
def from_cli_args(cls, argv: Sequence[str]) -> Config:
"""Parses command-line arguments."""
parser = clack.Parser()
parser.add_argument(
"phrase", help="The phrase to create anagrams from."
)
parser.add_argument(
"-m",
"--minimum-word-size",
type=int,
default=None,
help=(
"The minimum size of the anagrams we will output. Defaults to"
" %(default)s."
),
)
args = parser.parse_args(argv[1:])
kwargs = vars(args)
return Config(**kwargs)
[docs]def run(args: Config) -> int:
"""This function acts as this tool's main entry point."""
is_english_word = is_word_factory(english_words)
minimum_word_size = args.minimum_word_size or default_minimum_word_size(
args.phrase
)
found_any_matches = False
for i in range(minimum_word_size - 1, len(args.phrase)):
valid_words = set()
for combo in it.combinations(args.phrase, i + 1):
new_word = "".join(combo)
if is_english_word(new_word):
valid_words.add(new_word)
if valid_words:
if found_any_matches:
console.print()
else:
found_any_matches = True
console.rule(f"{i + 1}-letter words", style="bold black")
console.print(
sorted(list(valid_words), key=lambda word: (len(word), word))
)
return 0
[docs]@cache()
def english_words() -> WordContainer:
"""Returns a dictionary of english words to the integer 1.
Used for fast boolean check.
"""
result: WordContainer = defaultdict(bool)
with Path(ENGLISH_JSON).open("r") as fp:
result.update(json.load(fp))
return result
[docs]def is_word_factory(
make_word_container: Callable[[], WordContainer]
) -> Callable[[str], bool]:
"""Returns an 'is_word(word: str) -> bool' function."""
def is_word(word: str) -> bool:
"""Is ``word`` a valid word?."""
return make_word_container()[word]
return is_word
[docs]def default_minimum_word_size(phrase: str) -> int:
"""Returns the default minimum anagram size."""
if len(phrase) < 10:
return SMALL_WORD_MINIMUM
else:
return BIG_WORD_MINIMUM
main = clack.main_factory("anagrams", run)