diff --git a/README.md b/README.md index c92504d..3b511ea 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ LESP is a lightweight, efficient spelling proofreader written in Python. It's de Simply clone the repository and run the `demo.py` file to check it out. You don't need to install any additional libraries, so this is like plug-and-play. Just note that anything below Python 3.2 won't run this since old versions don't support `concurrent.futures`, which is used to speed up the process. +PyPi package coming soon, so stay tuned for a more convenient way to install LESP! + ### Detailed installation instructions 1. Clone the repository @@ -53,26 +55,29 @@ python demo.py ## Usage 📖 -LESP is pretty easy to setup, and basic configuration are already pre-built. You can find them in `config` and `demo_config` (these are files, not folders!) and you can edit them to your liking. Note that these files are **required** for the program to run, so don't delete, move, or rename them. +LESP is pretty easy to setup, and basic demo configuration is already pre-built. You can find it in `demo_config` (this is a file, not a folder!) and you can edit it to your liking. Note that the file is **required** for the demo to run, so don't delete, move, or rename it. Not required for installing it with `pip` though (coming soon). ### Basic usage -To use LESP, you need to import `is_correct` and `get_similar` from the `lesp` module. Then, you can use them like this: +To use LESP, you need to import the `Proofreader` class from the `lesp` module. The class has a decent amount of functions, but the most important ones are `is_correct` and `get_similar`. Here's an example: ```python -from lesp import is_correct, get_similar +from lesp.autocorrect import Proofreader -clearlynotcorrect = is_correct("apgle") # False +proofreader = Proofreader(wordlist="my_wordlist.txt") +clearlynotcorrect = proofreader.is_correct("apgle") # False if not clearlynotcorrect: - print("Did you mean: " + get_similar("apgle")) # Did you mean: apple + print("Did you mean: " + proofreader.get_similar("apgle")) # Did you mean: apple ``` Simple as that! ### Advanced usage -You can use a different wordlist by specifying it in the `config` file. Just note that the wordlist must be a `.txt` file, and it must be in the same folder as the file you want to run. +By default, `Proofreader` will use the `lesp-wordlist.txt` file as the wordlist. + +You can use a different wordlist by specifying the path to it in the `wordlist` argument, when initializing the `Proofreader` class. A wordlist must be structured with each word on a new line, like this: @@ -82,18 +87,22 @@ banana orange ``` -After you've done with putting a good wordlist in the directory, specify it in the `config` file like this: +When finished with writing your wordlist, save it as a `.txt` file. Then, you can use it like this: -``` -wordlist="my_wordlist.txt" +```python +from lesp.autocorrect import Proofreader + +proofreader = Proofreader(wordlist="my_wordlist.txt") ``` You can customize the process of getting similar words as well. Configuration will be provided as arguments to the `get_similar` function. Here's an example: ```python -from lesp import get_similar +from lesp.autocorrect import Proofreader + +proofreader = Proofreader(wordlist="my_wordlist.txt") -similar_words = get_similar("apgle", similarity_rate=0.5, chunks=4, upto=3) +similar_words = proofreader.get_similar("apgle", similarity_rate=0.5, chunks=4, upto=3) print(similar_words) ``` @@ -108,12 +117,14 @@ The `upto` argument specifies how many similar words will be returned. If you se ### Get similarity score -Even if this function isn't really supposed to be a feature, you can still use it if you want to. It's pretty simple to use, just import `get_similarity_score` from the `lesp` module and use it like this: +Even if this function isn't really supposed to be a feature, you can still use it if you want to. It's pretty simple to use, just use the `get_similarity_score` function of the `Proofreader` class and pass the two words you want to compare as arguments. Here's an example: ```python -from lesp import get_similarity_score +from lesp.autocorrect import Proofreader -score = get_similarity_score("apple", "apgle") # 0.8 +proofreader = Proofreader(wordlist="my_wordlist.txt") + +score = proofreader.get_similarity_score("apple", "apgle") # 0.8 print(score) ``` @@ -125,9 +136,11 @@ The function will return a float between 0 and 1, where 0 means that the words a If you're concerned about losing your wordlist, you can use the `backup` function to backup your wordlist. It will create a file in the path you specify, and it will write the wordlist in it. Note that the file will be overwritten if it already exists. Here's an example: ```python -from lesp import backup +from lesp.autocorrect import Proofreader + +proofreader = Proofreader(wordlist="my_wordlist.txt") -backup("my_wordlist_backup.txt") # Leave empty to use default path +proofreader.backup("my_wordlist_backup.txt") # Leave empty to use default path ``` ### Restore @@ -135,9 +148,11 @@ backup("my_wordlist_backup.txt") # Leave empty to use default path If you've backed up your wordlist, you can restore it using the `restore` function. It will read the file you specify and it will overwrite the current wordlist with the one in the file. Note that the file must exist, otherwise the function will raise a `FileNotFoundError`. Here's an example: ```python -from lesp import restore +from lesp.autocorrect import Proofreader -restore(True, "my_wordlist_backup.txt") # Leave empty to use default path +proofreader = Proofreader(wordlist="my_wordlist.txt") + +proofreader.restore(True, "my_wordlist_backup.txt") # Leave empty to use default path ``` True here stands for `overridecurrent`, which lets you choose whether you want the wordlist file to be overwritten or not. If you set it to `False`, then the function will leave your current wordlist file untouched, and will just modify the wordlist variable in the current session. If you set it to `True`, then the function will overwrite the wordlist file with the one in the backup file along with the wordlist variable in the current session. @@ -147,13 +162,15 @@ True here stands for `overridecurrent`, which lets you choose whether you want t This is useful if the user usually writes about a specific, non-general topic. For example, if the user is a programmer, you can extend the wordlist with programming-related words if one is not found in the wordlist already. Here's an example: ```python -from lesp import is_correct, extend_wordlist, backup, get_similar +from lesp.autocorrect import Proofreader + +proofreader = Proofreader(wordlist="my_wordlist.txt") -if not is_correct("reactjs") and get_similar("reactjs") is None: +if not proofreader.is_correct("reactjs") and proofreader.get_similar("reactjs") is None: confirm = input("reactjs is not in the wordlist. Would you like to add it? (y/n) ") if confirm.lower() == "y": - backup() - extend_wordlist("reactjs") + proofreader.backup() + proofreader.extend_wordlist("reactjs") print("reactjs added to wordlist.") else: pass @@ -162,11 +179,13 @@ if not is_correct("reactjs") and get_similar("reactjs") is None: You can also extend the wordlist with multiple words at once by passing a list or a tuple to the function. Like this: ```python -from lesp import extend_wordlist +from lesp.autocorrect import Proofreader + +proofreader = Proofreader(wordlist="my_wordlist.txt") words = ["reactjs", "vuejs", "angularjs"] -extend_wordlist(words) +proofreader.extend_wordlist(words) ``` ### Remove from wordlist @@ -174,20 +193,24 @@ extend_wordlist(words) An opposite of the `extend_wordlist` function, this function removes a word from the wordlist. Note that this function will raise a `ValueError` if the word is not in the wordlist. Also note that this function will not remove the word from the wordlist permanently, it will only remove it for the current session. Here's an example: ```python -from lesp import remove_from_wordlist +from lesp.autocorrect import Proofreader + +proofreader = Proofreader(wordlist="my_wordlist.txt") word = "reactjs" -remove_from_wordlist(word) +proofreader.remove_from_wordlist(word) ``` If you want to remove multiple words at once, you can pass a list or a tuple to the function. Like this: ```python -from lesp import remove_from_wordlist +from lesp.autocorrect import Proofreader + +proofreader = Proofreader(wordlist="my_wordlist.txt") words = ["reactjs", "vuejs", "angularjs"] -remove_from_wordlist(words) +proofreader.remove_from_wordlist(words) ``` ### Stacking @@ -195,9 +218,9 @@ remove_from_wordlist(words) This function lets you stack two wordlist files together, so you can have a bigger wordlist out of two combined. The function will take two arguments, the source file and the destination file. The source file is the file that will be stacked on top of the destination file. Here's an example: ```python -from lesp import stack +from lesp.autocorrect import Proofreader -stack("wordlist.txt", "my_wordlist.txt") +proofreader.stack("wordlist.txt", "my_wordlist.txt") ``` ### Merge delete @@ -228,9 +251,11 @@ raspberry Here's an example of how you can use it: ```python -from lesp import merge_delete +from lesp.autocorrect import Proofreader + +proofreader = Proofreader(wordlist="my_wordlist.txt") -merge_delete("wordlist.txt", "my_wordlist.txt") +proofreader.merge_delete("wordlist.txt", "my_wordlist.txt") with open("my_wordlist.txt", "r") as f: print(f.read()) @@ -240,6 +265,10 @@ with open("my_wordlist.txt", "r") as f: If you're still not sure where to use LESP, you can check out the `examples` folder. It contains some examples of how you can use LESP in your projects. These examples are pretty simple, but they should give you an idea of how you can use LESP in your projects. +### How to run an example? + +Simply open the folder of the example you want to run, then copy the `main.py` file to the root of the directory (same as `demo.py`, for instance). After that, run the `main.py` file and voila! The application is running! + ## Contributing 🤝 Contributions, issues and feature requests are welcome! Feel free to check out the [issues page](https://github.com/LyubomirT/lesp/issues). @@ -283,8 +312,8 @@ You can contact me on Discord either in my [Discord Server](https://discord.gg/X ## Future plans 📅 - [ ] Optimize even further -- [ ] Add more examples -- [ ] Improve documentation +- [x] Add more examples +- [x] Improve documentation ## License 📜 diff --git a/demo.py b/demo.py index 7ae93f5..9d06456 100644 --- a/demo.py +++ b/demo.py @@ -1,4 +1,4 @@ -from lesp.autocorrect import is_correct, get_similar +from lesp.autocorrect import Proofreader def load_config(): try: @@ -13,18 +13,22 @@ def load_config(): except FileNotFoundError as error: print(error) exit() + # showallsimilarities="True" showallsimilarities = config.split("showallsimilarities=\"")[1].split("\"")[0] def demo(): + wordlist_path = "small_wordlist.txt" # Update with the actual path to your small_wordlist.txt + proofreader = Proofreader(wordlist_path) + while True: word = input("Enter a word: ") - if is_correct(word): + if proofreader.is_correct(word): print("Correct!") else: print("Incorrect!") - similar = get_similar(word, 0.5, chunks=20, upto=5) - if similar == None: + similar = proofreader.get_similar(word, 0.5, chunks=20, upto=5) + if similar is None: print("No similar words found.") elif showallsimilarities == "True": print("Did you mean any of the following?") @@ -36,4 +40,4 @@ def demo(): print() if __name__ == "__main__": - demo() \ No newline at end of file + demo() diff --git a/examples/A Simple Spell Checker/main.py b/examples/A Simple Spell Checker/main.py index e0dfcf1..b939bc8 100644 --- a/examples/A Simple Spell Checker/main.py +++ b/examples/A Simple Spell Checker/main.py @@ -1,4 +1,7 @@ -from lesp import is_correct, get_similar +from lesp.autocorrect import Proofreader + +# Create an instance of the Proofreader class +proofreader = Proofreader() # Read a text file and check for spelling errors with open("text.txt", "r") as f: @@ -12,8 +15,8 @@ # Remove punctuation and make lowercase word = word.strip(".,").lower() # Check if the word is correct - if not is_correct(word): + if not proofreader.is_correct(word): # Get a suggestion for the word - suggestion = get_similar(word, similarity_rate=0.5, upto=1) + suggestion = proofreader.get_similar(word, similarity_rate=0.5, upto=1) # Print the word and the suggestion - print(f"{word} -> {suggestion}") + print(f"{word} -> {', '.join(suggestion)}" if suggestion else f"{word} -> No suggestions") diff --git a/examples/A Word Game/main.py b/examples/A Word Game/main.py index 9381a8d..1c4cf25 100644 --- a/examples/A Word Game/main.py +++ b/examples/A Word Game/main.py @@ -1,4 +1,4 @@ -from lesp import get_similar +from lesp.autocorrect import Proofreader import random # A list of words to choose from @@ -7,8 +7,11 @@ # Pick a random word word = random.choice(words) +# Create an instance of the Proofreader class +proofreader = Proofreader() + # Get three similar words with a similarity rate of 0.4 -similar_words = get_similar(word, similarity_rate=0.4, upto=3) +similar_words = proofreader.get_similar(word, similarity_rate=0.4, upto=3) # Add the original word to the list similar_words.append(word) diff --git a/examples/Fill In The Blanks/main.py b/examples/Fill In The Blanks/main.py index 969f9e3..69bf0d7 100644 --- a/examples/Fill In The Blanks/main.py +++ b/examples/Fill In The Blanks/main.py @@ -1,5 +1,4 @@ -#from lesp import is_correct -from lesp.autocorrect import is_correct, get_similar +from lesp.autocorrect import Proofreader import random # Load words from wordlist file @@ -20,8 +19,17 @@ # User input user_input = input("Your guess: ") +# Use the Proofreader class for spelling checking +proofreader = Proofreader() + # Check spelling -if user_input.lower() == random_word: +if proofreader.is_correct(user_input): print("Correct!") else: - print(f"Incorrect. The correct word is '{random_word}'.") \ No newline at end of file + # Get similar words + similar_words = proofreader.get_similar(user_input, similarity_rate=0.7, chunks=4, upto=3) + + if similar_words: + print(f"Incorrect. The correct word is '{random_word}'. Did you mean one of these: {', '.join(similar_words)}?") + else: + print(f"Incorrect. The correct word is '{random_word}'.") diff --git a/examples/Similarity Score Calculator/main.py b/examples/Similarity Score Calculator/main.py index 7e3efcb..b4f82d1 100644 --- a/examples/Similarity Score Calculator/main.py +++ b/examples/Similarity Score Calculator/main.py @@ -1,5 +1,5 @@ -# Import the get_similarity_score function from the lesp module -from lesp import get_similarity_score +# Import the Proofreader class from the lesp.autocorrect module +from lesp.autocorrect import Proofreader # Import tkinter for creating a graphical user interface import tkinter as tk @@ -27,13 +27,16 @@ # Create a button for calculating the similarity score button = tk.Button(window, text="Calculate") +# Create an instance of the Proofreader class +proofreader = Proofreader() + # Define a function for calculating the similarity score def calculate(): # Get the words from the entries word1 = entry1.get() word2 = entry2.get() # Calculate the similarity score - score = get_similarity_score(word1, word2) + score = proofreader.get_similarity_score(word1, word2) # Display the similarity score result.config(text=f"The similarity score is {score:.2f}") diff --git a/examples/Word Jumble Game/main.py b/examples/Word Jumble Game/main.py index 031111c..c25e30c 100644 --- a/examples/Word Jumble Game/main.py +++ b/examples/Word Jumble Game/main.py @@ -1,7 +1,10 @@ -#from lesp import is_correct -from lesp.autocorrect import is_correct, get_similar +#from lesp import Proofreader +from lesp.autocorrect import Proofreader import random +# Initialize the Proofreader instance +proofreader = Proofreader() + # Load words from wordlist file with open("wordlist.txt", "r") as f: words = [word.strip() for word in f.readlines()] @@ -15,7 +18,7 @@ guess = input("Your guess: ") # Check if guess is correct -if guess.lower() == word: +if proofreader.is_correct(guess): print("Correct!") else: print(f"Incorrect. The correct word was '{word}'.") diff --git a/lesp/autocorrect.py b/lesp/autocorrect.py index 915f6f3..289e31f 100644 --- a/lesp/autocorrect.py +++ b/lesp/autocorrect.py @@ -1,207 +1,195 @@ import concurrent.futures import os -def load_config(): - try: - with open("config", "r") as f: - config = f.read() - return config - except FileNotFoundError: - raise FileNotFoundError(f"Config File not found!") - -def load_wordlist(): - try: - with open(wordlistpath, "r") as f: - wordlist = f.read().split("\n") - if not all(word.isalpha() for word in wordlist): - raise ValueError("Invalid wordlist format. Words must contain only alphabetic characters.") - return wordlist - except FileNotFoundError: - raise FileNotFoundError(f"{wordlistpath} not found!") - -try: - config = load_config() - wordlistpath = config.split("wordlist=\"")[1].split("\"")[0] - wordlist = load_wordlist() -except FileNotFoundError as error: - print(error) - exit() - -def is_correct(word): - return word in wordlist - -def get_similarity_score(word1, word2): - len1 = len(word1) - len2 = len(word2) - matrix = [[0 for j in range(len2 + 1)] for i in range(len1 + 1)] - - for i in range(len1 + 1): - matrix[i][0] = i - for j in range(len2 + 1): - matrix[0][j] = j - - for i in range(1, len1 + 1): - for j in range(1, len2 + 1): - cost = 0 if word1[i - 1] == word2[j - 1] else 1 - matrix[i][j] = min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost) - - score = 1 - matrix[len1][len2] / max(len1, len2) - return score - -def get_similar_worker(args): - word, similarity_rate, wordlist_chunk = args - similar_words = [] - for w in wordlist_chunk: - score = get_similarity_score(word, w) - if score >= similarity_rate: - similar_words.append(w) - return similar_words - -def get_similar(word, similarity_rate, chunks=4, upto=3): - if upto < 1: - raise ValueError("Can only return 1 or more similar words.") - if chunks < 1: - raise ValueError("Can only split into 1 or more chunks.") - if similarity_rate < 0 or similarity_rate > 1: - raise ValueError("Similarity rate must be between 0 and 1.") - - word = word.lower() - similar_words = [] - chunk_size = len(wordlist) // chunks - - chunks = [(word, similarity_rate, wordlist[i:i + chunk_size]) for i in range(0, len(wordlist), chunk_size)] - - with concurrent.futures.ThreadPoolExecutor() as executor: - results = list(executor.map(get_similar_worker, chunks)) - - for similar_word_list in results: - similar_words.extend(similar_word_list) - - similar_words = list(set(similar_words)) - - if len(similar_words) == 0: - return None - else: - # Return only upto similar words - return similar_words[:upto] - - -def backup(path="wordlist_backup"): - if os.path.isdir(path): - raise ValueError("Path specified is a directory!") - with open(path, "w") as f: - f.write("\n".join(wordlist)) - - -def restore(overwritecurrent, path="wordlist_backup"): - try: - if not os.path.isfile(path): - raise FileNotFoundError("Backup file not found!") - - with open(path, "r") as f: - wordlist_ = f.read().split("\n") - - if not all(word.isalpha() for word in wordlist_): - raise ValueError("Invalid backup file format. Words must contain only alphabetic characters.") - - global wordlist - wordlist = wordlist_ - - if overwritecurrent: - with open(wordlistpath, "w") as f: - f.write("\n".join(wordlist)) - except Exception as e: - raise ValueError(f"Error during restore: {str(e)}") - -def extend_wordlist(word): - if isinstance(word, str): - if word.isalpha(): - wordlist.append(word.lower()) +class Proofreader: + def __init__(self, wordlist_path="lesp-wordlist.txt"): + self.wordlist_path = wordlist_path + self.load_wordlist() + + def load_wordlist(self): + try: + with open(self.wordlist_path, "r") as f: + self.wordlist = f.read().split("\n") + if not all(word.isalpha() for word in self.wordlist): + raise ValueError("Invalid wordlist format. Words must contain only alphabetic characters.") + except FileNotFoundError: + raise FileNotFoundError(f"{self.wordlist_path} not found!") + + @staticmethod + def get_similarity_score(word1, word2): + len1 = len(word1) + len2 = len(word2) + matrix = [[0 for j in range(len2 + 1)] for i in range(len1 + 1)] + + for i in range(len1 + 1): + matrix[i][0] = i + for j in range(len2 + 1): + matrix[0][j] = j + + for i in range(1, len1 + 1): + for j in range(1, len2 + 1): + cost = 0 if word1[i - 1] == word2[j - 1] else 1 + matrix[i][j] = min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost) + + score = 1 - matrix[len1][len2] / max(len1, len2) + return score + + @staticmethod + def get_similar_worker(args): + word, similarity_rate, wordlist_chunk = args + similar_words = [] + for w in wordlist_chunk: + score = Proofreader.get_similarity_score(word, w) + if score >= similarity_rate: + similar_words.append(w) + return similar_words + + def is_correct(self, word): + return word.lower() in self.wordlist + + def get_similar(self, word, similarity_rate, chunks=4, upto=3): + if upto < 1: + raise ValueError("Can only return 1 or more similar words.") + if chunks < 1: + raise ValueError("Can only split into 1 or more chunks.") + if similarity_rate < 0 or similarity_rate > 1: + raise ValueError("Similarity rate must be between 0 and 1.") + + word = word.lower() + similar_words = [] + chunk_size = len(self.wordlist) // chunks + + chunks = [(word, similarity_rate, self.wordlist[i:i + chunk_size]) for i in range(0, len(self.wordlist), chunk_size)] + + with concurrent.futures.ThreadPoolExecutor() as executor: + results = list(executor.map(Proofreader.get_similar_worker, chunks)) + + for similar_word_list in results: + similar_words.extend(similar_word_list) + + similar_words = list(set(similar_words)) + + if len(similar_words) == 0: + return None else: - raise ValueError(f"Invalid input: '{word}' is not a valid word.") - elif isinstance(word, (list, tuple)): - for w in word: - if isinstance(w, str) and w.isalpha(): - wordlist.append(w.lower()) + # Return only upto similar words + return similar_words[:upto] + + def backup(self, path="wordlist_backup"): + if os.path.isdir(path): + raise ValueError("Path specified is a directory!") + with open(path, "w") as f: + f.write("\n".join(self.wordlist)) + + def restore(self, overwrite_current, path="wordlist_backup"): + try: + if not os.path.isfile(path): + raise FileNotFoundError("Backup file not found!") + + with open(path, "r") as f: + wordlist_ = f.read().split("\n") + + if not all(word.isalpha() for word in wordlist_): + raise ValueError("Invalid backup file format. Words must contain only alphabetic characters.") + + self.wordlist = wordlist_ + + if overwrite_current: + with open(self.wordlist_path, "w") as f: + f.write("\n".join(self.wordlist)) + except Exception as e: + raise ValueError(f"Error during restore: {str(e)}") + + def extend_wordlist(self, word): + if isinstance(word, str): + if word.isalpha(): + self.wordlist.append(word.lower()) else: raise ValueError(f"Invalid input: '{word}' is not a valid word.") - else: - raise TypeError("Invalid input type. Please provide a string, list, or tuple of alphabetic words.") - -def remove_from_wordlist(word): - if isinstance(word, str): - if word.isalpha(): - if word in wordlist: - wordlist.remove(word) - else: - raise ValueError(f"\"{word}\" not in wordlist!") + elif isinstance(word, (list, tuple)): + for w in word: + if isinstance(w, str) and w.isalpha(): + self.wordlist.append(w.lower()) + else: + raise ValueError(f"Invalid input: '{word}' is not a valid word.") else: - raise ValueError(f"Invalid input: '{word}' is not a valid word.") - elif isinstance(word, (list, tuple)): - for w in word: - if isinstance(w, str) and w.isalpha(): - if w in wordlist: - wordlist.remove(w) + raise TypeError("Invalid input type. Please provide a string, list, or tuple of alphabetic words.") + + def remove_from_wordlist(self, word): + if isinstance(word, str): + if word.isalpha(): + if word in self.wordlist: + self.wordlist.remove(word) else: - raise ValueError(f"\"{w}\" not in wordlist!") + raise ValueError(f"\"{word}\" not in wordlist!") else: raise ValueError(f"Invalid input: '{word}' is not a valid word.") - else: - raise TypeError("Invalid input type. Please provide a string, list, or tuple of alphabetic words.") - - -def stack(source, destination): - try: - with open(source, "r") as f: - source_words = f.read().split("\n") - with open(destination, "r") as f: - destination_words = f.read().split("\n") - - if any(len(word.split()) > 1 for word in source_words): - raise ValueError("Invalid source file format. Each word must be on a separate line.") - if any(len(word.split()) > 1 for word in destination_words): - raise ValueError("Invalid destination file format. Each word must be on a separate line.") - - if not all(word.isalpha() for word in source_words): - raise ValueError("Invalid source file format. Words must contain only alphabetic characters.") - if not all(word.isalpha() for word in destination_words): - raise ValueError("Invalid destination file format. Words must contain only alphabetic characters.") - - destination_words.extend(source_words) - - with open(destination, "w") as f: - f.write("\n".join(destination_words)) - except FileNotFoundError as e: - raise FileNotFoundError(f"File not found: {str(e)}") - except Exception as e: - raise ValueError(f"Error during stacking: {str(e)}") - -def merge_delete(source, destination): - try: - with open(source, "r") as f: - source_words = f.read().split("\n") - with open(destination, "r") as f: - destination_words = f.read().split("\n") - - if any(len(word.split()) > 1 for word in source_words): - raise ValueError("Invalid source file format. Each word must be on a separate line.") - if any(len(word.split()) > 1 for word in destination_words): - raise ValueError("Invalid destination file format. Each word must be on a separate line.") - - if not all(word.isalpha() for word in source_words): - raise ValueError("Invalid source file format. Words must contain only alphabetic characters.") - if not all(word.isalpha() for word in destination_words): - raise ValueError("Invalid destination file format. Words must contain only alphabetic characters.") - - destination_words_ = list(set(destination_words) - set(source_words)) - - # All other words in the source file that are not in the destination file will be added to the destination file - - destination_words_ += [word for word in source_words if word not in destination_words] - - with open(destination, "w") as f: - f.write("\n".join(destination_words_)) - except FileNotFoundError as e: - raise FileNotFoundError(f"File not found: {str(e)}") - except Exception as e: - raise ValueError(f"Error during merge delete: {str(e)}") + elif isinstance(word, (list, tuple)): + for w in word: + if isinstance(w, str) and w.isalpha(): + if w in self.wordlist: + self.wordlist.remove(w) + else: + raise ValueError(f"\"{w}\" not in wordlist!") + else: + raise ValueError(f"Invalid input: '{word}' is not a valid word.") + else: + raise TypeError("Invalid input type. Please provide a string, list, or tuple of alphabetic words.") + + @staticmethod + def stack(source, destination): + try: + with open(source, "r") as f: + source_words = f.read().split("\n") + with open(destination, "r") as f: + destination_words = f.read().split("\n") + + if any(len(word.split()) > 1 for word in source_words): + raise ValueError("Invalid source file format. Each word must be on a separate line.") + if any(len(word.split()) > 1 for word in destination_words): + raise ValueError("Invalid destination file format. Each word must be on a separate line.") + + if not all(word.isalpha() for word in source_words): + raise ValueError("Invalid source file format. Words must contain only alphabetic characters.") + if not all(word.isalpha() for word in destination_words): + raise ValueError("Invalid destination file format. Words must contain only alphabetic characters.") + + destination_words.extend(source_words) + + with open(destination, "w") as f: + f.write("\n".join(destination_words)) + except FileNotFoundError as e: + raise FileNotFoundError(f"File not found: {str(e)}") + except Exception as e: + raise ValueError(f"Error during stacking: {str(e)}") + + @staticmethod + def merge_delete(source, destination): + try: + with open(source, "r") as f: + source_words = f.read().split("\n") + with open(destination, "r") as f: + destination_words = f.read().split("\n") + + if any(len(word.split()) > 1 for word in source_words): + raise ValueError("Invalid source file format. Each word must be on a separate line.") + if any(len(word.split()) > 1 for word in destination_words): + raise ValueError("Invalid destination file format. Each word must be on a separate line.") + + if not all(word.isalpha() for word in source_words): + raise ValueError("Invalid source file format. Words must contain only alphabetic characters.") + if not all(word.isalpha() for word in destination_words): + raise ValueError("Invalid destination file format. Words must contain only alphabetic characters.") + + destination_words_ = list(set(destination_words) - set(source_words)) + + # All other words in the source file that are not in the destination file will be added to the destination file + + destination_words_ += [word for word in source_words if word not in destination_words] + + with open(destination, "w") as f: + f.write("\n".join(destination_words_)) + except FileNotFoundError as e: + raise FileNotFoundError(f"File not found: {str(e)}") + except Exception as e: + raise ValueError(f"Error during merge delete: {str(e)}") \ No newline at end of file diff --git a/small_wordlist.txt b/small_wordlist.txt index 3164d7b..6361e6d 100644 --- a/small_wordlist.txt +++ b/small_wordlist.txt @@ -9997,4 +9997,4 @@ varieties arbor mediawiki configurations -poison +poison \ No newline at end of file