invesyservertools 0.1.0__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.
- invesyservertools/__init__.py +26 -0
- invesyservertools/interactive_tools.py +74 -0
- invesyservertools/print_tools.py +260 -0
- invesyservertools/system_tools.py +78 -0
- invesyservertools-0.1.0.dist-info/METADATA +113 -0
- invesyservertools-0.1.0.dist-info/RECORD +9 -0
- invesyservertools-0.1.0.dist-info/WHEEL +5 -0
- invesyservertools-0.1.0.dist-info/licenses/LICENSE.txt +7 -0
- invesyservertools-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
__all__ = []
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _load_tools():
|
|
8
|
+
"""Re-export the public names of every ``*_tools.py`` submodule."""
|
|
9
|
+
package_dir = Path(__file__).parent
|
|
10
|
+
for file in sorted(package_dir.glob('*_tools.py')):
|
|
11
|
+
module = importlib.import_module(f'.{file.stem}', __package__)
|
|
12
|
+
names = getattr(
|
|
13
|
+
module,
|
|
14
|
+
'__all__',
|
|
15
|
+
[name for name in dir(module) if not name.startswith('_')]
|
|
16
|
+
)
|
|
17
|
+
for name in names:
|
|
18
|
+
if name in __all__:
|
|
19
|
+
raise RuntimeError(
|
|
20
|
+
f"duplicate export {name!r} from {file.stem}"
|
|
21
|
+
)
|
|
22
|
+
globals()[name] = getattr(module, name)
|
|
23
|
+
__all__.append(name)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_load_tools()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
=================
|
|
4
|
+
interactive_tools
|
|
5
|
+
=================
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import warnings
|
|
10
|
+
from typing import Optional, Union
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import termios
|
|
14
|
+
except ImportError: # termios is POSIX-only (absent on Windows)
|
|
15
|
+
termios = None
|
|
16
|
+
|
|
17
|
+
from .print_tools import print_
|
|
18
|
+
|
|
19
|
+
__all__ = ['wait_for_key']
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def wait_for_key(
|
|
23
|
+
text: str = 'continue: press any button',
|
|
24
|
+
list_bullet: Union[bool, str] = False,
|
|
25
|
+
color: str = None,
|
|
26
|
+
indent: int = 0
|
|
27
|
+
) -> Optional[str]:
|
|
28
|
+
''' Wait for a key press on the console and return it.'''
|
|
29
|
+
|
|
30
|
+
if termios is None:
|
|
31
|
+
raise RuntimeError(
|
|
32
|
+
'wait_for_key requires a POSIX terminal '
|
|
33
|
+
'(the termios module is unavailable on this platform)'
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if text:
|
|
37
|
+
print_(
|
|
38
|
+
text,
|
|
39
|
+
color=color,
|
|
40
|
+
list_bullet=list_bullet,
|
|
41
|
+
indent=indent
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# without a real terminal we cannot switch to raw mode; read a single
|
|
45
|
+
# character (or return None at EOF) instead of crashing in termios
|
|
46
|
+
if not sys.stdin.isatty():
|
|
47
|
+
try:
|
|
48
|
+
return sys.stdin.read(1) or None
|
|
49
|
+
except OSError:
|
|
50
|
+
warnings.warn("wait_for_key: failed to read from stdin")
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
fd = sys.stdin.fileno()
|
|
54
|
+
result = None
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
oldterm = termios.tcgetattr(fd)
|
|
58
|
+
newattr = termios.tcgetattr(fd)
|
|
59
|
+
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
|
|
60
|
+
termios.tcsetattr(fd, termios.TCSANOW, newattr)
|
|
61
|
+
except termios.error:
|
|
62
|
+
warnings.warn("wait_for_key: failed to configure the terminal")
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
result = sys.stdin.read(1) or None
|
|
67
|
+
except OSError:
|
|
68
|
+
warnings.warn("wait_for_key: failed to read from stdin")
|
|
69
|
+
finally:
|
|
70
|
+
# only reached when the terminal was reconfigured successfully,
|
|
71
|
+
# so oldterm is always bound here
|
|
72
|
+
termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
|
|
73
|
+
|
|
74
|
+
return result
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
===========
|
|
4
|
+
print_tools
|
|
5
|
+
===========
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging as _logging
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import textwrap
|
|
12
|
+
from typing import Dict, Optional, Union
|
|
13
|
+
|
|
14
|
+
from colorama import Fore, Style
|
|
15
|
+
|
|
16
|
+
LIST_BULLET = '-'
|
|
17
|
+
COLOR_RESET = getattr(Style, 'RESET_ALL')
|
|
18
|
+
|
|
19
|
+
INDENT_MULT = 4
|
|
20
|
+
|
|
21
|
+
UNIT_PREFIXES = {
|
|
22
|
+
'binary': ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'],
|
|
23
|
+
'decimal': ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
__all__ = ['and_list', 'indent', 'print_', 'format_filesize']
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def and_list(
|
|
30
|
+
elements: list,
|
|
31
|
+
et: str = 'and'
|
|
32
|
+
) -> str:
|
|
33
|
+
"""Create a human-readable list, joining elements with commas and a final conjunction.
|
|
34
|
+
|
|
35
|
+
:param elements: items to join
|
|
36
|
+
:param et: conjunction before the last element (e.g. ``'and'``, ``'und'``)
|
|
37
|
+
:return: the formatted string
|
|
38
|
+
"""
|
|
39
|
+
elements = [str(el) for el in elements]
|
|
40
|
+
if len(elements) <= 1:
|
|
41
|
+
return ''.join(elements)
|
|
42
|
+
return ', '.join(elements[:-1]) + f' {et} ' + elements[-1]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def indent(
|
|
46
|
+
level: int = 1,
|
|
47
|
+
raw: bool = False,
|
|
48
|
+
base: int = INDENT_MULT,
|
|
49
|
+
base_str: str = ' '
|
|
50
|
+
) -> str:
|
|
51
|
+
"""Build an indentation string of *level* steps.
|
|
52
|
+
|
|
53
|
+
:param level: number of indentation steps
|
|
54
|
+
:param raw: if ``True`` return the full width; otherwise trim one
|
|
55
|
+
character (see below)
|
|
56
|
+
:param base: width of one indentation step
|
|
57
|
+
:param base_str: character the indentation is filled with
|
|
58
|
+
:return: the indentation string
|
|
59
|
+
"""
|
|
60
|
+
base = base * base_str
|
|
61
|
+
if raw:
|
|
62
|
+
return base * level
|
|
63
|
+
else:
|
|
64
|
+
# Trim the last character so that appending a single-character marker
|
|
65
|
+
# — e.g. the bullet from print_(..., list_bullet=True) — places that
|
|
66
|
+
# marker exactly on the base-column grid (columns base, 2*base, …;
|
|
67
|
+
# with the defaults: 4, 8, …). Use raw=True when you add no marker.
|
|
68
|
+
return (base * level)[:-1]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def print_(
|
|
72
|
+
*texts: str,
|
|
73
|
+
logging: Optional[_logging.Logger] = None,
|
|
74
|
+
indent: int = 0,
|
|
75
|
+
list_bullet: Union[bool, str] = '',
|
|
76
|
+
color: Union[str, Dict[int, str]] = '',
|
|
77
|
+
style: str = '',
|
|
78
|
+
sep: str = ' ',
|
|
79
|
+
end: str = '\n',
|
|
80
|
+
ignore: bool = False,
|
|
81
|
+
flush: bool = None,
|
|
82
|
+
nowrapper: bool = None
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Enhanced print function.
|
|
85
|
+
|
|
86
|
+
The text arguments can contain ``'\\n'``, either as a
|
|
87
|
+
singleton or as part of a string argument.
|
|
88
|
+
|
|
89
|
+
:param texts: the texts to print
|
|
90
|
+
:param logging: provide a logging object to log the output
|
|
91
|
+
:param indent: set the indentation level
|
|
92
|
+
:param list_bullet: ``True`` for the default bullet or provide a string
|
|
93
|
+
:param color: a color name or a dict mapping 1-based text indices to
|
|
94
|
+
color names, e.g. ``{1: 'red', 4: 'blue'}``.
|
|
95
|
+
Strings containing only line endings don't count for the index.
|
|
96
|
+
:param style: a colorama style name
|
|
97
|
+
:param sep: separator between texts (as in :func:`print`)
|
|
98
|
+
:param end: end string (as in :func:`print`)
|
|
99
|
+
:param ignore: don't print (but possibly log)
|
|
100
|
+
:param flush: flush the output (as in :func:`print`)
|
|
101
|
+
:param nowrapper: disable textwrap
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
# are we outputting to a tty?
|
|
105
|
+
output_to_tty = sys.stdout.isatty()
|
|
106
|
+
|
|
107
|
+
if nowrapper is None:
|
|
108
|
+
nowrapper = not output_to_tty
|
|
109
|
+
if flush is None:
|
|
110
|
+
flush = not output_to_tty
|
|
111
|
+
|
|
112
|
+
# wrapping needs a known terminal width, available only on a tty
|
|
113
|
+
if not output_to_tty:
|
|
114
|
+
nowrapper = True
|
|
115
|
+
|
|
116
|
+
texts = [str(t) for t in texts]
|
|
117
|
+
|
|
118
|
+
# we have line endings
|
|
119
|
+
if any(
|
|
120
|
+
['\n' in t for t in texts]
|
|
121
|
+
):
|
|
122
|
+
for i, t in enumerate(texts):
|
|
123
|
+
if t.endswith('\n'):
|
|
124
|
+
try:
|
|
125
|
+
texts[i + 1] = '\n' + texts[i + 1] # update next string by adding line end
|
|
126
|
+
texts[i] = t[:-1] # remove line end from current string
|
|
127
|
+
except IndexError:
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
nowrapper = True
|
|
131
|
+
|
|
132
|
+
texts = list(filter(None, texts)) # remove empty strings
|
|
133
|
+
|
|
134
|
+
if logging:
|
|
135
|
+
logging.info(sep.join(texts))
|
|
136
|
+
|
|
137
|
+
if ignore:
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
colors = None
|
|
141
|
+
|
|
142
|
+
if isinstance(color, dict):
|
|
143
|
+
colors = color
|
|
144
|
+
color = None
|
|
145
|
+
|
|
146
|
+
# only use colors/styles if stdout is a tty
|
|
147
|
+
if not output_to_tty:
|
|
148
|
+
colors = {}
|
|
149
|
+
color = None
|
|
150
|
+
style = ''
|
|
151
|
+
|
|
152
|
+
if len(texts):
|
|
153
|
+
try:
|
|
154
|
+
prefix = ' ' * indent
|
|
155
|
+
except TypeError:
|
|
156
|
+
prefix = indent or ''
|
|
157
|
+
|
|
158
|
+
# visible width of the prefix, ignoring invisible color escapes
|
|
159
|
+
visible_prefix_len = len(prefix)
|
|
160
|
+
|
|
161
|
+
suffix = ''
|
|
162
|
+
if color or style:
|
|
163
|
+
prefix += getattr(
|
|
164
|
+
Fore,
|
|
165
|
+
(color or '').upper(), ''
|
|
166
|
+
) + getattr(
|
|
167
|
+
Style, (style or '').upper(),
|
|
168
|
+
''
|
|
169
|
+
)
|
|
170
|
+
suffix = COLOR_RESET
|
|
171
|
+
|
|
172
|
+
if colors:
|
|
173
|
+
texts_colored = []
|
|
174
|
+
# line-ending-only arguments don't consume a color index
|
|
175
|
+
color_index = 0
|
|
176
|
+
for t in texts:
|
|
177
|
+
p = s = ''
|
|
178
|
+
if t.strip('\n'):
|
|
179
|
+
color_index += 1
|
|
180
|
+
if color_index in colors:
|
|
181
|
+
p = getattr(Fore, colors[color_index].upper(), '')
|
|
182
|
+
s = COLOR_RESET if p else ''
|
|
183
|
+
|
|
184
|
+
texts_colored.append(p + t + s)
|
|
185
|
+
|
|
186
|
+
texts = texts_colored
|
|
187
|
+
|
|
188
|
+
if isinstance(list_bullet, bool):
|
|
189
|
+
if list_bullet:
|
|
190
|
+
list_bullet = LIST_BULLET
|
|
191
|
+
else:
|
|
192
|
+
list_bullet = ''
|
|
193
|
+
|
|
194
|
+
bullet_sep = ' '
|
|
195
|
+
if list_bullet:
|
|
196
|
+
prefix += list_bullet + bullet_sep
|
|
197
|
+
visible_prefix_len += len(list_bullet) + len(bullet_sep)
|
|
198
|
+
|
|
199
|
+
if output_to_tty:
|
|
200
|
+
try:
|
|
201
|
+
term_columns = os.get_terminal_size().columns
|
|
202
|
+
wrapper = textwrap.TextWrapper(
|
|
203
|
+
initial_indent=prefix,
|
|
204
|
+
width=term_columns,
|
|
205
|
+
subsequent_indent=' ' * visible_prefix_len
|
|
206
|
+
)
|
|
207
|
+
except OSError:
|
|
208
|
+
nowrapper = True
|
|
209
|
+
|
|
210
|
+
if flush or nowrapper:
|
|
211
|
+
texts[-1] += suffix
|
|
212
|
+
print(
|
|
213
|
+
prefix + texts[0],
|
|
214
|
+
*texts[1:],
|
|
215
|
+
sep=sep,
|
|
216
|
+
end=end,
|
|
217
|
+
flush=flush
|
|
218
|
+
)
|
|
219
|
+
else:
|
|
220
|
+
print(wrapper.fill(sep.join(texts) + suffix), end=end)
|
|
221
|
+
|
|
222
|
+
else:
|
|
223
|
+
print()
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def format_filesize(
|
|
227
|
+
num: Union[int, float],
|
|
228
|
+
suffix: str = "B",
|
|
229
|
+
prefix_type: str = 'binary'
|
|
230
|
+
) -> str:
|
|
231
|
+
"""Format a file size as a human-readable string.
|
|
232
|
+
|
|
233
|
+
Inspired by `Fred Cirera <https://stackoverflow.com/a/1094933/1690805>`_.
|
|
234
|
+
|
|
235
|
+
:param num: size in bytes
|
|
236
|
+
:param suffix: unit suffix (default ``'B'``)
|
|
237
|
+
:param prefix_type: ``'binary'`` for IEC prefixes (Ki, Mi, …) or
|
|
238
|
+
``'decimal'`` for SI prefixes (K, M, …)
|
|
239
|
+
:raises ValueError: if *prefix_type* is not ``'binary'`` or ``'decimal'``
|
|
240
|
+
:return: the formatted size string
|
|
241
|
+
"""
|
|
242
|
+
if prefix_type not in UNIT_PREFIXES:
|
|
243
|
+
raise ValueError(
|
|
244
|
+
f"prefix_type must be one of {sorted(UNIT_PREFIXES)}, "
|
|
245
|
+
f"got {prefix_type!r}"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
prefixes = UNIT_PREFIXES[prefix_type]
|
|
249
|
+
divider = 1024.0 if prefix_type == 'binary' else 1000.0
|
|
250
|
+
|
|
251
|
+
# stop before the largest prefix so it can serve as the overflow cap;
|
|
252
|
+
# dividing past it would underreport values above the largest unit
|
|
253
|
+
for unit in prefixes[:-1]:
|
|
254
|
+
if abs(num) < divider:
|
|
255
|
+
return f"{num:3.1f} {unit}{suffix}"
|
|
256
|
+
|
|
257
|
+
num /= divider
|
|
258
|
+
|
|
259
|
+
# value reaches or exceeds the largest defined prefix
|
|
260
|
+
return f"{num:3.1f} {prefixes[-1]}{suffix}"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
============
|
|
4
|
+
system_tools
|
|
5
|
+
============
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import warnings
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import pwd
|
|
15
|
+
except ImportError: # pwd is POSIX-only (absent on Windows)
|
|
16
|
+
pwd = None
|
|
17
|
+
|
|
18
|
+
from .print_tools import print_
|
|
19
|
+
|
|
20
|
+
__all__ = ['get_username', 'get_home_dir', 'find_file_path']
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_username() -> str:
|
|
24
|
+
"""Return the login name of the current user."""
|
|
25
|
+
if pwd is None:
|
|
26
|
+
raise RuntimeError(
|
|
27
|
+
'get_username requires a POSIX platform '
|
|
28
|
+
'(the pwd module is unavailable on this platform)'
|
|
29
|
+
)
|
|
30
|
+
return pwd.getpwuid(os.getuid())[0]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_home_dir() -> str:
|
|
34
|
+
"""Return the current user's home directory."""
|
|
35
|
+
return str(Path.home())
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def find_file_path(
|
|
39
|
+
filename: str,
|
|
40
|
+
rootpath: str,
|
|
41
|
+
path_only: bool = False,
|
|
42
|
+
verbose=False
|
|
43
|
+
) -> Optional[str]:
|
|
44
|
+
"""Find the complete path for a file.
|
|
45
|
+
|
|
46
|
+
Walks the directory tree starting at *rootpath* and returns the
|
|
47
|
+
full path to the first file matching *filename*.
|
|
48
|
+
|
|
49
|
+
Unreadable subdirectories are skipped with a warning rather than
|
|
50
|
+
aborting the search, so a ``None`` result does not guarantee the file
|
|
51
|
+
is absent from an inaccessible subtree.
|
|
52
|
+
|
|
53
|
+
:param filename: name of the file to search for
|
|
54
|
+
:param rootpath: root directory to start the search (``~`` is expanded)
|
|
55
|
+
:param path_only: if ``True``, return only the containing directory
|
|
56
|
+
:param verbose: if ``True``, print each directory as it is visited
|
|
57
|
+
:return: the file path, the containing directory, or ``None`` if the
|
|
58
|
+
file is not found
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
# expand ~ and ~user to the corresponding home directory
|
|
62
|
+
rootpath = os.path.expanduser(rootpath)
|
|
63
|
+
|
|
64
|
+
def _on_error(error):
|
|
65
|
+
warnings.warn(
|
|
66
|
+
f"find_file_path: cannot access {error.filename!r}: {error}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
for root, dirs, files in os.walk(rootpath, onerror=_on_error):
|
|
70
|
+
if verbose:
|
|
71
|
+
print_(root)
|
|
72
|
+
for name in files:
|
|
73
|
+
if name == filename:
|
|
74
|
+
if path_only:
|
|
75
|
+
return root
|
|
76
|
+
return os.path.join(root, filename)
|
|
77
|
+
|
|
78
|
+
return None
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: invesyservertools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Tools for Python scripts or terminal
|
|
5
|
+
Author-email: Georg Pfolz <georg.pfolz@invesy.at>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://gitlab.com/Rastaf/invesyservertools
|
|
8
|
+
Project-URL: Bug Tracker, https://gitlab.com/Rastaf/invesyservertools/-/issues
|
|
9
|
+
Project-URL: Documentation, https://rastaf.gitlab.io/invesyservertools/
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: POSIX
|
|
12
|
+
Classifier: Operating System :: MacOS
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE.txt
|
|
16
|
+
Requires-Dist: colorama
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: pytest; extra == "test"
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# invesyservertools
|
|
22
|
+
|
|
23
|
+
A small set of useful tools on a server, especially for terminal scripts.
|
|
24
|
+
|
|
25
|
+
Why "invesy"? Invesy (from German **In**halts**ve**rwaltungs**sy**stem == content management system) is a closed source cms I created with Thomas Macher. It's only used in-house, that's why we didn't bother making it open source.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
pip install invesyservertools
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Requires Python >= 3.9 on a POSIX platform (Linux, *BSD, macOS — uses the Unix-only modules `termios` and `pwd`).
|
|
34
|
+
|
|
35
|
+
## Quick example
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from invesyservertools import print_, format_filesize, wait_for_key
|
|
39
|
+
|
|
40
|
+
print_('Hello', 'World', color={1: 'green'}, sep=' ')
|
|
41
|
+
print(format_filesize(234567890)) # '223.7 MiB'
|
|
42
|
+
wait_for_key()
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## What's in the box?
|
|
46
|
+
|
|
47
|
+
### interactive_tools
|
|
48
|
+
- **wait_for_key**: Wait for a pressed key to continue.
|
|
49
|
+
|
|
50
|
+
### print_tools
|
|
51
|
+
- **and_list**: human-readable list like `a, 1, 2 and something`
|
|
52
|
+
- **indent**: get an indentation string
|
|
53
|
+
- **print_**: Enhanced print function, specially enhanced for printing in the terminal and into log files.
|
|
54
|
+
- **format_filesize**: return a human-readable filesize, like `31.9 GiB`.
|
|
55
|
+
|
|
56
|
+
### system_tools
|
|
57
|
+
- **get_username**: get the username in the operating system
|
|
58
|
+
- **get_home_dir**: get the home directory
|
|
59
|
+
- **find_file_path**: provide a filename and a start path to begin searching recursively, get the complete path including the filename
|
|
60
|
+
|
|
61
|
+
## Documentation
|
|
62
|
+
|
|
63
|
+
Full API docs: https://rastaf.gitlab.io/invesyservertools/
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
MIT
|
|
68
|
+
|
|
69
|
+
# History
|
|
70
|
+
## 0.1.0
|
|
71
|
+
- Packaging modernized: built from `pyproject.toml`, tests run under `pytest`
|
|
72
|
+
- **Breaking:** requires Python >= 3.9 (was 3.7)
|
|
73
|
+
- **Breaking:** `find_file_path()` returns `None` when nothing is found (was a message string); the `not_found` argument was removed
|
|
74
|
+
- Targets POSIX (Linux, *BSD, macOS) but now imports cleanly everywhere — the POSIX-only `wait_for_key` and `get_username` raise a clear error on non-POSIX instead of breaking `import invesyservertools`
|
|
75
|
+
- **and_list**: keeps the conjunction for multi-word or punctuated final elements
|
|
76
|
+
- **print_**: several correctness fixes (terminal-size detection, `end`/separator handling, colour indexing and wrapped-line indentation, `style=` off a tty)
|
|
77
|
+
- **format_filesize**: correct SI (decimal) thresholds and large-value handling; clear error on an invalid `prefix_type`
|
|
78
|
+
- **wait_for_key**: robust on non-tty stdin (no crash; returns `None` at EOF)
|
|
79
|
+
|
|
80
|
+
## 0.0.10 (2023-07-13)
|
|
81
|
+
- bumped version to 0.0.10
|
|
82
|
+
- replaced dynamic package imports using `exec()` and `sys.path` mutation with `importlib`
|
|
83
|
+
|
|
84
|
+
## 0.0.9 (2023-06-10)
|
|
85
|
+
- fixed IndexError in **print_** (for line ending in last string)
|
|
86
|
+
- **print_** checks for terminal (-> no colors, no wrapper)
|
|
87
|
+
|
|
88
|
+
## 0.0.5 (2022-12-09)
|
|
89
|
+
- fixed import of submodules
|
|
90
|
+
|
|
91
|
+
## 0.0.4 (2022-12-09)
|
|
92
|
+
- *wait_for_key* gets a "indent" argument
|
|
93
|
+
- Sphinx documentation
|
|
94
|
+
|
|
95
|
+
## 0.0.3 (2022-06-16)
|
|
96
|
+
Added support for line breaks in *print_*:
|
|
97
|
+
|
|
98
|
+
- can be added as singletons or as part of strings
|
|
99
|
+
- line break singletons are not counted in the color argument
|
|
100
|
+
|
|
101
|
+
## 0.0.2 (2022-06-13)
|
|
102
|
+
* splitted modules (now: *interactive_tools*, *print_tools*, *system_tools*)
|
|
103
|
+
* added new functions:
|
|
104
|
+
* system_tools
|
|
105
|
+
* get_username
|
|
106
|
+
* get\_home\_dir
|
|
107
|
+
* find\_file\_path
|
|
108
|
+
* print_tools
|
|
109
|
+
* and_list
|
|
110
|
+
* format_filesize
|
|
111
|
+
|
|
112
|
+
## 0.0.1 (2022-06-10)
|
|
113
|
+
* first version
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
invesyservertools/__init__.py,sha256=aeEZB3l4AhPmCS1dbDbS27N8_5g7r9bc6iB0zdmYht8,745
|
|
2
|
+
invesyservertools/interactive_tools.py,sha256=w5Vx7wj9lrSQpmWIPGfJ_Ya_Z9rXNZ78QqOqYzwoiXw,1954
|
|
3
|
+
invesyservertools/print_tools.py,sha256=HF8vFZhrT0LH2PAKhdeHFglGQrqsAW9-kosXGROFoYI,7598
|
|
4
|
+
invesyservertools/system_tools.py,sha256=0EiWqlTtotSziXr9SmGETePNnSR8vtp1NMCmNVxeG3g,2150
|
|
5
|
+
invesyservertools-0.1.0.dist-info/licenses/LICENSE.txt,sha256=-vEbVpKDEALxD3QtS7yMJ4T9uRB_AbN9aQey6X941Ws,1050
|
|
6
|
+
invesyservertools-0.1.0.dist-info/METADATA,sha256=3Ga32YQdPNdvJ2K5uFaUm2CEQIzOfya_mtb7Ye8BCB8,4015
|
|
7
|
+
invesyservertools-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
8
|
+
invesyservertools-0.1.0.dist-info/top_level.txt,sha256=goITXYiS_8Th-GRPKmgp9P0erFrNVyKbTFTluN0bL9Y,18
|
|
9
|
+
invesyservertools-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2022 Georg Pfolz
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
invesyservertools
|