Add tests
This commit is contained in:
parent
3018d16cfe
commit
2515f8b343
@ -1,13 +1,60 @@
|
||||
import re
|
||||
import argparse
|
||||
|
||||
|
||||
__version__ = '0.1.0'
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
CLI entry point of the program.
|
||||
"""
|
||||
|
||||
parser = argparse.ArgumentParser(prog="soundchanger")
|
||||
parser.add_argument(
|
||||
"changes",
|
||||
help="Sound change to be applied. Multiple sound changes should be separated by a space."
|
||||
)
|
||||
parser.add_argument(
|
||||
"strings",
|
||||
help="Word that the sound change should be applied to. Multiple words should be separated by a space."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-C", "--categories",
|
||||
default={},
|
||||
help="Categories to be used in the sound change."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i", "--ignore-errors",
|
||||
action="store_false",
|
||||
help="Categories to be used in the sound change."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-z", "--zero-characters",
|
||||
default='∅-',
|
||||
help="Characters that should be empty strings in the changed words."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--version",
|
||||
action="version",
|
||||
version=f"%(prog)s {__version__}"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
print(apply(
|
||||
args.changes.split(),
|
||||
args.strings.split(),
|
||||
args.categories,
|
||||
args.ignore_errors,
|
||||
args.zero_characters
|
||||
))
|
||||
|
||||
|
||||
def apply(
|
||||
changes,
|
||||
strings,
|
||||
apply=True,
|
||||
categories={},
|
||||
ignore_errors=True,
|
||||
zero_characters=['∅']):
|
||||
zero_characters='∅-'):
|
||||
"""
|
||||
Applies a sound change or a list of sound changes to a string or
|
||||
a list of given strings.
|
||||
@ -16,22 +63,19 @@ def apply(
|
||||
If the input value is of type str, the output will also be of type str.
|
||||
|
||||
Options:
|
||||
- apply (default: True)
|
||||
Whether or not the changes should be applied.
|
||||
- categories (default: {})
|
||||
Which categories will be detected.
|
||||
For vowels it would be {'V'='aeiou'} or {'V'=['a', 'e', 'i', 'o', 'u']})
|
||||
|
||||
- ignore_errors (default: True)
|
||||
If this option is set to `True`, any erroneous sound change will be skipped.
|
||||
If set to `False`, a ValueError will be raised.
|
||||
- zero_characters (default: ['∅'])
|
||||
If set to `False`, a ValueError will be raised instead.
|
||||
|
||||
- zero_characters (default: '∅-')
|
||||
These characters will be removed in the changed words.
|
||||
For example, `apply('h>∅', 'aha')` will return 'aa', not 'a∅a'.
|
||||
"""
|
||||
|
||||
if not apply:
|
||||
return strings
|
||||
|
||||
if isinstance(changes, str):
|
||||
changes = [changes]
|
||||
|
||||
@ -42,10 +86,10 @@ def apply(
|
||||
strings = strings.copy()
|
||||
|
||||
for change in changes:
|
||||
if validate_change(change, ignore_errors=ignore_errors) == False:
|
||||
if is_valid_change(change, ignore_errors=ignore_errors) == False:
|
||||
continue
|
||||
|
||||
change = convert_change_to_regex(change, categories=categories, zero_characters=zero_characters)
|
||||
change = reformat_change_to_regex(change, categories=categories, zero_characters=zero_characters)
|
||||
original, change_to, before, after = split_change(change)
|
||||
|
||||
pattern = f"({before})({original})({after})"
|
||||
@ -60,8 +104,15 @@ def apply(
|
||||
return strings
|
||||
|
||||
|
||||
def validate_change(change, ignore_errors):
|
||||
valid = re.search(r'^[^>_/]+?>[^>_/]*?(:?/[^>_/]*?_+[^>_/]*)?$', change)
|
||||
def is_valid_change(change, ignore_errors=True):
|
||||
"""
|
||||
Returns `True` if the change is valid.
|
||||
If the change is not valid, it returns `False`
|
||||
unless `ignore_errors` is set to `False`,
|
||||
in which case it raises a ValueError.
|
||||
"""
|
||||
|
||||
valid = re.search(r'^[^>_/]+?>[^>_/]*?/?(:?[^>_/]*?_+[^>_/]*)?$', change)
|
||||
if ignore_errors:
|
||||
return valid != None
|
||||
if not valid:
|
||||
@ -69,7 +120,14 @@ def validate_change(change, ignore_errors):
|
||||
return True
|
||||
|
||||
|
||||
def convert_change_to_regex(change, categories, zero_characters):
|
||||
def reformat_change_to_regex(change, categories={}, zero_characters='∅-'):
|
||||
"""
|
||||
Reformats a sound change for use in regular expressions.
|
||||
Replaces mentions of categories with their actual values
|
||||
and removes all zero characters.
|
||||
Returns the reformatted change.
|
||||
"""
|
||||
|
||||
# Prepare change for regex
|
||||
for k, v in {' ': '', '{': '(', '}': ')', ',': '|'}.items():
|
||||
change = change.replace(k, v)
|
||||
@ -89,15 +147,33 @@ def convert_change_to_regex(change, categories, zero_characters):
|
||||
|
||||
|
||||
def split_change(change):
|
||||
"""
|
||||
Splits a valid change into four components.
|
||||
|
||||
Syntax:
|
||||
original > change_to / before __ after
|
||||
╰────── Change ──────╯ ╰─ Environment ─╯
|
||||
|
||||
Returns a tuple of the four components:
|
||||
(original, change_to, before, after)
|
||||
"""
|
||||
|
||||
if '/' in change:
|
||||
change, environment = change.split('/')
|
||||
else:
|
||||
environment = '_'
|
||||
|
||||
# Collaplse multiple underscores
|
||||
# Collapse multiple underscores
|
||||
environment = re.sub('_+', '_', environment)
|
||||
|
||||
original, change_to = change.split('>')
|
||||
if '_' in environment:
|
||||
before, after = environment.split('_')
|
||||
else:
|
||||
before = after = ''
|
||||
|
||||
return (original, change_to, before, after)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
32
tests/test_is_valid_change.py
Normal file
32
tests/test_is_valid_change.py
Normal file
@ -0,0 +1,32 @@
|
||||
from soundchanger.change import is_valid_change
|
||||
import pytest
|
||||
|
||||
def test_valid_change():
|
||||
assert is_valid_change('a>b/c_d') == True
|
||||
assert is_valid_change('a>b/c__d') == True
|
||||
assert is_valid_change('a>b/c_') == True
|
||||
assert is_valid_change('a>b/_d') == True
|
||||
assert is_valid_change('a>b') == True
|
||||
assert is_valid_change('a>b/_') == True
|
||||
assert is_valid_change('a>b/') == True
|
||||
|
||||
def test_invalid_change():
|
||||
assert is_valid_change('>') == False
|
||||
assert is_valid_change('>b') == False
|
||||
assert is_valid_change('a>b/a') == False
|
||||
assert is_valid_change('>/_') == False
|
||||
assert is_valid_change('a>b//c_d') == False
|
||||
|
||||
def test_raises_value_error():
|
||||
with pytest.raises(ValueError):
|
||||
is_valid_change('>', ignore_errors=False)
|
||||
with pytest.raises(ValueError):
|
||||
is_valid_change('>', ignore_errors=False)
|
||||
with pytest.raises(ValueError):
|
||||
is_valid_change('>b', ignore_errors=False)
|
||||
with pytest.raises(ValueError):
|
||||
is_valid_change('a>b/a', ignore_errors=False)
|
||||
with pytest.raises(ValueError):
|
||||
is_valid_change('>/_', ignore_errors=False)
|
||||
with pytest.raises(ValueError):
|
||||
is_valid_change('a>b//c_d', ignore_errors=False)
|
||||
21
tests/test_reformat_change_to_regex.py
Normal file
21
tests/test_reformat_change_to_regex.py
Normal file
@ -0,0 +1,21 @@
|
||||
from soundchanger.change import reformat_change_to_regex
|
||||
|
||||
|
||||
def test_replace_spaces():
|
||||
assert reformat_change_to_regex(' a > b / c _ d ') == 'a>b/c_d'
|
||||
|
||||
def test_replace_brackets_and_commas():
|
||||
assert reformat_change_to_regex('a>b/{#,a}_') == 'a>b/(#|a)_'
|
||||
|
||||
def test_replace_categories():
|
||||
assert reformat_change_to_regex('a>b/V_',
|
||||
categories={'V': 'aiu'}
|
||||
) == 'a>b/(a|i|u)_'
|
||||
|
||||
def test_replace_categories_in_brackets():
|
||||
assert reformat_change_to_regex('a>b/{#,V}_',
|
||||
categories={'V': 'aiu'}
|
||||
) == 'a>b/(#|(a|i|u))_'
|
||||
|
||||
def test_replace_zero_characters():
|
||||
assert reformat_change_to_regex('a>∅') == 'a>'
|
||||
34
tests/test_split_change.py
Normal file
34
tests/test_split_change.py
Normal file
@ -0,0 +1,34 @@
|
||||
from soundchanger.change import split_change
|
||||
import pytest
|
||||
|
||||
|
||||
def test_simple_change():
|
||||
assert split_change('a>b') == ('a', 'b', '', '')
|
||||
|
||||
def test_change_with_slash():
|
||||
assert split_change('a>b/') == ('a', 'b', '', '')
|
||||
|
||||
def test_change_with_environment():
|
||||
assert split_change('a>b/c_d') == ('a', 'b', 'c', 'd')
|
||||
|
||||
def test_change_with_environment_double_underscore():
|
||||
assert split_change('a>b/c__d') == ('a', 'b', 'c', 'd')
|
||||
|
||||
def test_change_with_environment_before():
|
||||
assert split_change('a>b/#_') == ('a', 'b', '#', '')
|
||||
|
||||
def test_change_with_environment_after():
|
||||
assert split_change('a>b/_#') == ('a', 'b', '', '#')
|
||||
|
||||
def test_change_with_empty_environment():
|
||||
assert split_change('a>b/_') == ('a', 'b', '', '')
|
||||
|
||||
def test_change_to_nothing():
|
||||
assert split_change('a>') == ('a', '', '', '')
|
||||
|
||||
def test_change_to_zero():
|
||||
assert split_change('a>-') == ('a', '-', '', '')
|
||||
|
||||
def test_invalid_input():
|
||||
with pytest.raises(ValueError):
|
||||
assert split_change('')
|
||||
Loading…
x
Reference in New Issue
Block a user