From 565f00de389198145cbc94992ecb33b60a1191bb Mon Sep 17 00:00:00 2001 From: Jordan Welsman Date: Mon, 10 Apr 2023 15:28:01 -0700 Subject: [PATCH 01/13] build: Incremented version number. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 94281d7..29a4feb 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ # Arguments git_name = "jutils" pypi_name = "jutl" -version = "0.5.2" # update __init__.py +version = "0.6.0" # update __init__.py python_version = ">=3.10" # Long description from README.md From 79690ddf23a977878bd12a68dcd211d0b83b997f Mon Sep 17 00:00:00 2001 From: Jordan Welsman Date: Mon, 10 Apr 2023 15:28:24 -0700 Subject: [PATCH 02/13] build: Incremented version number. --- jutl/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jutl/__init__.py b/jutl/__init__.py index 0104410..5da551a 100644 --- a/jutl/__init__.py +++ b/jutl/__init__.py @@ -1,5 +1,5 @@ # Dunder attributes -__version__ = "0.5.2" +__version__ = "0.6.0" __author__ = "Jordan Welsman" __license__ = "MIT" __copyright__ = "Copyright 2023 Jordan Welsman" From 7c315951b01100be6a2884d213763d1e4e47ae0d Mon Sep 17 00:00:00 2001 From: Jordan Welsman Date: Mon, 10 Apr 2023 15:29:15 -0700 Subject: [PATCH 03/13] feat: Added Caesar Cipher component. --- jutl/cryptography/__init__.py | 8 ++++++++ jutl/cryptography/caesarcipher.py | 0 2 files changed, 8 insertions(+) create mode 100644 jutl/cryptography/caesarcipher.py diff --git a/jutl/cryptography/__init__.py b/jutl/cryptography/__init__.py index e69de29..0b378cb 100644 --- a/jutl/cryptography/__init__.py +++ b/jutl/cryptography/__init__.py @@ -0,0 +1,8 @@ +# Import utils.py so module +# functions are usable at +# 'from jutl import cryptography' level. +from .caesarcipher import * + +# Only show functions specified in +# submodule files to the outside world. +__all__ = caesarcipher.__all__ \ No newline at end of file diff --git a/jutl/cryptography/caesarcipher.py b/jutl/cryptography/caesarcipher.py new file mode 100644 index 0000000..e69de29 From fbaa1992465bf334e9c2b5c54eea59bad917a519 Mon Sep 17 00:00:00 2001 From: Jordan Welsman Date: Mon, 10 Apr 2023 15:36:52 -0700 Subject: [PATCH 04/13] feat: Added Substitution Cipher component. --- jutl/cryptography/substitutioncipher.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 jutl/cryptography/substitutioncipher.py diff --git a/jutl/cryptography/substitutioncipher.py b/jutl/cryptography/substitutioncipher.py new file mode 100644 index 0000000..dd2de06 --- /dev/null +++ b/jutl/cryptography/substitutioncipher.py @@ -0,0 +1 @@ +# Module imports \ No newline at end of file From f1cca9a7d22c8627ccd04f48da80e3f4382b41ff Mon Sep 17 00:00:00 2001 From: Jordan Welsman Date: Mon, 10 Apr 2023 15:37:32 -0700 Subject: [PATCH 05/13] feat: Added ROT13 Cipher component. --- jutl/cryptography/rot13cipher.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 jutl/cryptography/rot13cipher.py diff --git a/jutl/cryptography/rot13cipher.py b/jutl/cryptography/rot13cipher.py new file mode 100644 index 0000000..e69de29 From 6391aff7e36e2f1bd10a8df2b5f55285b0f8c752 Mon Sep 17 00:00:00 2001 From: Jordan Welsman Date: Wed, 10 May 2023 08:14:01 -0500 Subject: [PATCH 06/13] feat: Added init file. --- jutl/cryptography/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jutl/cryptography/__init__.py b/jutl/cryptography/__init__.py index 0b378cb..d875201 100644 --- a/jutl/cryptography/__init__.py +++ b/jutl/cryptography/__init__.py @@ -2,7 +2,9 @@ # functions are usable at # 'from jutl import cryptography' level. from .caesarcipher import * +from .rot13cipher import * +from .substitutioncipher import * # Only show functions specified in # submodule files to the outside world. -__all__ = caesarcipher.__all__ \ No newline at end of file +__all__ = caesarcipher.__all__, rot13cipher.__all__, substitutioncipher.__all__ \ No newline at end of file From d338f7db1ec92746c5ed4c4ffcc113b395e2d1ed Mon Sep 17 00:00:00 2001 From: Jordan Welsman Date: Wed, 10 May 2023 08:14:27 -0500 Subject: [PATCH 07/13] feat: Started work on substitution cipher. --- jutl/cryptography/substitutioncipher.py | 64 ++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/jutl/cryptography/substitutioncipher.py b/jutl/cryptography/substitutioncipher.py index dd2de06..1d3a03d 100644 --- a/jutl/cryptography/substitutioncipher.py +++ b/jutl/cryptography/substitutioncipher.py @@ -1 +1,63 @@ -# Module imports \ No newline at end of file +# Module imports + +# External class visibility +__all__ = ['SubstitutionCipher'] + + +class SubstitutionCipher(): + """ + Class which acts as the + base Cipher implementation. + """ + def __init__(self, rotation: int = 0): + "Initialization method." + self.rotation = self._check_rotation(rotation) + + def __repr__(self) -> str: + """ + Tells the interpreter how + to represent this class. + """ + if self.rotation != 0: + print("s") + + + def _check_rotation(self, rotation: int) -> int: + """ + Checks if rotation is valid. + """ + allowed_range = range(0, 27) + + if rotation in allowed_range: + return rotation + else: + return 0 + + + def set_rotation(self, rotation: int = 0): + """ + Sets the rotation + after initialization. + """ + self.rotation = self._check_rotation(rotation) + + + def _encrypt(self, message: str): + """ + Encrypts the passed message + using the defined rotation. + """ + __alpha_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + __alpha_lower = "abcdefghijklmnopqrstuvwxyz" + encrypted_message: str = "" + + for letter in message: + if letter in __alpha_upper: + encrypted_message += __alpha_upper[(__alpha_upper.find(letter) + self.rotation) % 26] + elif letter in __alpha_lower: + encrypted_message += __alpha_lower[(__alpha_lower.find(letter) + self.rotation) % 26] + else: + encrypted_message += letter + + return encrypted_message + From 5541ffcf1cebb5aa81c0d7cfce0d987866030419 Mon Sep 17 00:00:00 2001 From: Jordan Welsman Date: Wed, 10 May 2023 08:14:41 -0500 Subject: [PATCH 08/13] feat: Started work on rot13 cipher. --- jutl/cryptography/rot13cipher.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jutl/cryptography/rot13cipher.py b/jutl/cryptography/rot13cipher.py index e69de29..5c01c18 100644 --- a/jutl/cryptography/rot13cipher.py +++ b/jutl/cryptography/rot13cipher.py @@ -0,0 +1,4 @@ +# Module imports + +# External class visibility +__all__ = [] \ No newline at end of file From 55e86abe5f3e6bbf67b2616d99d25ce737bfb601 Mon Sep 17 00:00:00 2001 From: Jordan Welsman Date: Wed, 10 May 2023 08:14:50 -0500 Subject: [PATCH 09/13] feat: Started work on caesar cipher. --- jutl/cryptography/caesarcipher.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jutl/cryptography/caesarcipher.py b/jutl/cryptography/caesarcipher.py index e69de29..5c01c18 100644 --- a/jutl/cryptography/caesarcipher.py +++ b/jutl/cryptography/caesarcipher.py @@ -0,0 +1,4 @@ +# Module imports + +# External class visibility +__all__ = [] \ No newline at end of file From 6ec1c96ebe100a738c24756d056b571c901df593 Mon Sep 17 00:00:00 2001 From: Jordan Welsman Date: Thu, 11 May 2023 14:22:13 -0500 Subject: [PATCH 10/13] feat: Implemented loop reporter class. --- jutl/timers/__init__.py | 3 +- jutl/timers/reporter.py | 196 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 jutl/timers/reporter.py diff --git a/jutl/timers/__init__.py b/jutl/timers/__init__.py index 86b18f2..74f85f3 100644 --- a/jutl/timers/__init__.py +++ b/jutl/timers/__init__.py @@ -1,9 +1,10 @@ # Import submodule files so # classes and functions are usable at # 'from jutl.timers import _' level. +from .reporter import * from .stopwatch import * from .timer import * # Only show functions specified in # submodule files to the outside world. -__all__ = stopwatch.__all__, timer.__all__ \ No newline at end of file +__all__ = reporter.__all__, stopwatch.__all__, timer.__all__ \ No newline at end of file diff --git a/jutl/timers/reporter.py b/jutl/timers/reporter.py new file mode 100644 index 0000000..6efac4d --- /dev/null +++ b/jutl/timers/reporter.py @@ -0,0 +1,196 @@ +# Module imports +from jutl.formatting import apply +from time import time + +# External class visibility +__all__ = ['Reporter'] + + +class Reporter(): + """ + Class which acts as a timer to + record loop times for comparison. + """ + def __init__(self, name: str = None, declare_times: bool = False): + "Initialization method." + self.name: str = name + self._start_time: float + self._stop_time: float + self.total_time: float = None + self._loops: list[float] = [] + self.loop_times: list[float] = [] + self._declare_times = declare_times + + def __repr__(self) -> str: + """ + Tells the interpreter how + to represent this class. + """ + if self.name is None: + if self.total_time is None: + return "Reporter()" + else: + return f"Reporter({round(self.total_time, 2)}s)" + else: + if self.total_time is None: + return f"Reporter({self.name})" + else: + return f"Reporter({self.name}, {round(self.total_time, 2)}s)" + + def __call__(self, color: str = None): + """ + Tells the interpreter what to + do when an object of this + class is called directly. + """ + if self.loop_times: + for n, time in enumerate(self.loop_times): + if color: + print(apply(text=f"Loop {n+1}: {round(time, 2)}s", text_color=color)) + else: + print(f"Loop {n+1}: {round(time, 2)}s") + else: + print("There are no loop times.") + + def __len__(self) -> int: + """ + Tells the interpreter what to + consider this class' length. + """ + return len(self._loops) + + def __iter__(self) -> iter: + """ + Tells the interpreter what to + iterate over thwn iterator methods + are called on this class. + """ + return iter(self.loop_times) + + def __eq__(self, other) -> bool: + """ + Tells the interpreter how this class + handles equal operators. + """ + return self.total_time == other.total_time + + def __ne__(self, other) -> bool: + """ + Tells the interpreter how this class + handles not equal operators. + """ + return self.total_time != other.total_time + + def __gt__(self, other) -> bool: + """ + Tells the interpreter how this class + handles greater than operators. + """ + return self.total_time > other.total_time + + def __ge__(self, other) -> bool: + """ + Tells the interpreter how this class + handles greater or equal operators. + """ + return self.total_time >= other.total_time + + def __lt__(self, other) -> bool: + """ + Tells the interpreter how this class + handles less than operators. + """ + return self.total_time < other.total_time + + def __le__(self, other) -> bool: + """ + Tells the interpreter how this class + handles less than or equal operators. + """ + return self.total_time <= other.total_time + + def __add__(self, other) -> float: + """ + Tells the interpreter how to sum these objects. + """ + return self.total_time + other.total_time + + def __sub__(self, other) -> float: + """ + Tells the interpreter how to subtract these objects. + """ + return self.total_time - other.total_time + + def __mul__(self, multiplier) -> float: + """ + Tells the interpreter how to subtract these objects. + """ + return self.total_time * multiplier + + def __truediv__(self, other) -> float: + """ + Tells the interpreter how to subtract these objects. + """ + return self.total_time / other.total_time + + + def start(self, message: str = None, color : str = None): + """ + Starts the reporter and + optionall prints a message. + """ + self._start_time = time() + if message: + print(apply(text=message, text_color=color) if color else print(message)) + + + def loop(self, loop_time: float = None, message: str = None, color: str = None): + """ + Adds the current time to the loop + time list, records the time since + the start or time of the last recorded + loop, and optionally prints a message. + """ + if loop_time: + self._loops.append(loop_time) + else: + self._loops.append(time()) + if not self.loop_times: + self.loop_times.append(self._calculate_time(self._start_time, self._loops[-1])) + else: + self.loop_times.append(self._calculate_time(self._loops[-2], self._loops[-1])) + if message: + print(apply(text=message, text_color=color) if color else print(message)) + + def stop(self, message: str = None, color: str = None): + """ + Stops the reporter, calculates + the total time passed, and + optionally prints a message. + """ + self._stop_time = time() + self.total_time = self._calculate_time(self._start_time, self._stop_time) + self.loop(self._stop_time) # add final loop time and calculate loop difference + if message: + print(apply(text=message, text_color=color) if color else print(text=message)) + + + def _calculate_time(self, time1: float, time2: float) -> float: + """ + Returns the difference in time where t2>t1. + """ + return time2 - time1 + + + def reset(self, message: str = None, color: str = None): + """ + Resets all reporter attributes + and optionally prints a message. + """ + self._start_time = None + self._stop_time = None + self.total_time = None + self._loops.clear() + self.loop_times.clear() + if message: + print(apply(text=message, text_color=color) if color else print(text=message)) From 07ddd69fa7a72603546efb77587153f8365a6a52 Mon Sep 17 00:00:00 2001 From: Jordan Welsman Date: Thu, 11 May 2023 14:22:37 -0500 Subject: [PATCH 11/13] test: Implemented tets for loop reporter class. --- test/timers/test_reporter.py | 271 +++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 test/timers/test_reporter.py diff --git a/test/timers/test_reporter.py b/test/timers/test_reporter.py new file mode 100644 index 0000000..147018d --- /dev/null +++ b/test/timers/test_reporter.py @@ -0,0 +1,271 @@ +# jutils/test/timers/test_reporter.py +from jutl.timers import Reporter +from time import sleep + +test_name: str = "Test name" + +class TestInit(): + def test_def(self): + "Tests if an object can be created from the Reporter class." + reporter = Reporter() + assert isinstance(reporter, Reporter) + del(reporter) + + def test_name(self): + "Tests if naming a reporter works." + reporter = Reporter() + assert reporter.name is None + del(reporter) + + reporter = Reporter(test_name) + assert reporter.name == test_name + del(reporter) + + +class TestDunder(): + def test_repr(self): + "Tests what is output for representation." + reporter = Reporter() + assert repr(reporter) == f"Reporter()" + del(reporter) + + reporter = Reporter() + reporter.start() + reporter.stop() + assert repr(reporter) == f"Reporter({round(reporter.total_time, 2)}s)" + del(reporter) + + reporter = Reporter(test_name) + assert repr(reporter) == f"Reporter({test_name})" + del(reporter) + + reporter = Reporter(test_name) + reporter.start() + reporter.stop() + assert repr(reporter) == f"Reporter({test_name}, {round(reporter.total_time, 2)}s)" + del(reporter) + + def test_len(self): + "Tests what is output for object length." + reporter = Reporter() + reporter.start() + reporter.stop() + assert len(reporter) == 1 + del(reporter) + + reporter = Reporter() + reporter.start() + reporter.loop() + reporter.stop() + assert len(reporter) == 2 + del(reporter) + + reporter = Reporter() + reporter.start() + reporter.loop() + reporter.loop() + reporter.stop() + assert len(reporter) == 3 + del(reporter) + + def test_iter(self): + "Tests object's iteration reporting." + reporter = Reporter() + reporter.start() + reporter.stop() + reporter_iter = iter(reporter) + assert next(reporter_iter) == reporter.total_time + del(reporter) + del(reporter_iter) + + reporter = Reporter() + reporter.start() + reporter.loop() + reporter.loop() + reporter.loop() + reporter.stop() + reporter_iter = iter(reporter) + assert next(reporter_iter) == reporter.loop_times[0] + assert next(reporter_iter) == reporter.loop_times[1] + assert next(reporter_iter) == reporter.loop_times[2] + assert next(reporter_iter) == reporter.loop_times[3] + del(reporter) + del(reporter_iter) + + def test_equal(self): + "Tests the overridden equal function." + reporter1 = Reporter() + reporter1.start() + reporter1.stop() + reporter2 = reporter1 + assert reporter1 == reporter2 + del(reporter1) + del(reporter2) + + def test_not_equal(self): + "Tests the overridden not equal function." + reporter1 = Reporter() + reporter2 = Reporter() + reporter1.start() + sleep(0.01) + reporter1.stop() + reporter2.start() + reporter2.stop() + assert reporter1 != reporter2 + del(reporter1) + del(reporter2) + + def test_greater_than(self): + "Tests the overridden greater than function." + reporter1 = Reporter() + reporter2 = Reporter() + reporter1.start() + sleep(0.01) + reporter1.stop() + reporter2.start() + reporter2.stop() + assert reporter1 > reporter2 + del(reporter1) + del(reporter2) + + def test_greater_equal(self): + "Tests the overridden greater or equal function." + reporter1 = Reporter() + reporter2 = Reporter() + reporter1.start() + sleep(0.01) + reporter1.stop() + reporter2.start() + reporter2.stop() + assert reporter1 > reporter2 + del(reporter1) + del(reporter2) + + reporter1 = Reporter() + reporter1.start() + reporter1.stop() + reporter2 = reporter1 + assert reporter1 == reporter2 + del(reporter1) + del(reporter2) + + def test_less_than(self): + "Tests the overridden less than function." + reporter1 = Reporter() + reporter2 = Reporter() + reporter1.start() + sleep(0.01) + reporter1.stop() + reporter2.start() + reporter2.stop() + assert reporter2 < reporter1 + del(reporter1) + del(reporter2) + + def test_less_equal(self): + "Tests the overridden less or equal function." + reporter1 = Reporter() + reporter2 = Reporter() + reporter1.start() + sleep(0.01) + reporter1.stop() + reporter2.start() + reporter2.stop() + assert reporter2 < reporter1 + del(reporter1) + del(reporter2) + + reporter1 = Reporter() + reporter1.start() + reporter1.stop() + reporter2 = reporter1 + assert reporter1 == reporter2 + del(reporter1) + del(reporter2) + + def test_add(self): + "Tests the sum operator." + reporter1 = Reporter() + reporter2 = Reporter() + reporter1.start() + sleep(0.01) + reporter1.stop() + reporter2.start() + reporter2.stop() + assert reporter1 + reporter2 == reporter1.total_time + reporter2.total_time + del(reporter1) + del(reporter2) + + def test_sub(self): + "Tests the subtract operator." + reporter1 = Reporter() + reporter2 = Reporter() + reporter1.start() + sleep(0.01) + reporter1.stop() + reporter2.start() + reporter2.stop() + assert reporter1 - reporter2 == reporter1.total_time - reporter2.total_time + del(reporter1) + del(reporter2) + + def test_mul(self): + "Tests the multiply operator." + reporter = Reporter() + reporter.start() + sleep(0.01) + reporter.stop() + assert reporter * 2 == reporter.total_time * 2 + del(reporter) + + def test_truediv(self): + "Tests the divide operator." + reporter1 = Reporter() + reporter2 = Reporter() + reporter1.start() + sleep(0.02) + reporter1.stop() + reporter2.start() + sleep(0.01) + reporter2.stop() + assert reporter1 / reporter2 == reporter1.total_time / reporter2.total_time + del(reporter1) + del(reporter2) + + +class TestRobustness(): + def test_equal_list_sizes(self): + """ + Checks recorded loop time and loop + difference lists are equally sized. + """ + reporter = Reporter() + assert len(reporter._loops) == len(reporter.loop_times) + del(reporter) + + reporter = Reporter() + reporter.start() + reporter.stop() + assert len(reporter._loops) == len(reporter.loop_times) + del(reporter) + + reporter = Reporter() + reporter.start() + reporter.loop() + reporter.loop() + reporter.loop() + reporter.stop() + assert len(reporter._loops) == len(reporter.loop_times) + del(reporter) + + def test_stop_accuracy(self): + """ + Checks accuracy of + calculate_time() method. + """ + reporter = Reporter() + reporter.start() + sleep(0.1) + reporter.stop() + assert 0.100 <= reporter.total_time <= 0.110 # within 10ms tolerance + del(reporter) From 923dc716dc428ad11d53744da29aa497a9425692 Mon Sep 17 00:00:00 2001 From: Jordan Welsman Date: Sat, 27 May 2023 00:17:16 -0500 Subject: [PATCH 12/13] build: Incremented version number. --- jutl/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jutl/__init__.py b/jutl/__init__.py index 5da551a..5f87658 100644 --- a/jutl/__init__.py +++ b/jutl/__init__.py @@ -1,5 +1,5 @@ # Dunder attributes -__version__ = "0.6.0" +__version__ = "0.5.3" __author__ = "Jordan Welsman" __license__ = "MIT" __copyright__ = "Copyright 2023 Jordan Welsman" diff --git a/setup.py b/setup.py index 29a4feb..38166f5 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ # Arguments git_name = "jutils" pypi_name = "jutl" -version = "0.6.0" # update __init__.py +version = "0.5.3" # update __init__.py python_version = ">=3.10" # Long description from README.md From aa0d527086ba376c4dd7b6a860f6ee23e121c0de Mon Sep 17 00:00:00 2001 From: Jordan Welsman Date: Sat, 27 May 2023 00:17:49 -0500 Subject: [PATCH 13/13] feat: Timer can now print based on percision. --- jutl/timers/timer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jutl/timers/timer.py b/jutl/timers/timer.py index 963c930..6b21312 100644 --- a/jutl/timers/timer.py +++ b/jutl/timers/timer.py @@ -36,7 +36,7 @@ def __repr__(self) -> str: else: return f"Timer({self.name}, {round(self.total_time, 2)}s)" - def __call__(self, color: str = None): + def __call__(self, precision: int = 2, color: str = None): """ Tells the interpreter what to do when an object of this @@ -44,9 +44,9 @@ class is called directly. """ if self.total_time: if color: - print(apply(text=f"{self.name} total time: {round(self.total_time, 2)}s", text_color=color)) + print(apply(text=f"{self.name} total time: {round(self.total_time, precision)}s", text_color=color)) else: - print(f"{self.name} total time: {round(self.total_time, 2)}s") + print(f"{self.name} total time: {round(self.total_time, precision)}s") else: print("There is no recorded time.")