Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev #7

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
__pycache__
.idea
.venv
152 changes: 152 additions & 0 deletions excel2py/excel_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -202,6 +203,157 @@ 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 option.join([value1, value2])
return "".join(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]


# 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 option.join([text[:start_num-1], 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))
144 changes: 144 additions & 0 deletions tests/test_excel_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,5 +169,149 @@ 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)


# 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()