From bad56a7f07177e40f84709be8c6c266e3465073c Mon Sep 17 00:00:00 2001 From: Muhammad Sesay Date: Tue, 5 May 2020 21:35:01 -0700 Subject: [PATCH 1/4] add: Count, Counta, Concatenate, Median and Trim functions and tests Resolves: #4 --- excel2py/excel_functions.py | 50 +++++++++++++++++++++++++++ tests/test_excel_functions.py | 63 +++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/excel2py/excel_functions.py b/excel2py/excel_functions.py index 1efd481..bd2e32d 100644 --- a/excel2py/excel_functions.py +++ b/excel2py/excel_functions.py @@ -8,6 +8,7 @@ import numbers from collections import Iterable from functools import reduce +from statistics import median from excel2py.ex_datetime import ex_datetime, to_excel_number @@ -202,6 +203,55 @@ def VLOOKUP(value, table, column, range_lookup=True): return table[-1][column-1] return None + +# count function +def COUNT(*args): + """ + Counts all values that are numeric in a column, e.g. COUNT(A1:A2) + """ + return tuple(filter(lambda x: type(x) is int, *args)) + + +# median function +def MEDIAN(*args): # *args + """ + Returns the median of a given list of values, e.g. =MEDIAN(1,2,3,4,5) returns 3 + """ + try: + return median(*args) + except TypeError: + raise TypeError("median works on only strings values") + + +# trim function +def TRIM(val): + """ + Trim removes all white spaces from a given cell, e.g. =TRIM(A1) + """ + return " ".join(val.split()) + + +# concatenate function +def CONCATENATE(value1,value2,option): + """ + Concatenate joins two cells into a combined cell, e.g. =CONCATENATE(A2,"",B2) + :param value1: represents the first cell + :param value2: represents the second cell + :param option: represents any white space(s) to be included between the two values + """ + if option is ' ': + return value1 +option+ value2 + return value1 + value2 + + +# counta function +def COUNTA(*args): + """ + Counta counts all cells regardless of type but only skips empty cell, e.g. =COUNTA(value1, [value2], …) + """ + return [len([a for a in arg if a != '']) for arg in args] + + # # dt = ex_datetime(2018, 7, 5) # print(MIN(dt)) diff --git a/tests/test_excel_functions.py b/tests/test_excel_functions.py index 8994b16..261a174 100644 --- a/tests/test_excel_functions.py +++ b/tests/test_excel_functions.py @@ -169,5 +169,68 @@ def test_ok(self): self.assertEqual(ef.INT(val), expect) +# Count test +class TestCount(unittest.TestCase): + """ + Test that the count functions can counts all the list of integers + """ + def test_ok(self): + data = (1, 2, 3, 12, 15, 45, 'one', 25, 'two', ' ') + result = ef.COUNT(data) + self.assertTrue(result) + + +# Median test +class TestMedian(unittest.TestCase): + """ + Test to get the median of a list of values + Will raise an error if strings are passed + """ + def test_ok(self): + data = [1, 2, 3, 12, 15, 45] + result = ef.MEDIAN(data) + self.assertTrue(result) + + def test_error(self): + data = [1, 2, 3, 12, 15, 45, 'one', 25, 'two', ' '] + with self.assertRaises(TypeError, msg="Only "): ef.MEDIAN(data) + + +# Trim test +class TestTrim(unittest.TestCase): + """ + Test to remove all whitespace from a string + """ + def test_ok(self): + text = ' PYTHON UNITTEST TRIM ' + result = ef.TRIM(text) + self.assertTrue(result) + + +# Concatenate test +class TestConcatenate(unittest.TestCase): + """ + Test to join to cells together to form one + """ + def test_ok(self): + cell1 = 'one' + cell2 = 'two' + option = '' + result = ef.CONCATENATE(cell1, cell2, option) + self.assertTrue(result) + + +# Counta test +class TestCounta(unittest.TestCase): + """ + Test to counts all cells regardless of type but only skips empty cell + """ + def test_ok(self): + data = ['one', 1, 'two', '', 3, 'three', 25, ''] + result = ef.COUNTA(data) + self.assertTrue(result) + + + if __name__ == "__main__": unittest.main() From 4137bc17c27e1a07debe0ef71b81fcaedf0b0a48 Mon Sep 17 00:00:00 2001 From: Muhammad Sesay Date: Sat, 16 May 2020 18:19:33 -0700 Subject: [PATCH 2/4] add: MID, REPLACE, SEARCH, ABS and EXACT excel functions test: MID, REPLACE, SEARCH, ABS, and EXACT test_excel_functions Resolve #6 --- excel2py/excel_functions.py | 102 ++++++++++++++++++++++++++++++++++ tests/test_excel_functions.py | 81 +++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) diff --git a/excel2py/excel_functions.py b/excel2py/excel_functions.py index bd2e32d..9b69507 100644 --- a/excel2py/excel_functions.py +++ b/excel2py/excel_functions.py @@ -252,6 +252,108 @@ def COUNTA(*args): return [len([a for a in arg if a != '']) for arg in args] +# mid function +def MID(text, start_num, num_chars): + """ + The Excel MID function extracts a given number of characters from the middle of a supplied text string. + For example, =MID("apple",2,3) returns "ppl". =MID (text, start_num, num_chars) + + :param text: The text to extract from. + :param start_num: The location of the first character to extract. + :param num_chars: The number of characters to extract. + :return: The extracted text + """ + + if start_num and num_chars > 0: + return text[start_num-1:(start_num+num_chars)-1] + + # raise ValueError if param start_num value is not positive integer + if start_num == 0: + raise ValueError('start_num value must be positive integers eg: 1') + + # raise ValueError if param start_num value is not a positive integer + if num_chars == 0: + raise ValueError('num_chars value must be positive integers eg: 1') + + # raise ValueError if param start_num or num_chars are negative values + if start_num < 0 or num_chars < 0: + raise ValueError('start_num and num_chars values cannot be negative values') + + +# replace function +def REPLACE(text, start_num, num_chars, option): + """ + Replace, replaces text by position. + For example, =REPLACE("apple##",2,3,"*") returns "p*l" + + :param text: The text to extract from. + :param start_num: The location of the first character to extract. + :param num_chars: The number of characters to extract. + :param option: integer(s), string(s),char(s) or symbol(s) to be inserted into the position. + :return: The replaced text + """ + # raise TypeError if a non int param start_num value + if not isinstance(start_num, int): + raise TypeError('start_num should be of type int') + + # raise TypeError if a non int param num_chars value + if not isinstance(num_chars, int): + raise TypeError('num_chars should be of type int') + + # raise TypeError for negative param values + if start_num < 0 or num_chars < 0: + raise TypeError('start_num or num_chars should be of positive integers') + + # return the string in place + return text[:start_num-1] + option + text[(start_num+num_chars)-1:] + + +# search function +def SEARCH(find_text, within_text): + """ + Search for a word in a string and returns the position. + For example, =SEARCH("we love python", "love") -> output(4) + + :param find_text: The text to find. + :param within_text: The string of text. + :return: The numeric position of matching text. + """ + try: + return within_text.index(find_text) + 1 + except ValueError: + raise ValueError('No such charater available in string') + + +# abs function +def ABS(val): + """ + Will return the absolute value of the param passed. + For example, =ABS(-13.40) -> output(13.40) + + :param val: The value to check + :return: The absolute value of a number + """ + if not isinstance(val, str): + return abs(val) + raise TypeError('Value must be of type str') + + +# exact function +def EXACT(val1, val2): + """ + Check for equality between two text strings in a case-sensitive manner. + For example, =EXACT("Test","test") -> output(FALSE) + + :param val1: First value + :param val2: Second Value + :return: True/False + """ + if val1 == val2: + return True + return False + + + # # dt = ex_datetime(2018, 7, 5) # print(MIN(dt)) diff --git a/tests/test_excel_functions.py b/tests/test_excel_functions.py index 261a174..236fb78 100644 --- a/tests/test_excel_functions.py +++ b/tests/test_excel_functions.py @@ -231,6 +231,87 @@ def test_ok(self): self.assertTrue(result) +# MID test +class TestMid(unittest.TestCase): + """ + Test to strip out some part of a text based on the start_num and num_chars + """ + def test_ok(self): + result = ef.MID('apple', 2, 3) + self.assertTrue(result) + + # @unittest.skip("demonstrating skipping") + def test_value_error(self): + with self.assertRaises(ValueError): + ef.MID('apple', 0, -1) + + +# REPLACE test +class TestReplace(unittest.TestCase): + """ + Replace, replaces text by position. + For example, =REPLACE("apple##",2,3,"*") returns "p*l" + + :param text: The text to extract from. + :param start_num: The location of the first character to extract. + :param num_chars: The number of characters to extract. + :param option: ie- integer(s), string(s), char(s) or symbol(s) to be inserted into the position + """ + def test_ok(self): + """ + Test that the text was replaced + """ + self.assertTrue(ef.REPLACE('apple##', 6, 2, '$2')) + + def test_not_int(self): + """ + Test that start_num and num_chars are type int + """ + with self.assertRaises(TypeError): + ef.REPLACE('apple', '0', '1', '') + + +# SEARCH test +class TestSearch(unittest.TestCase): + """ + Test to get the index position of a matching text + """ + def test_ok(self): + self.assertTrue(ef.SEARCH("python", "we love python")) + + def test_value_error(self): + with self.assertRaises(ValueError): + ef.SEARCH("code", "we love python") + + +# ABS test +class TestAbs(unittest.TestCase): + def test_true(self): + """ + Test that we get an absolute number + """ + self.assertTrue(ef.ABS(-13.40)) + + def test_type_error(self): + """ + Test that string param should throws an error + """ + with self.assertRaises(TypeError) as context: + ef.ABS('-13.40') + self.assertEqual( + 'Value must be of type str', str(context.exception)) + + +# EXACT test +class TestExact(unittest.TestCase): + def test_equality(self): + """ + Test that both values are True/False + """ + self.assertTrue(ef.EXACT(123,123)) + self.assertFalse(ef.EXACT("Code",12345)) + + if __name__ == "__main__": unittest.main() From d7f40015ae6c3557d82e810d50c2bcf56bd34b36 Mon Sep 17 00:00:00 2001 From: Muhammad Sesay Date: Sun, 31 May 2020 00:19:21 +0000 Subject: [PATCH 3/4] refactor: changed the concatenation from the plus operator to the join method --- excel2py/excel_functions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/excel2py/excel_functions.py b/excel2py/excel_functions.py index 9b69507..740f00d 100644 --- a/excel2py/excel_functions.py +++ b/excel2py/excel_functions.py @@ -240,8 +240,8 @@ def CONCATENATE(value1,value2,option): :param option: represents any white space(s) to be included between the two values """ if option is ' ': - return value1 +option+ value2 - return value1 + value2 + return option.join([value1, value2]) + return "".join(value1, value2) # counta function @@ -305,7 +305,7 @@ def REPLACE(text, start_num, num_chars, option): raise TypeError('start_num or num_chars should be of positive integers') # return the string in place - return text[:start_num-1] + option + text[(start_num+num_chars)-1:] + return option.join([text[:start_num-1], text[(start_num+num_chars)-1:]]) # search function From 14e099ba419f322570f7475b124f666d6c0d7cff Mon Sep 17 00:00:00 2001 From: Michael Grazebrook Date: Sun, 5 Jul 2020 20:06:49 +0100 Subject: [PATCH 4/4] Ignore .venv --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 083d732..84ecaff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ __pycache__ .idea +.venv