Source code for src.ch01.challenge.c2_name_generator

"""Generate pseudo-random names from a list of names."""
import os
import random
import sys
from src.ch01.challenge import SPLIT_NAME_LIST_ERROR, SPLIT_NAME_EMPTY_ERROR,\
    ADD_NAME_TO_KEY_ERROR, GENERATE_NAME_ERROR, BUILD_LIST_ERROR


[docs]def read_from_file(filepath: str) -> list: """Read from file. Reads lines from text file and returns a :py:obj:`list`. Args: filepath (str): Path to file with names. Returns: List with each line from the file as an element. Note: Removes trailing whitespaces. """ # Read lines from file and remove trailing whitespaces. with open(filepath, 'r') as file: file_data = [line.rstrip() for line in file] return file_data
[docs]def build_name_list(folderpath: str) -> list: """Build name list from folder. Builds list of names from name files in given folder. Args: folderpath (str): Path to folder with name files. Returns: List with names from **folderpath**. Raises: IndexError: If **folderpath** has no ``.txt`` files. """ if not folderpath.endswith('/'): folderpath = folderpath + '/' files, name_list = [file for file in os.listdir(folderpath) if file.endswith('.txt')], [] if not files: raise IndexError(BUILD_LIST_ERROR) for file in files: name_list.extend(read_from_file(folderpath + file)) name_list.sort() # Sort for test consistency. return name_list
[docs]def add_name_to_key(name: str, dictionary: dict, key: str) -> None: """Add name to key in dictionary. Add **name** to **dictionary** under **key** if not already present. Args: name (str): Name to add to **dictionary**. key (str): Key to add **name** under. dictionary (dict): Dictionary to add **name** to. Returns: :py:obj:`None`. **name** is added under **key** if not present, **dictionary** is unchanged otherwise. Raises: TypeError: If **name** and **key** aren't :py:obj:`str` or if **dictionary** isn't a :py:obj:`dict`. """ if not all([isinstance(name, str), isinstance(key, str), isinstance(dictionary, dict)]): raise TypeError(ADD_NAME_TO_KEY_ERROR) # Check for repeat names while adding. if name not in dictionary[key]: dictionary[key].append(name) dictionary[key].sort() # Sort for test consistency.
[docs]def split_names(name_list: list) -> dict: """Split names from list of names. Splits first, middle, and last names from a given list of names. Args: name_list (list): List with names as elements. Returns: Dictionary of lists with ``first``, ``middle``, and ``last`` as keys and names as values. Raises: TypeError: If given name list is not a :py:obj:`list` or :py:obj:`tuple`. ValueError: If given name list is empty. Note: Drops suffix and adds nickname to middle names. """ if not any([isinstance(name_list, list), isinstance(name_list, tuple)]): raise TypeError(SPLIT_NAME_LIST_ERROR) if not name_list: raise ValueError(SPLIT_NAME_EMPTY_ERROR) names = {'first': [], 'middle': [], 'last': []} for name in name_list: # Check for quotes for nicknames and add to middle names. if '"' in name: start = name.find('"') end = name.find('"', start + 1) nickname = name[start:end + 2] # Include space for replace add_name_to_key(nickname.strip(), names, 'middle') name = name.replace(nickname, '') # Drop suffix by dropping last name if less than 4 characters, has # a '.', or all uppercase. end = name.rfind(' ') suffix = name[end:] # Include space for replace if len(suffix.strip()) < 4 and any(['.' in suffix, suffix.isupper()]): name = name.replace(suffix, '') # Check middle name by counting whitespace and add to middle names. if name.count(' ') >= 2: # For multi-name middle names start, end = name.find(' '), name.rfind(' ') middle = name[start:end] # Include leading space for replace if middle.count(' ') > 1: end = middle.rfind(' ') middle1, middle2 = middle[:end], middle[end:] add_name_to_key(middle1.strip(), names, 'middle') add_name_to_key(middle2.strip(), names, 'middle') else: add_name_to_key(middle.strip(), names, 'middle') name = name.replace(middle, '') # Split and add first and last names. end = name.find(' ') first, last = name[:end], name[end + 1:] # Exclude leading space add_name_to_key(first, names, 'first') add_name_to_key(last, names, 'last') return names
[docs]def generate_name(name_dict: dict) -> str: """Generate pseudo-random name. Use names in dictionary to generate a random name. Args: name_dict: Dictionary from :func:`split_names`. Returns: String with a random name. Raises: KeyError: If there aren't three keys in the dictionary. Note: Only add middle name between 1/3 and 1/4 of the time. """ # Raise error if there aren't three keys in dictionary. if not len(name_dict.keys()) == 3: raise KeyError(GENERATE_NAME_ERROR) keys = list(name_dict.keys()) # Make keys indexable. Python3.6 only? first, last = random.choice(name_dict[keys[0]]),\ random.choice(name_dict[keys[2]]) # Add middle name between 1/3 and 1/4 of the time. if 25 >= random.choice(range(100)) <= 33: middle = random.choice(name_dict[keys[1]]) return ' '.join([first, middle, last]) return ' '.join([first, last])
[docs]def name_generator(folderpath: str) -> str: """Wrap generate_name, split_names, and build_name_list. Passes given **folderpath** through :func:`build_name_list` to get the names in a :py:obj:`list`, then :func:`split_names` to split them into a :py:obj:`dict`, and finally through :func:`generate_name` to make the actual name. Args: folderpath (str): Path to folder with name files. Returns: String with pseudo-random name. """ return generate_name(split_names(build_name_list(folderpath)))
[docs]def main(): """Demonstrate name generator.""" print('This is a random name generator.\n' 'Similar to a character from a certain American detective\n' 'comedy-drama television series.\n') folder = os.path.abspath('src/ch01/challenge/c2files') print(f'Generated name: {name_generator(folder)}', file=sys.stderr) # Red output
if __name__ == '__main__': main()