pycfutils 2024.5.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycfutils/__init__.py +1 -0
- pycfutils/common.py +64 -0
- pycfutils/ctypes.py +119 -0
- pycfutils/gui/__init__.py +66 -0
- pycfutils/gui/constants.py +81 -0
- pycfutils/keyboard.py +94 -0
- pycfutils/tests/__init__.py +1 -0
- pycfutils/tests/test_common.py +57 -0
- pycfutils/tests/test_ctypes.py +34 -0
- pycfutils/tests/test_gui.py +14 -0
- pycfutils/tests/test_keyboard.py +14 -0
- pycfutils-2024.5.8.dist-info/LICENSE +21 -0
- pycfutils-2024.5.8.dist-info/METADATA +56 -0
- pycfutils-2024.5.8.dist-info/RECORD +16 -0
- pycfutils-2024.5.8.dist-info/WHEEL +5 -0
- pycfutils-2024.5.8.dist-info/top_level.txt +1 -0
pycfutils/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# @TODO - cfati: Dummy file - dir is picked up by setuptools.find_packages
|
pycfutils/common.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Sequence, Tuple, Union
|
|
6
|
+
|
|
7
|
+
__all__ = (
|
|
8
|
+
"int_format",
|
|
9
|
+
"timestamp_string",
|
|
10
|
+
"dimensions_2d",
|
|
11
|
+
"uniques",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def int_format(limit: int) -> str:
|
|
16
|
+
sgn = 1 if limit < 0 else 0
|
|
17
|
+
return f"{{:0{math.ceil(math.log10(max(abs(limit), 2))) + sgn:d}d}}"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def timestamp_string(
|
|
21
|
+
timestamp: Union[None, int, float, Sequence] = None,
|
|
22
|
+
human_readable: bool = False,
|
|
23
|
+
date_separator: str = "-",
|
|
24
|
+
time_separator: str = ":",
|
|
25
|
+
separator: str = " ",
|
|
26
|
+
) -> str:
|
|
27
|
+
tm = (
|
|
28
|
+
time.gmtime(timestamp)
|
|
29
|
+
if timestamp is None or isinstance(timestamp, (int, float))
|
|
30
|
+
else timestamp.timetuple()
|
|
31
|
+
if isinstance(timestamp, datetime)
|
|
32
|
+
else timestamp
|
|
33
|
+
)[:6]
|
|
34
|
+
if human_readable:
|
|
35
|
+
return (
|
|
36
|
+
f"{tm[0]:04d}{date_separator}"
|
|
37
|
+
f"{tm[1]:02d}{date_separator}"
|
|
38
|
+
f"{tm[2]:02d}{separator}"
|
|
39
|
+
f"{tm[3]:02d}{time_separator}"
|
|
40
|
+
f"{tm[4]:02d}{time_separator}{tm[5]:02d}"
|
|
41
|
+
)
|
|
42
|
+
return f"{tm[0]:04d}{tm[1]:02d}{tm[2]:02d}{tm[3]:02d}{tm[4]:02d}{tm[5]:02d}"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def dimensions_2d(n: int) -> Tuple:
|
|
46
|
+
if n <= 0:
|
|
47
|
+
return 0, 0
|
|
48
|
+
sq = round(math.sqrt(n))
|
|
49
|
+
return sq, math.ceil(n / sq)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def uniques(sequence: Sequence) -> Sequence:
|
|
53
|
+
ret = []
|
|
54
|
+
handled = set()
|
|
55
|
+
for e in sequence:
|
|
56
|
+
if e not in handled:
|
|
57
|
+
ret.append(e)
|
|
58
|
+
handled.add(e)
|
|
59
|
+
return ret if isinstance(sequence, list) else tuple(ret)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
print("This script is not meant to be run directly.\n")
|
|
64
|
+
sys.exit(-1)
|
pycfutils/ctypes.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import ctypes as cts
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
cts.c_bool = 1
|
|
6
|
+
try:
|
|
7
|
+
_CLS_CDATA = cts.c_int.__mro__[-2]
|
|
8
|
+
except Exception:
|
|
9
|
+
_CLS_CDATA = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
__all__ = ("to_string",)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _to_string(
|
|
16
|
+
obj: Any,
|
|
17
|
+
indent: int,
|
|
18
|
+
prefix: Optional[str],
|
|
19
|
+
suffix: Optional[str],
|
|
20
|
+
indent_text: str,
|
|
21
|
+
indent_first_line: bool,
|
|
22
|
+
) -> str:
|
|
23
|
+
cls = obj.__class__
|
|
24
|
+
ret = [prefix] if prefix is not None else []
|
|
25
|
+
ret.append(f"{(indent_text * indent) if indent_first_line else '':s}{obj}")
|
|
26
|
+
if _CLS_CDATA is None or not issubclass(cls, _CLS_CDATA):
|
|
27
|
+
if suffix is not None:
|
|
28
|
+
ret.append(suffix)
|
|
29
|
+
return "\n".join(ret)
|
|
30
|
+
if issubclass(cls, (cts.Structure, cts.Union)):
|
|
31
|
+
for name, _ in obj._fields_:
|
|
32
|
+
ret.append(
|
|
33
|
+
"{:s}{:s}: {:s}".format(
|
|
34
|
+
indent_text * (indent + 1),
|
|
35
|
+
name,
|
|
36
|
+
_to_string(
|
|
37
|
+
obj=getattr(obj, name),
|
|
38
|
+
indent=indent + 1,
|
|
39
|
+
prefix=None,
|
|
40
|
+
suffix=None,
|
|
41
|
+
indent_text=indent_text,
|
|
42
|
+
indent_first_line=False,
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
elif issubclass(cls, cts.Array):
|
|
47
|
+
for e in obj:
|
|
48
|
+
ret.append(
|
|
49
|
+
_to_string(
|
|
50
|
+
obj=e,
|
|
51
|
+
indent=indent + 1,
|
|
52
|
+
prefix=None,
|
|
53
|
+
suffix=None,
|
|
54
|
+
indent_text=indent_text,
|
|
55
|
+
indent_first_line=True,
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
elif issubclass(cls, cts._Pointer):
|
|
59
|
+
if obj:
|
|
60
|
+
ret.append(
|
|
61
|
+
_to_string(
|
|
62
|
+
obj=obj.contents,
|
|
63
|
+
indent=indent + 1,
|
|
64
|
+
prefix=prefix,
|
|
65
|
+
suffix=suffix,
|
|
66
|
+
indent_text=indent_text,
|
|
67
|
+
indent_first_line=True,
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
if suffix is not None:
|
|
71
|
+
ret.append(suffix)
|
|
72
|
+
return "\n".join(ret)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def to_string(
|
|
76
|
+
obj: Any,
|
|
77
|
+
indent: int = 0,
|
|
78
|
+
prefix: Optional[str] = "",
|
|
79
|
+
suffix: Optional[str] = "",
|
|
80
|
+
indent_text: str = " ",
|
|
81
|
+
) -> str:
|
|
82
|
+
return _to_string(
|
|
83
|
+
obj=obj,
|
|
84
|
+
indent=indent,
|
|
85
|
+
prefix=prefix,
|
|
86
|
+
suffix=suffix,
|
|
87
|
+
indent_text=indent_text,
|
|
88
|
+
indent_first_line=True,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if _CLS_CDATA:
|
|
93
|
+
__all__ += (
|
|
94
|
+
"Structure",
|
|
95
|
+
"Union",
|
|
96
|
+
# "Array",
|
|
97
|
+
"BigEndianStructure",
|
|
98
|
+
"LittleEndianStructure",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
class Structure(cts.Structure):
|
|
102
|
+
to_string = to_string
|
|
103
|
+
|
|
104
|
+
class Union(cts.Union):
|
|
105
|
+
to_string = to_string
|
|
106
|
+
|
|
107
|
+
# class Array(cts.Array):
|
|
108
|
+
# to_string = to_string
|
|
109
|
+
|
|
110
|
+
class BigEndianStructure(cts.BigEndianStructure):
|
|
111
|
+
to_string = to_string
|
|
112
|
+
|
|
113
|
+
class LittleEndianStructure(cts.LittleEndianStructure):
|
|
114
|
+
to_string = to_string
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
print("This script is not meant to be run directly.\n")
|
|
119
|
+
sys.exit(-1)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
import ctypes as cts
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
__all__ = ()
|
|
8
|
+
|
|
9
|
+
_IS_WIN = sys.platform[:3].lower() == "win"
|
|
10
|
+
_DLL_NAME = os.path.join(
|
|
11
|
+
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
|
12
|
+
f"libcinterface.{'dll' if _IS_WIN else 'so'}",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
if _IS_WIN:
|
|
16
|
+
from ctypes import wintypes as wts
|
|
17
|
+
|
|
18
|
+
__all__ += ("message_box",)
|
|
19
|
+
|
|
20
|
+
_DLL = cts.CDLL(_DLL_NAME)
|
|
21
|
+
|
|
22
|
+
_MessageBoxXY = _DLL.MessageBoxXY
|
|
23
|
+
_MessageBoxXY.argtypes = (
|
|
24
|
+
wts.HWND,
|
|
25
|
+
wts.LPCWSTR,
|
|
26
|
+
wts.LPCWSTR,
|
|
27
|
+
wts.UINT,
|
|
28
|
+
cts.c_int,
|
|
29
|
+
cts.c_int,
|
|
30
|
+
)
|
|
31
|
+
_MessageBoxXY.restype = cts.c_int
|
|
32
|
+
|
|
33
|
+
def message_box(
|
|
34
|
+
title: str,
|
|
35
|
+
text: str,
|
|
36
|
+
x: Optional[int] = None,
|
|
37
|
+
y: Optional[int] = None,
|
|
38
|
+
style: int = 0,
|
|
39
|
+
hwnd: Optional[wts.HWND] = None,
|
|
40
|
+
) -> int:
|
|
41
|
+
return _MessageBoxXY(
|
|
42
|
+
hwnd,
|
|
43
|
+
text,
|
|
44
|
+
title,
|
|
45
|
+
style,
|
|
46
|
+
x if x is not None else 0x80000000,
|
|
47
|
+
y if y is not None else 0x80000000,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
_clearHooks = _DLL.clearHooks
|
|
51
|
+
_clearHooks.argtypes = ()
|
|
52
|
+
_clearHooks.restype = cts.c_int
|
|
53
|
+
atexit.register(_clearHooks)
|
|
54
|
+
del _clearHooks
|
|
55
|
+
else: # Nix
|
|
56
|
+
_DLL = None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
del _DLL
|
|
60
|
+
del _DLL_NAME
|
|
61
|
+
del _IS_WIN
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
print("This script is not meant to be run directly.\n")
|
|
66
|
+
sys.exit(-1)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module containing package constants.
|
|
3
|
+
|
|
4
|
+
NOTE:
|
|
5
|
+
Due to the fact that for MessageBox there had to be added ~50 of them,
|
|
6
|
+
it's not scalable, so they (or the entire module)
|
|
7
|
+
might be removed in the future.
|
|
8
|
+
|
|
9
|
+
Please use some specialized packages (if any) that export them.
|
|
10
|
+
For MessageBox, win32con can be used (https://github.com/mhammond/pywin32).
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
import sys
|
|
14
|
+
import warnings
|
|
15
|
+
|
|
16
|
+
warnings.warn("This module might be removed in the future")
|
|
17
|
+
|
|
18
|
+
__IS_WIN = sys.platform[:3].lower() == "win"
|
|
19
|
+
|
|
20
|
+
if __IS_WIN:
|
|
21
|
+
MB_OK = 0x00000000
|
|
22
|
+
MB_OKCANCEL = 0x00000001
|
|
23
|
+
MB_ABORTRETRYIGNORE = 0x00000002
|
|
24
|
+
MB_YESNOCANCEL = 0x00000003
|
|
25
|
+
MB_YESNO = 0x00000004
|
|
26
|
+
MB_RETRYCANCEL = 0x00000005
|
|
27
|
+
MB_CANCELTRYCONTINUE = 0x00000006
|
|
28
|
+
MB_ICONHAND = 0x00000010
|
|
29
|
+
MB_ICONQUESTION = 0x00000020
|
|
30
|
+
MB_ICONEXCLAMATION = 0x00000030
|
|
31
|
+
MB_ICONASTERISK = 0x00000040
|
|
32
|
+
MB_USERICON = 0x00000080
|
|
33
|
+
MB_ICONWARNING = MB_ICONEXCLAMATION
|
|
34
|
+
MB_ICONERROR = MB_ICONHAND
|
|
35
|
+
MB_ICONINFORMATION = MB_ICONASTERISK
|
|
36
|
+
MB_ICONSTOP = MB_ICONHAND
|
|
37
|
+
MB_DEFBUTTON1 = 0x00000000
|
|
38
|
+
MB_DEFBUTTON2 = 0x00000100
|
|
39
|
+
MB_DEFBUTTON3 = 0x00000200
|
|
40
|
+
MB_DEFBUTTON4 = 0x00000300
|
|
41
|
+
MB_APPLMODAL = 0x00000000
|
|
42
|
+
MB_SYSTEMMODAL = 0x00001000
|
|
43
|
+
MB_TASKMODAL = 0x00002000
|
|
44
|
+
MB_HELP = 0x00004000
|
|
45
|
+
MB_NOFOCUS = 0x00008000
|
|
46
|
+
MB_SETFOREGROUND = 0x00010000
|
|
47
|
+
MB_DEFAULT_DESKTOP_ONLY = 0x00020000
|
|
48
|
+
MB_TOPMOST = 0x00040000
|
|
49
|
+
MB_RIGHT = 0x00080000
|
|
50
|
+
MB_RTLREADING = 0x00100000
|
|
51
|
+
MB_SERVICE_NOTIFICATION = 0x00200000
|
|
52
|
+
MB_SERVICE_NOTIFICATION = 0x00040000
|
|
53
|
+
MB_SERVICE_NOTIFICATION_NT3X = 0x00040000
|
|
54
|
+
MB_TYPEMASK = 0x0000000F
|
|
55
|
+
MB_ICONMASK = 0x000000F0
|
|
56
|
+
MB_DEFMASK = 0x00000F00
|
|
57
|
+
MB_MODEMASK = 0x00003000
|
|
58
|
+
MB_MISCMASK = 0x0000C000
|
|
59
|
+
|
|
60
|
+
IDOK = 1
|
|
61
|
+
IDCANCEL = 2
|
|
62
|
+
IDABORT = 3
|
|
63
|
+
IDRETRY = 4
|
|
64
|
+
IDIGNORE = 5
|
|
65
|
+
IDYES = 6
|
|
66
|
+
IDNO = 7
|
|
67
|
+
IDCLOSE = 8
|
|
68
|
+
IDHELP = 9
|
|
69
|
+
IDTRYAGAIN = 10
|
|
70
|
+
IDCONTINUE = 11
|
|
71
|
+
IDTIMEOUT = 32000
|
|
72
|
+
|
|
73
|
+
# else: # Nix
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
del __IS_WIN
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
print("This script is not meant to be run directly.\n")
|
|
81
|
+
sys.exit(-1)
|
pycfutils/keyboard.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any, Callable, Optional
|
|
7
|
+
|
|
8
|
+
__all__ = ("read_key",)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _read_key(
|
|
12
|
+
interval: float = 0.5,
|
|
13
|
+
poll_interval: float = 0.1,
|
|
14
|
+
kp_func: Optional[Callable[[], bool]] = None,
|
|
15
|
+
rk_func: Optional[Callable[[], int]] = None,
|
|
16
|
+
start_func: Optional[Callable[[], Any]] = None,
|
|
17
|
+
end_func: Optional[Callable[[Any], None]] = None,
|
|
18
|
+
):
|
|
19
|
+
ctx = start_func() if start_func is not None else None
|
|
20
|
+
try:
|
|
21
|
+
poll_interval = (
|
|
22
|
+
interval / 2.0 if poll_interval > interval / 2.0 else poll_interval
|
|
23
|
+
)
|
|
24
|
+
time_end = time.time() + interval
|
|
25
|
+
while True:
|
|
26
|
+
if kp_func is not None and kp_func():
|
|
27
|
+
return rk_func() if rk_func is not None else None
|
|
28
|
+
if time.time() >= time_end:
|
|
29
|
+
break
|
|
30
|
+
time.sleep(poll_interval)
|
|
31
|
+
return None
|
|
32
|
+
finally:
|
|
33
|
+
if end_func is not None:
|
|
34
|
+
end_func(ctx)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if sys.platform[:3].lower() == "win":
|
|
38
|
+
import msvcrt
|
|
39
|
+
|
|
40
|
+
def _key_pressed_func() -> bool:
|
|
41
|
+
return msvcrt.kbhit()
|
|
42
|
+
|
|
43
|
+
def _read_key_func() -> int:
|
|
44
|
+
return ord(msvcrt.getch())
|
|
45
|
+
|
|
46
|
+
_start_func = None
|
|
47
|
+
_end_func = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
else: # Nix
|
|
51
|
+
import select
|
|
52
|
+
import termios
|
|
53
|
+
import tty
|
|
54
|
+
|
|
55
|
+
def _key_pressed_func() -> bool:
|
|
56
|
+
return sys.stdin in select.select([sys.stdin], [], [], 0)[0]
|
|
57
|
+
|
|
58
|
+
def _read_key_func() -> int:
|
|
59
|
+
return ord(sys.stdin.read(1))
|
|
60
|
+
|
|
61
|
+
def _start_func() -> Any:
|
|
62
|
+
attrs = termios.tcgetattr(sys.stdin)
|
|
63
|
+
tty.setcbreak(sys.stdin.fileno(), when=termios.TCSAFLUSH)
|
|
64
|
+
return attrs
|
|
65
|
+
|
|
66
|
+
def _end_func(attrs) -> None:
|
|
67
|
+
if attrs is not None:
|
|
68
|
+
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, attrs)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
read_key = functools.partial(
|
|
72
|
+
_read_key,
|
|
73
|
+
kp_func=_key_pressed_func,
|
|
74
|
+
rk_func=_read_key_func,
|
|
75
|
+
start_func=_start_func,
|
|
76
|
+
end_func=_end_func,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
del _end_func
|
|
80
|
+
del _start_func
|
|
81
|
+
del _read_key_func
|
|
82
|
+
del _key_pressed_func
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if __name__ == "__main__":
|
|
86
|
+
exit_char = 0x1B # Esc
|
|
87
|
+
print(f"Looping till 0x{exit_char:02X} is pressed")
|
|
88
|
+
while 1:
|
|
89
|
+
k = read_key()
|
|
90
|
+
if k is not None:
|
|
91
|
+
print(f"Pressed: 0x{k:02X} ({k:c})")
|
|
92
|
+
if k == exit_char:
|
|
93
|
+
print("Exit\n")
|
|
94
|
+
break
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# @TODO - cfati: Dummy file - dir is picked up by setuptools.find_packages
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
from pycfutils import common
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CommonTestCase(unittest.TestCase):
|
|
8
|
+
def test_int_format(self):
|
|
9
|
+
self.assertEqual(common.int_format(-101), "{:04d}")
|
|
10
|
+
self.assertEqual(common.int_format(-100), "{:03d}")
|
|
11
|
+
self.assertEqual(common.int_format(-11), "{:03d}")
|
|
12
|
+
self.assertEqual(common.int_format(-10), "{:02d}")
|
|
13
|
+
self.assertEqual(common.int_format(-1), "{:02d}")
|
|
14
|
+
self.assertEqual(common.int_format(0), "{:01d}")
|
|
15
|
+
self.assertEqual(common.int_format(1), "{:01d}")
|
|
16
|
+
self.assertEqual(common.int_format(10), "{:01d}")
|
|
17
|
+
self.assertEqual(common.int_format(11), "{:02d}")
|
|
18
|
+
self.assertEqual(common.int_format(100), "{:02d}")
|
|
19
|
+
self.assertEqual(common.int_format(101), "{:03d}")
|
|
20
|
+
|
|
21
|
+
def test_timestamp_string(self):
|
|
22
|
+
ts = (2024, 5, 6, 12, 34, 56)
|
|
23
|
+
self.assertEqual(
|
|
24
|
+
common.timestamp_string(timestamp=ts, human_readable=False),
|
|
25
|
+
"20240506123456",
|
|
26
|
+
)
|
|
27
|
+
self.assertEqual(
|
|
28
|
+
common.timestamp_string(timestamp=ts, human_readable=True),
|
|
29
|
+
"2024-05-06 12:34:56",
|
|
30
|
+
)
|
|
31
|
+
self.assertTrue(common.timestamp_string(datetime.now()))
|
|
32
|
+
|
|
33
|
+
def test_uniques(self):
|
|
34
|
+
l0 = [1, 2, 3, 1, 4, 3, 5, 1, 1, 2, 6, 0, 0]
|
|
35
|
+
l1 = [1, 2, 3, 4, 5, 6, 0]
|
|
36
|
+
self.assertEqual(common.uniques([]), [])
|
|
37
|
+
self.assertEqual(common.uniques(()), ())
|
|
38
|
+
self.assertEqual(common.uniques(range(3)), (0, 1, 2))
|
|
39
|
+
self.assertEqual(common.uniques(l0), l1)
|
|
40
|
+
self.assertEqual(common.uniques((e for e in l0)), tuple(l1))
|
|
41
|
+
self.assertEqual(set(common.uniques(set(l0))), set(l0))
|
|
42
|
+
|
|
43
|
+
def test_dimensions2d(self):
|
|
44
|
+
self.assertEqual(common.dimensions_2d(-3), (0, 0))
|
|
45
|
+
self.assertEqual(common.dimensions_2d(0), (0, 0))
|
|
46
|
+
self.assertEqual(common.dimensions_2d(1), (1, 1))
|
|
47
|
+
self.assertEqual(common.dimensions_2d(2), (1, 2))
|
|
48
|
+
self.assertEqual(common.dimensions_2d(3), (2, 2))
|
|
49
|
+
self.assertEqual(common.dimensions_2d(4), (2, 2))
|
|
50
|
+
self.assertEqual(common.dimensions_2d(5), (2, 3))
|
|
51
|
+
self.assertEqual(common.dimensions_2d(6), (2, 3))
|
|
52
|
+
self.assertEqual(common.dimensions_2d(7), (3, 3))
|
|
53
|
+
self.assertEqual(common.dimensions_2d(8), (3, 3))
|
|
54
|
+
self.assertEqual(common.dimensions_2d(9), (3, 3))
|
|
55
|
+
self.assertEqual(common.dimensions_2d(10), (3, 4))
|
|
56
|
+
self.assertEqual(common.dimensions_2d(13), (4, 4))
|
|
57
|
+
self.assertEqual(common.dimensions_2d(20), (4, 5))
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import ctypes as cts
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
from pycfutils import ctypes
|
|
5
|
+
|
|
6
|
+
FloatArr10 = cts.c_float * 10
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class S0(ctypes.Structure):
|
|
10
|
+
_fields_ = (
|
|
11
|
+
("i", cts.c_int),
|
|
12
|
+
("s", cts.c_char_p),
|
|
13
|
+
("fa", FloatArr10),
|
|
14
|
+
("pi", cts.POINTER(cts.c_int)),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# @TODO - cfati: Dummy-ish
|
|
19
|
+
class CTypesTestCase(unittest.TestCase):
|
|
20
|
+
def test_to_string(self):
|
|
21
|
+
self.assertEqual(ctypes.to_string(1), "\n1\n")
|
|
22
|
+
|
|
23
|
+
def test_structure(self):
|
|
24
|
+
i = cts.c_int(1234)
|
|
25
|
+
s0 = S0(
|
|
26
|
+
-32,
|
|
27
|
+
b"dummy text",
|
|
28
|
+
FloatArr10(*(float(e) for e in range(20, 30))),
|
|
29
|
+
cts.pointer(i),
|
|
30
|
+
)
|
|
31
|
+
s = s0.to_string(suffix=None)
|
|
32
|
+
# print(s)
|
|
33
|
+
self.assertIn("-32", s)
|
|
34
|
+
self.assertIn("dummy text", s)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
from pycfutils import gui
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# @TODO - cfati: Dummy
|
|
8
|
+
class GUITestCase(unittest.TestCase):
|
|
9
|
+
def test_message_box(self):
|
|
10
|
+
message_box_name = "message_box"
|
|
11
|
+
if sys.platform[:3].lower() == "win":
|
|
12
|
+
self.assertIsNotNone(getattr(gui, message_box_name, None))
|
|
13
|
+
else:
|
|
14
|
+
self.assertIsNone(getattr(gui, message_box_name, None))
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
from pycfutils import keyboard
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class KwyboardTestCase(unittest.TestCase):
|
|
8
|
+
def test_read_key(self):
|
|
9
|
+
itv = 0.5
|
|
10
|
+
pitv = 0.1
|
|
11
|
+
margin = 0.05
|
|
12
|
+
ts = time.time()
|
|
13
|
+
k = keyboard.read_key(interval=itv, poll_interval=pitv)
|
|
14
|
+
self.assertTrue(k is not None or abs(time.time() - ts - itv) < pitv + margin)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022-2024 Cristi Fati
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pycfutils
|
|
3
|
+
Version: 2024.5.8
|
|
4
|
+
Summary: PyCFUtils (Cristi Fati's Utils for Python (&& more)) - a collection of (cool) scripts / utilities
|
|
5
|
+
Home-page: https://github.com/CristiFati/pycfutils
|
|
6
|
+
Download-URL: https://pypi.org/project/pycfutils
|
|
7
|
+
Author: Cristi Fati
|
|
8
|
+
Author-email: fati_utcluj@yahoo.com
|
|
9
|
+
Maintainer: Cristi Fati
|
|
10
|
+
Maintainer-email: fati_utcluj@yahoo.com
|
|
11
|
+
License: MIT
|
|
12
|
+
Platform: All
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Operating System :: POSIX
|
|
18
|
+
Classifier: Operating System :: Unix
|
|
19
|
+
Classifier: Programming Language :: C
|
|
20
|
+
Classifier: Programming Language :: C++
|
|
21
|
+
Classifier: Programming Language :: Python
|
|
22
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
23
|
+
Classifier: Topic :: Software Development
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
|
|
27
|
+
# *PyCFUtils*
|
|
28
|
+
|
|
29
|
+
*PyCFUtils* (**C**risti **F**ati's ***Utils*** for ***Py**thon* (&& more)) - a collection of (cool) scripts / utilities
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
Use *PIP*:
|
|
35
|
+
|
|
36
|
+
```shell
|
|
37
|
+
python -m pip install --upgrade pycfutils
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## Usage example
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import pycfutils.common
|
|
45
|
+
import pycfutils.keyboard
|
|
46
|
+
|
|
47
|
+
print("Press a key...")
|
|
48
|
+
print(pycfutils.keyboard.read_key())
|
|
49
|
+
print(pycfutils.common.timestamp_string(human_readable=True))
|
|
50
|
+
|
|
51
|
+
# --- Windows only ---
|
|
52
|
+
import pycfutils.gui
|
|
53
|
+
|
|
54
|
+
pycfutils.gui.message_box("Title", "Text to display", 200, 200)
|
|
55
|
+
```
|
|
56
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
pycfutils/__init__.py,sha256=KfnpE2DO2NB_2acg0Gc3ti6llSM8zuYUSXtmRXB9ZqI,76
|
|
2
|
+
pycfutils/common.py,sha256=E2TLZsogyxwQlB4huE9GKQ0XR56prWOwISX9_Q0vymE,1615
|
|
3
|
+
pycfutils/ctypes.py,sha256=Gh0XxHc1SC5rlQG5aIADwTsjMKOYe-TtL1_4_LenT6o,3033
|
|
4
|
+
pycfutils/keyboard.py,sha256=eMAjeCb1hATL4IPdaXjCkjwuhOHp1uC_bZBXDXNupJI,2291
|
|
5
|
+
pycfutils/gui/__init__.py,sha256=HXAIbSR5cCrzd-oz-1NVCbiowK2eXsKJm59dIO_8hgg,1376
|
|
6
|
+
pycfutils/gui/constants.py,sha256=1IU9V7lC0x6L809DtbT73mTw5fSiWCPmCPoOf1opxP8,2052
|
|
7
|
+
pycfutils/tests/__init__.py,sha256=KfnpE2DO2NB_2acg0Gc3ti6llSM8zuYUSXtmRXB9ZqI,76
|
|
8
|
+
pycfutils/tests/test_common.py,sha256=o6llfAVTo1uus8i6WjjjYdasKo8ehFFblXTIjIboyAQ,2583
|
|
9
|
+
pycfutils/tests/test_ctypes.py,sha256=DYdwf6YfpndysTCKcviYcqiejDDZcI07nGP5JiCzCbw,809
|
|
10
|
+
pycfutils/tests/test_gui.py,sha256=G32_Rak3k7KDoHUKOHgeFET-Rcb4w9CWJjJlfJn7vSw,403
|
|
11
|
+
pycfutils/tests/test_keyboard.py,sha256=ntD3PtdJnTBiYWiAdI8yBzu3421LPzsvXusWofhuy3s,382
|
|
12
|
+
pycfutils-2024.5.8.dist-info/LICENSE,sha256=kf8dqfk_H2Xt3LmvUktDZmFubGK1xzfl_63jompMkQU,1073
|
|
13
|
+
pycfutils-2024.5.8.dist-info/METADATA,sha256=RM80Aw_nygkwm9ec_1E5nP2DHIUpIfwugMUZZsfKmto,1500
|
|
14
|
+
pycfutils-2024.5.8.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
|
|
15
|
+
pycfutils-2024.5.8.dist-info/top_level.txt,sha256=KV3mT8aaA6pjo3iiPx1ROIfllPX8tb5Z3StPRKebGj0,10
|
|
16
|
+
pycfutils-2024.5.8.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pycfutils
|