Add tests
This commit is contained in:
parent
3018d16cfe
commit
2515f8b343
@ -1,13 +1,60 @@
|
|||||||
import re
|
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(
|
def apply(
|
||||||
changes,
|
changes,
|
||||||
strings,
|
strings,
|
||||||
apply=True,
|
|
||||||
categories={},
|
categories={},
|
||||||
ignore_errors=True,
|
ignore_errors=True,
|
||||||
zero_characters=['∅']):
|
zero_characters='∅-'):
|
||||||
"""
|
"""
|
||||||
Applies a sound change or a list of sound changes to a string or
|
Applies a sound change or a list of sound changes to a string or
|
||||||
a list of given strings.
|
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.
|
If the input value is of type str, the output will also be of type str.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- apply (default: True)
|
|
||||||
Whether or not the changes should be applied.
|
|
||||||
- categories (default: {})
|
- categories (default: {})
|
||||||
Which categories will be detected.
|
Which categories will be detected.
|
||||||
For vowels it would be {'V'='aeiou'} or {'V'=['a', 'e', 'i', 'o', 'u']})
|
For vowels it would be {'V'='aeiou'} or {'V'=['a', 'e', 'i', 'o', 'u']})
|
||||||
|
|
||||||
- ignore_errors (default: True)
|
- ignore_errors (default: True)
|
||||||
If this option is set to `True`, any erroneous sound change will be skipped.
|
If this option is set to `True`, any erroneous sound change will be skipped.
|
||||||
If set to `False`, a ValueError will be raised.
|
If set to `False`, a ValueError will be raised instead.
|
||||||
- zero_characters (default: ['∅'])
|
|
||||||
|
- zero_characters (default: '∅-')
|
||||||
These characters will be removed in the changed words.
|
These characters will be removed in the changed words.
|
||||||
For example, `apply('h>∅', 'aha')` will return 'aa', not 'a∅a'.
|
For example, `apply('h>∅', 'aha')` will return 'aa', not 'a∅a'.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not apply:
|
|
||||||
return strings
|
|
||||||
|
|
||||||
if isinstance(changes, str):
|
if isinstance(changes, str):
|
||||||
changes = [changes]
|
changes = [changes]
|
||||||
|
|
||||||
@ -42,10 +86,10 @@ def apply(
|
|||||||
strings = strings.copy()
|
strings = strings.copy()
|
||||||
|
|
||||||
for change in changes:
|
for change in changes:
|
||||||
if validate_change(change, ignore_errors=ignore_errors) == False:
|
if is_valid_change(change, ignore_errors=ignore_errors) == False:
|
||||||
continue
|
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)
|
original, change_to, before, after = split_change(change)
|
||||||
|
|
||||||
pattern = f"({before})({original})({after})"
|
pattern = f"({before})({original})({after})"
|
||||||
@ -60,8 +104,15 @@ def apply(
|
|||||||
return strings
|
return strings
|
||||||
|
|
||||||
|
|
||||||
def validate_change(change, ignore_errors):
|
def is_valid_change(change, ignore_errors=True):
|
||||||
valid = re.search(r'^[^>_/]+?>[^>_/]*?(:?/[^>_/]*?_+[^>_/]*)?$', change)
|
"""
|
||||||
|
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:
|
if ignore_errors:
|
||||||
return valid != None
|
return valid != None
|
||||||
if not valid:
|
if not valid:
|
||||||
@ -69,7 +120,14 @@ def validate_change(change, ignore_errors):
|
|||||||
return True
|
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
|
# Prepare change for regex
|
||||||
for k, v in {' ': '', '{': '(', '}': ')', ',': '|'}.items():
|
for k, v in {' ': '', '{': '(', '}': ')', ',': '|'}.items():
|
||||||
change = change.replace(k, v)
|
change = change.replace(k, v)
|
||||||
@ -89,15 +147,33 @@ def convert_change_to_regex(change, categories, zero_characters):
|
|||||||
|
|
||||||
|
|
||||||
def split_change(change):
|
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:
|
if '/' in change:
|
||||||
change, environment = change.split('/')
|
change, environment = change.split('/')
|
||||||
else:
|
else:
|
||||||
environment = '_'
|
environment = '_'
|
||||||
|
|
||||||
# Collaplse multiple underscores
|
# Collapse multiple underscores
|
||||||
environment = re.sub('_+', '_', environment)
|
environment = re.sub('_+', '_', environment)
|
||||||
|
|
||||||
original, change_to = change.split('>')
|
original, change_to = change.split('>')
|
||||||
|
if '_' in environment:
|
||||||
before, after = environment.split('_')
|
before, after = environment.split('_')
|
||||||
|
else:
|
||||||
|
before = after = ''
|
||||||
|
|
||||||
return (original, change_to, 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