diff --git a/soundchanger/change.py b/soundchanger/change.py index a9c7f79..a63e8eb 100644 --- a/soundchanger/change.py +++ b/soundchanger/change.py @@ -1,18 +1,45 @@ import re -def apply(change, string, categories={}, apply=True, zero_characters=['∅']): +def apply(changes, strings, categories={}, apply=True, zero_characters=['∅']): """Apply a sound change to a given string""" if not apply: - return string + return strings + + if isinstance(changes, str): + changes = [changes] + + return_str = isinstance(strings, str) + if isinstance(strings, str): + strings = [strings] - # Check validity of change + for change in changes: + validate_change(change) + change = convert_change_to_regex(change, categories=categories, zero_characters=zero_characters) + + original, change_to, before, after = split_change(change) + + pattern = f"({before})({original})({after})" + last_group_index = pattern.count('(') - after.count('(') + replacement = f"\\1{change_to}\\{last_group_index}" + + for i, string in enumerate(strings): + strings[i] = re.sub(pattern, replacement, f"#{string}#").strip('#') + + if return_str: + return strings[0] + return strings + + +def validate_change(change): if change.count('>') != 1: raise ValueError(f"Change {change} is not a valid sound change. (Missing character '>')") if change.count('/') > 1: raise ValueError(f"Change {change} is not a valid sound change. (More than one '/' character)") + +def convert_change_to_regex(change, categories, zero_characters): # Prepare change for regex for k, v in {' ': '', '{': '(', '}': ')', ',': '|'}.items(): change = change.replace(k, v) @@ -23,7 +50,15 @@ def apply(change, string, categories={}, apply=True, zero_characters=['∅']): change = re.sub(f'((?<=,){k}|{k}(?=,))', '|'.join(v), change) # Replacements of categories anywhere else change = change.replace(k, '('+'|'.join(v)+')') + + # Remove zero characters + for char in zero_characters: + change = change.replace(char, '') + + return change + +def split_change(change): if '/' in change: change, environment = change.split('/') else: @@ -40,13 +75,5 @@ def apply(change, string, categories={}, apply=True, zero_characters=['∅']): if not original: raise ValueError(f"Nothing to change from.") - - # Remove zero characters - for char in zero_characters: - change_to = change_to.replace(char, '') - - pattern = f"({before})({original})({after})" - last_group_index = pattern.count('(') - after.count('(') - replacement = f"\\1{change_to}\\{last_group_index}" - - return re.sub(pattern, replacement, f"#{string}#").strip('#') + + return (original, change_to, before, after) \ No newline at end of file diff --git a/tests/test_change.py b/tests/test_change.py index fba8203..0399f24 100644 --- a/tests/test_change.py +++ b/tests/test_change.py @@ -41,9 +41,7 @@ def test_complex_environment(): inputs = ['pana', 'pina', 'puna', 'pama', 'pima', 'puma'] outputs = ['pana', 'hina', 'huna', 'pama', 'hima', 'huma'] for string, output in zip(inputs, outputs): - assert apply( - change='p>h/#_{u,i}Na#', - string=string, + assert apply('p>h/#_{u,i}Na#', string, categories={'N': 'nm'} ) == output @@ -54,7 +52,7 @@ def test_complex_group_and_categories(): 'N': 'mn', }) == 'pani' -def test_with_complex_group_and_categories_and_digraphs(): +def test_complex_group_and_categories_and_digraphs(): assert apply('u>o/#V{ts,pf,t}_k{V,e,o}#', 'atsuka', categories={ 'V': ['a', 'i', 'u'] }) == 'atsoka' @@ -65,13 +63,28 @@ def test_with_complex_group_and_categories_and_digraphs(): 'V': ['a', 'i', 'u'] }) == 'matsuka' +def test_change_from_category(): + assert apply('V>o', 'tatiru', categories={ + 'V': 'aiu' + }) == 'totoro' + +def test_list_and_str_inputs(): + assert apply('a>b', 'cad') == 'cbd' + assert apply(['a>b'], 'cad') == 'cbd' + assert apply('a>b', ['cad']) == ['cbd'] + assert apply(['a>b'], ['cad']) == ['cbd'] + def test_multiple_changes(): string = 'pana' for change in ['p>f', 'n>m/fa_']: string = apply(change, string) assert string == 'fama' + assert apply(['p>f', 'n>m/fa_'], string) == 'fama' -def test_change_from_category(): - assert apply('V>o', 'tatiru', categories={ - 'V': 'aiu' - }) == 'totoro' +def test_multiple_strings(): + assert apply('p>f', ['pana', 'pune']) == [ + 'fana', 'fune' + ] + +def test_multiple_changes_and_strings(): + assert apply(['p>f', 'f>h/#_a'], ['pana', 'pune']) == ['hana', 'fune']