diff --git a/src/calc/constants.py b/src/calc/constants.py new file mode 100644 index 0000000..3e45c2f --- /dev/null +++ b/src/calc/constants.py @@ -0,0 +1,57 @@ +"""! +@file constants.py +@brief Constants used by the console calculator. +""" + +## Operation symbols, in the order they are offered to the user. +SYMBOL_ADD: str = "+" +SYMBOL_SUBTRACT: str = "-" +SYMBOL_MULTIPLY: str = "*" +SYMBOL_DIVIDE: str = "/" + +## Answers accepted after a calculation. +ANSWER_CONTINUE: str = "y" +ANSWER_NEW: str = "n" +ANSWER_QUIT: str = "q" + +## Prompts and messages shown to the user. +PROMPT_FIRST_NUMBER: str = "What's the first number?: " +PROMPT_NEXT_NUMBER: str = "What's the next number?: " +PROMPT_OPERATION: str = "Pick an operation: " +PROMPT_AGAIN: str = ( + f"Type '{ANSWER_CONTINUE}' to continue calculating with {{result}}, " + f"'{ANSWER_NEW}' to start a new calculation, " + f"or '{ANSWER_QUIT}' to quit: " +) +MESSAGE_INVALID_NUMBER: str = "That is not a number, please try again." +MESSAGE_INVALID_OPERATION: str = "That is not a valid operation, please try again." +MESSAGE_INVALID_ANSWER: str = "Please answer with one of the listed letters." +MESSAGE_DIVIDE_BY_ZERO: str = "You cannot divide by zero." +MESSAGE_GOODBYE: str = "Goodbye!" + +## ASCII title shown when the program starts. +ASCII_TITLE: str = r""" + ____ _ _ _ + / ___|__ _ | | ___ _ _| | __ _| |_ ___ _ __ +| | / _` | | |/ __| | | | |/ _` | __/ _ \| '__| +| |__| (_| | | | (__| |_| | | (_| | || (_) | | + \____\__,_| |_|\___|\__,_|_|\__,_|\__\___/|_| +""" + +## ASCII calculator shown under the title. +ASCII_CALCULATOR: str = r""" + _____________________ +| _________________ | +| | 0 | | +| |_________________| | +| ___ ___ ___ ___ | +| | 7 | 8 | 9 | | + | | +| |___|___|___| |___| | +| | 4 | 5 | 6 | | - | | +| |___|___|___| |___| | +| | 1 | 2 | 3 | | x | | +| |___|___|___| |___| | +| | . | 0 | = | | / | | +| |___|___|___| |___| | +|_____________________| +""" diff --git a/src/calc/operations.py b/src/calc/operations.py new file mode 100644 index 0000000..b062846 --- /dev/null +++ b/src/calc/operations.py @@ -0,0 +1,63 @@ +"""! +@file operations.py +@brief Arithmetic operations and the dictionary that maps symbols to them. +""" + +from collections.abc import Callable + +from calc.constants import ( + SYMBOL_ADD, + SYMBOL_DIVIDE, + SYMBOL_MULTIPLY, + SYMBOL_SUBTRACT, +) + + +def add(n1: float, n2: float) -> float: + """! + @brief Add two numbers. + @param n1 The first number. + @param n2 The second number. + @return The sum of @p n1 and @p n2. + """ + return n1 + n2 + + +def subtract(n1: float, n2: float) -> float: + """! + @brief Subtract the second number from the first. + @param n1 The first number. + @param n2 The second number. + @return The difference @p n1 minus @p n2. + """ + return n1 - n2 + + +def multiply(n1: float, n2: float) -> float: + """! + @brief Multiply two numbers. + @param n1 The first number. + @param n2 The second number. + @return The product of @p n1 and @p n2. + """ + return n1 * n2 + + +def divide(n1: float, n2: float) -> float: + """! + @brief Divide the first number by the second. + @param n1 The dividend. + @param n2 The divisor. + @return The quotient @p n1 divided by @p n2. + @throws ZeroDivisionError When @p n2 is zero. + """ + return n1 / n2 + + +## Maps an operation symbol to its function. The functions are stored, not called. +OPERATIONS: dict[str, Callable[[float, float], float]] = { + SYMBOL_ADD: add, + SYMBOL_SUBTRACT: subtract, + SYMBOL_MULTIPLY: multiply, + SYMBOL_DIVIDE: divide, +} diff --git a/tests/test_operations.py b/tests/test_operations.py new file mode 100644 index 0000000..b4cd59b --- /dev/null +++ b/tests/test_operations.py @@ -0,0 +1,33 @@ +"""! +@file test_operations.py +@brief Tests for the arithmetic operations. +""" + +import pytest + +from calc.operations import OPERATIONS, add, divide, multiply, subtract + + +def test_add_returns_sum() -> None: + assert add(2, 3) == 5 + + +def test_subtract_returns_difference() -> None: + assert subtract(2, 3) == -1 + + +def test_multiply_returns_product() -> None: + assert multiply(4, 2.5) == 10 + + +def test_divide_returns_quotient() -> None: + assert divide(9, 3) == 3 + + +def test_divide_when_divisor_is_zero() -> None: + with pytest.raises(ZeroDivisionError): + divide(1, 0) + + +def test_operations_maps_symbols_to_functions() -> None: + assert OPERATIONS == {"+": add, "-": subtract, "*": multiply, "/": divide}