Skip to content

Commit 1004282

Browse files
committed
main branch merged rel branch
2 parents 68e3b77 + be20b66 commit 1004282

File tree

5 files changed

+368
-2
lines changed

5 files changed

+368
-2
lines changed

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,58 @@ Loads the data from a text file to a string and returns the string.
235235

236236
Saves a string to a text file.
237237

238+
## `clamp_float`
239+
240+
- Full path: `lyc_pyutils.clamps.clamp_float`
241+
- Shortcut: `lyc_pyutils.clamp_float`
242+
243+
Clamps a number to a range and returns a float.
244+
245+
Python interactive shell demo use case:
246+
247+
```python
248+
>>> import lyc_pyutils
249+
>>> lyc_pyutils.clamp_float(0.5, 0.0, 1.0)
250+
0.5
251+
>>> lyc_pyutils.clamp_float(1.5, 0.0, 1.0)
252+
1.0
253+
>>> lyc_pyutils.clamp_float(-0.5, 0.0, 1.0)
254+
0.0
255+
>>> lyc_pyutils.clamp_float(0.5, 1.0, 0.0)
256+
0.5
257+
>>> lyc_pyutils.clamp_float(1.5, 1.0, 0.0)
258+
1.0
259+
>>> lyc_pyutils.clamp_float(-0.5, 1.0, 0.0)
260+
0.0
261+
>>>
262+
```
263+
264+
## `clamp_int`
265+
266+
- Full path: `lyc_pyutils.clamps.clamp_int`
267+
- Shortcut: `lyc_pyutils.clamp_int`
268+
269+
Clamps a number to a range and returns an integer.
270+
271+
Python interactive shell demo use case:
272+
273+
```python
274+
>>> import lyc_pyutils
275+
>>> lyc_pyutils.clamp_int(1, 0, 2)
276+
1
277+
>>> lyc_pyutils.clamp_int(3, 0, 2)
278+
2
279+
>>> lyc_pyutils.clamp_int(-1, 0, 2)
280+
0
281+
>>> lyc_pyutils.clamp_int(1, 2, 0)
282+
1
283+
>>> lyc_pyutils.clamp_int(3, 2, 0)
284+
2
285+
>>> lyc_pyutils.clamp_int(-1, 2, 0)
286+
0
287+
>>>
288+
```
289+
238290
# Miscellaneous
239291
## Developer's Notes :memo: And Warnings :warning:
240292
### Notes :memo:

lyc_pyutils/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from lyc_pyutils.libs import randbool
1313
from lyc_pyutils.libs import textrw
1414
from lyc_pyutils.libs import timedinput
15-
15+
from lyc_pyutils.libs import clamps
1616

1717
DotDict = dotdict.DotDict
1818

@@ -33,3 +33,6 @@
3333

3434
load_text = textrw.load_text
3535
save_text = textrw.save_text
36+
37+
clamp_float = clamps.clamp_float
38+
clamp_int = clamps.clamp_int

lyc_pyutils/libs/clamps.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""Clamp functions."""
2+
3+
# Copyright 2022 Yucheng Liu. GNU GPL3 license.
4+
# GNU GPL3 license copy: https://www.gnu.org/licenses/gpl-3.0.txt
5+
# First added by username: liu-yucheng
6+
# Last updated by username: liu-yucheng
7+
8+
9+
def clamp_float(inval, bound1, bound2):
10+
"""Clamps inval to the range bounded by bounds 1 and 2.
11+
12+
Performs comparisons in floats.
13+
14+
Args:
15+
inval: the input value
16+
bound1: bound 1
17+
bound2: bound 2
18+
19+
Returns:
20+
result: the result
21+
"""
22+
23+
# Part of LYC-PythonUtils
24+
# Copyright 2022 Yucheng Liu. GNU GPL3 license.
25+
# GNU GPL3 license copy: https://www.gnu.org/licenses/gpl-3.0.txt
26+
27+
inval = float(inval)
28+
bound1 = float(bound1)
29+
bound2 = float(bound2)
30+
31+
if bound1 < bound2:
32+
floor = bound1
33+
ceil = bound2
34+
else: # elif bound1 >= bound2:
35+
floor = bound2
36+
ceil = bound1
37+
# end if
38+
39+
result = inval
40+
41+
if result < floor:
42+
result = floor
43+
44+
if result > ceil:
45+
result = ceil
46+
47+
return result
48+
49+
50+
def clamp_int(inval, bound1, bound2):
51+
"""Clamps inval to the range bounded by bounds 1 and 2.
52+
53+
Performs comparisons in integers.
54+
55+
Args:
56+
inval: the input value
57+
bound1: bound 1
58+
bound2: bound 2
59+
60+
Returns:
61+
result: the result
62+
"""
63+
64+
# Part of LYC-PythonUtils
65+
# Copyright 2022 Yucheng Liu. GNU GPL3 license.
66+
# GNU GPL3 license copy: https://www.gnu.org/licenses/gpl-3.0.txt
67+
68+
inval = int(inval)
69+
bound1 = int(bound1)
70+
bound2 = int(bound2)
71+
72+
if bound1 < bound2:
73+
floor = bound1
74+
ceil = bound2
75+
else: # elif bound1 >= bound2:
76+
floor = bound2
77+
ceil = bound1
78+
# end if
79+
80+
result = inval
81+
82+
if result < floor:
83+
result = floor
84+
85+
if result > ceil:
86+
result = ceil
87+
88+
return result

lyc_pyutils/tests/test_clamps.py

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
"""Executable that tests the clamps utilities."""
2+
3+
# Copyright 2022 Yucheng Liu. GNU GPL3 license.
4+
# GNU GPL3 license copy: https://www.gnu.org/licenses/gpl-3.0.txt
5+
# First added by username: liu-yucheng
6+
# Last updated by username: liu-yucheng
7+
8+
import os
9+
import pathlib
10+
# import shutil
11+
import typing
12+
import unittest
13+
from os import path as ospath
14+
15+
import lyc_pyutils
16+
17+
# _copytree = shutil.copytree
18+
_IO = typing.IO
19+
_join = ospath.join
20+
_makedirs = os.makedirs
21+
_Path = pathlib.Path
22+
# _rmtree = shutil.rmtree
23+
_TestCase = unittest.TestCase
24+
25+
_lyc_clamp_float = lyc_pyutils.clamp_float
26+
_lyc_clamp_int = lyc_pyutils.clamp_int
27+
28+
_tests_path = _Path(__file__).parent
29+
_repo_path = _Path(_tests_path).parent.parent
30+
31+
# _default_configs_path = _join(_repo_path, "lyc_pyutils_default_configs")
32+
# _default_test_data_path = _join(_default_configs_path, "test_data")
33+
34+
_test_data_path = _join(_repo_path, ".lyc_pyutils_test_data")
35+
_log_loc = _join(_test_data_path, "log.txt")
36+
37+
38+
def _dquote_repr(instr):
39+
instr = str(instr)
40+
41+
result = repr(instr)[1: -1]
42+
result = f"\"{result}\""
43+
return result
44+
45+
46+
class _BaseCase(_TestCase):
47+
48+
def __init__(self, methodName):
49+
"""Initializes self with the given args."""
50+
super().__init__(methodName)
51+
52+
self._log: _IO = None
53+
54+
def setUp(self):
55+
"""Sets up before the tests."""
56+
super().setUp()
57+
58+
_makedirs(_test_data_path, exist_ok=True)
59+
self._log = open(_log_loc, "a+")
60+
61+
case_name = type(self).__name__
62+
info = f"- Test-case {case_name}"
63+
self._logln(info)
64+
65+
def tearDown(self):
66+
"""Tears down after the tests."""
67+
super().tearDown()
68+
69+
case_name = type(self).__name__
70+
info = f"- End of test-case {case_name}"
71+
self._logln(info)
72+
73+
self._log.flush()
74+
self._log.close()
75+
76+
def _logstr(self, str_to_log):
77+
str_to_log = str(str_to_log)
78+
79+
if self._log is not None:
80+
self._log.write(str_to_log)
81+
82+
def _logln(self, line):
83+
line = str(line)
84+
85+
line = line + "\n"
86+
self._logstr(line)
87+
88+
def _log_method_start(self, method_name):
89+
method_name = str(method_name)
90+
91+
info = f"-- Test-method {method_name}"
92+
self._logln(info)
93+
94+
def _log_method_end(self, method_name):
95+
method_name = str(method_name)
96+
97+
info = f"-- End of test-method {method_name}"
98+
self._logln(info)
99+
100+
101+
class TestClampFloat(_BaseCase):
102+
"""Tests for the clamp_float function."""
103+
104+
def _match_values(self, actual, expect, not_match_info, match_info):
105+
not_match_info = str(not_match_info)
106+
match_info = str(match_info)
107+
108+
fail_msg = not_match_info.format(actual, expect)
109+
success_msg = match_info.format(actual)
110+
self.assertTrue(actual == expect, fail_msg)
111+
self._logln(success_msg)
112+
113+
def test_norm(self):
114+
"""Tests the normal use case."""
115+
method_name = self.test_norm.__name__
116+
self._log_method_start(method_name)
117+
118+
not_match_info = str(
119+
f"Clamp result does not match\n"
120+
f"Actual: {{}}\n"
121+
f"Expected: {{}}\n"
122+
)
123+
124+
match_info = str(
125+
f"Clamp result matched\n"
126+
f"Actual and expected: {{}}\n"
127+
)
128+
129+
bound1 = float(0)
130+
bound2 = float(1)
131+
132+
inval = 0.5
133+
outval = _lyc_clamp_float(inval, bound1, bound2)
134+
self._match_values(outval, inval, not_match_info, match_info)
135+
136+
inval = 1.5
137+
outval = _lyc_clamp_float(inval, bound1, bound2)
138+
self._match_values(outval, bound2, not_match_info, match_info)
139+
140+
inval = -0.5
141+
outval = _lyc_clamp_float(inval, bound1, bound2)
142+
self._match_values(outval, bound1, not_match_info, match_info)
143+
144+
inval = 0.5
145+
outval = _lyc_clamp_float(inval, bound2, bound1)
146+
self._match_values(outval, inval, not_match_info, match_info)
147+
148+
inval = 1.5
149+
outval = _lyc_clamp_float(inval, bound2, bound1)
150+
self._match_values(outval, bound2, not_match_info, match_info)
151+
152+
inval = -0.5
153+
outval = _lyc_clamp_float(inval, bound2, bound1)
154+
self._match_values(outval, bound1, not_match_info, match_info)
155+
156+
self._log_method_end(method_name)
157+
158+
159+
class TestClampInt(_BaseCase):
160+
"""Tests for the clamp_int function."""
161+
162+
def _match_values(self, actual, expect, not_match_info, match_info):
163+
not_match_info = str(not_match_info)
164+
match_info = str(match_info)
165+
166+
fail_msg = not_match_info.format(actual, expect)
167+
success_msg = match_info.format(actual)
168+
self.assertTrue(actual == expect, fail_msg)
169+
self._logln(success_msg)
170+
171+
def test_norm(self):
172+
"""Tests the normal use case."""
173+
method_name = self.test_norm.__name__
174+
self._log_method_start(method_name)
175+
176+
not_match_info = str(
177+
f"Clamp result does not match\n"
178+
f"Actual: {{}}\n"
179+
f"Expected: {{}}\n"
180+
)
181+
182+
match_info = str(
183+
f"Clamp result matched\n"
184+
f"Actual and expected: {{}}\n"
185+
)
186+
187+
bound1 = 0
188+
bound2 = 2
189+
190+
inval = 1
191+
outval = _lyc_clamp_int(inval, bound1, bound2)
192+
self._match_values(outval, inval, not_match_info, match_info)
193+
194+
inval = 3
195+
outval = _lyc_clamp_int(inval, bound1, bound2)
196+
self._match_values(outval, bound2, not_match_info, match_info)
197+
198+
inval = -1
199+
outval = _lyc_clamp_int(inval, bound1, bound2)
200+
self._match_values(outval, bound1, not_match_info, match_info)
201+
202+
inval = 1
203+
outval = _lyc_clamp_int(inval, bound2, bound1)
204+
self._match_values(outval, inval, not_match_info, match_info)
205+
206+
inval = 3
207+
outval = _lyc_clamp_int(inval, bound2, bound1)
208+
self._match_values(outval, bound2, not_match_info, match_info)
209+
210+
inval = -1
211+
outval = _lyc_clamp_int(inval, bound2, bound1)
212+
self._match_values(outval, bound1, not_match_info, match_info)
213+
214+
self._log_method_end(method_name)
215+
216+
217+
def main():
218+
"""Runs this module as an executable."""
219+
unittest.main(verbosity=1)
220+
221+
222+
if __name__ == "__main__":
223+
main()

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
def main():
2222
_setup(
2323
name="lyc-pyutils",
24-
version="1.0.0",
24+
version="1.1.0",
2525
description="LYC's personal Python utilities.",
2626
author="Yucheng Liu",
2727
packages=_find_packages(),

0 commit comments

Comments
 (0)