fb-tools 2.6.3__tar.gz → 2.7.0__tar.gz
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.
- {fb_tools-2.6.3/lib/fb_tools.egg-info → fb_tools-2.7.0}/PKG-INFO +1 -1
- fb_tools-2.7.0/bin/show-spinner +78 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/__init__.py +1 -1
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/ddns/__init__.py +40 -2
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/errors.py +8 -1
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/handling_obj.py +28 -11
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/local_version.py +1 -1
- fb_tools-2.7.0/lib/fb_tools/show_spinner_app.py +300 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0/lib/fb_tools.egg-info}/PKG-INFO +1 -1
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools.egg-info/SOURCES.txt +2 -0
- fb_tools-2.7.0/locale/de/LC_MESSAGES/fb_tools.mo +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/locale/de/LC_MESSAGES/fb_tools.po +176 -105
- fb_tools-2.7.0/locale/en/LC_MESSAGES/fb_tools.mo +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/locale/en/LC_MESSAGES/fb_tools.po +164 -104
- {fb_tools-2.6.3 → fb_tools-2.7.0}/locale/fb_tools.pot +163 -103
- {fb_tools-2.6.3 → fb_tools-2.7.0}/test/test_20_handling_object.py +48 -1
- fb_tools-2.6.3/locale/de/LC_MESSAGES/fb_tools.mo +0 -0
- fb_tools-2.6.3/locale/en/LC_MESSAGES/fb_tools.mo +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/LICENSE +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/MANIFEST.in +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/README.md +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/bin/get-file-to-remove +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/bin/myip +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/bin/update-ddns +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/app.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/argparse_actions.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/cfg_app.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/colcts.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/collections.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/common.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/config.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/ddns/config.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/ddns/errors.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/ddns/myip_app.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/ddns/update_app.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/get_file_rm_app.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/handler/__init__.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/handler/lock.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/mailaddress.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/merge.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/multi_config/__init__.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/multi_config/files.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/multi_config/inits.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/multi_config/read.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/obj.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/pidfile.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/spinner.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools/xlate.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools.egg-info/dependency_links.txt +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools.egg-info/requires.txt +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/lib/fb_tools.egg-info/top_level.txt +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/setup.cfg +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/setup.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/test/call_script.sh +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/test/call_sleep.sh +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/test/do_sleep +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/test/general.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/test/test_00_errors.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/test/test_05_common.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/test/test_10_base_object.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/test/test_13_colcts.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/test/test_15_mailaddress.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/test/test_17_multicfg.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/test/test_18_pidfile.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/test/test_25_ddns_update_status.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/test/test_30_base_handler.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/test/test_33_lock.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/test/test_40_app_modules.py +0 -0
- {fb_tools-2.6.3 → fb_tools-2.7.0}/test/test_50_spinner.py +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
@summary: Print a newline separated list of files generated from file globbing patterns.
|
|
5
|
+
|
|
6
|
+
@author: Frank Brehm
|
|
7
|
+
@contact: frank.brehm@pixelpark.com
|
|
8
|
+
@copyright: © 2024 by Frank Brehm, Berlin
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import print_function
|
|
11
|
+
|
|
12
|
+
# Standard modules
|
|
13
|
+
import locale
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
except ImportError:
|
|
19
|
+
from pathlib2 import Path
|
|
20
|
+
|
|
21
|
+
__exp_py_version_major__ = 3
|
|
22
|
+
__min_py_version_minor__ = 6
|
|
23
|
+
|
|
24
|
+
if sys.version_info[0] != __exp_py_version_major__:
|
|
25
|
+
print('This script is intended to use with Python {}.'.format(
|
|
26
|
+
__exp_py_version_major__), file=sys.stderr)
|
|
27
|
+
print('You are using Python: {0}.{1}.{2}-{3}-{4}.'.format(
|
|
28
|
+
*sys.version_info) + '\n', file=sys.stderr)
|
|
29
|
+
sys.exit(1)
|
|
30
|
+
|
|
31
|
+
if sys.version_info[1] < __min_py_version_minor__:
|
|
32
|
+
print('A minimal Python version of {maj}.{min} is necessary to execute this script.'.format(
|
|
33
|
+
maj=__exp_py_version_major__, min=__min_py_version_minor__), file=sys.stderr)
|
|
34
|
+
print('You are using Python: {0}.{1}.{2}-{3}-{4}.'.format(
|
|
35
|
+
*sys.version_info) + '\n', file=sys.stderr)
|
|
36
|
+
sys.exit(1)
|
|
37
|
+
|
|
38
|
+
my_path = Path(__file__)
|
|
39
|
+
my_real_path = my_path.resolve()
|
|
40
|
+
bin_path = my_real_path.parent
|
|
41
|
+
base_dir = bin_path.parent
|
|
42
|
+
lib_dir = base_dir.joinpath('lib')
|
|
43
|
+
module_dir = lib_dir.joinpath('fb_tools')
|
|
44
|
+
|
|
45
|
+
# print("My path: {!r}".format(str(my_path)))
|
|
46
|
+
# print("My real path: {!r}".format(str(my_real_path)))
|
|
47
|
+
# print("Bin path: {!r}".format(str(bin_path)))
|
|
48
|
+
# print("Base dir: {!r}".format(str(base_dir)))
|
|
49
|
+
# print("Lib dir: {!r}".format(str(lib_dir)))
|
|
50
|
+
# print("Module dir: {!r}".format(str(module_dir)))
|
|
51
|
+
|
|
52
|
+
if module_dir.exists():
|
|
53
|
+
sys.path.insert(0, str(lib_dir))
|
|
54
|
+
|
|
55
|
+
# own modules:
|
|
56
|
+
from fb_tools.show_spinner_app import ShowSpinnerApplication
|
|
57
|
+
from fb_tools.xlate import XLATOR
|
|
58
|
+
|
|
59
|
+
_ = XLATOR.gettext
|
|
60
|
+
|
|
61
|
+
__author__ = 'Frank Brehm <frank.brehm@pixelpark.com>'
|
|
62
|
+
__copyright__ = '(C) 2024 by Frank Brehm, Pixelpark GmbH, Berlin'
|
|
63
|
+
|
|
64
|
+
appname = my_path.name
|
|
65
|
+
|
|
66
|
+
locale.setlocale(locale.LC_ALL, '')
|
|
67
|
+
|
|
68
|
+
app = ShowSpinnerApplication(appname=appname, base_dir=base_dir)
|
|
69
|
+
app.initialized = True
|
|
70
|
+
|
|
71
|
+
if app.verbose > 2:
|
|
72
|
+
print(_('{c}-Object:\n{a}').format(c=app.__class__.__name__, a=app), file=sys.stderr)
|
|
73
|
+
|
|
74
|
+
app()
|
|
75
|
+
|
|
76
|
+
sys.exit(0)
|
|
77
|
+
|
|
78
|
+
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
|
|
@@ -46,10 +46,11 @@ from .. import __version__ as GLOBAL_VERSION
|
|
|
46
46
|
from ..argparse_actions import DirectoryOptionAction
|
|
47
47
|
from ..cfg_app import FbConfigApplication
|
|
48
48
|
from ..common import pp
|
|
49
|
+
from ..common import to_bool
|
|
49
50
|
from ..errors import IoTimeoutError
|
|
50
51
|
from ..xlate import XLATOR, format_list
|
|
51
52
|
|
|
52
|
-
__version__ = '2.
|
|
53
|
+
__version__ = '2.3.0'
|
|
53
54
|
LOG = logging.getLogger(__name__)
|
|
54
55
|
|
|
55
56
|
_ = XLATOR.gettext
|
|
@@ -65,6 +66,8 @@ class BaseDdnsApplication(FbConfigApplication):
|
|
|
65
66
|
work_directory_must_exists = False
|
|
66
67
|
work_directory_must_be_writeable = False
|
|
67
68
|
|
|
69
|
+
default_verify_server_cert = True
|
|
70
|
+
|
|
68
71
|
# -------------------------------------------------------------------------
|
|
69
72
|
def __init__(
|
|
70
73
|
self, appname=None, verbose=0, version=GLOBAL_VERSION, base_dir=None,
|
|
@@ -76,6 +79,7 @@ class BaseDdnsApplication(FbConfigApplication):
|
|
|
76
79
|
description = _('This is a base DDNS related application.')
|
|
77
80
|
|
|
78
81
|
self._user_agent = '{}/{}'.format(self.library_name, GLOBAL_VERSION)
|
|
82
|
+
self._verify_server_cert = self.default_verify_server_cert
|
|
79
83
|
|
|
80
84
|
super(BaseDdnsApplication, self).__init__(
|
|
81
85
|
appname=appname, verbose=verbose, version=version, base_dir=base_dir,
|
|
@@ -99,6 +103,19 @@ class BaseDdnsApplication(FbConfigApplication):
|
|
|
99
103
|
raise DdnsAppError(_('Invalid user agent {!r} given.').format(value))
|
|
100
104
|
self._user_agent = str(value).strip()
|
|
101
105
|
|
|
106
|
+
# -----------------------------------------------------------
|
|
107
|
+
@property
|
|
108
|
+
def verify_server_cert(self):
|
|
109
|
+
"""Return whether to verify the SSL certificate of the requested server."""
|
|
110
|
+
return self._verify_server_cert
|
|
111
|
+
|
|
112
|
+
@verify_server_cert.setter
|
|
113
|
+
def verify_server_cert(self, value):
|
|
114
|
+
if value is None:
|
|
115
|
+
self._verify_server_cert = self.default_verify_server_cert
|
|
116
|
+
return
|
|
117
|
+
self._verify_server_cert = to_bool(value)
|
|
118
|
+
|
|
102
119
|
# -------------------------------------------------------------------------
|
|
103
120
|
@property
|
|
104
121
|
def has_dns_module(self):
|
|
@@ -119,6 +136,7 @@ class BaseDdnsApplication(FbConfigApplication):
|
|
|
119
136
|
res = super(BaseDdnsApplication, self).as_dict(short=short)
|
|
120
137
|
res['has_dns_module'] = self.has_dns_module
|
|
121
138
|
res['user_agent'] = self.user_agent
|
|
139
|
+
res['verify_server_cert'] = self.verify_server_cert
|
|
122
140
|
|
|
123
141
|
return res
|
|
124
142
|
|
|
@@ -176,6 +194,14 @@ class BaseDdnsApplication(FbConfigApplication):
|
|
|
176
194
|
DdnsConfiguration.default_timeout),
|
|
177
195
|
)
|
|
178
196
|
|
|
197
|
+
ddns_group.add_argument(
|
|
198
|
+
'-k', '--insecure', dest='insecure', action='store_true',
|
|
199
|
+
help=_(
|
|
200
|
+
'By default, every secure connection {app} makes is verified to be secure before '
|
|
201
|
+
'the request takes place. This option makes {app} skip the verification step and '
|
|
202
|
+
'proceed without checking.').format(app=self.appname),
|
|
203
|
+
)
|
|
204
|
+
|
|
179
205
|
super(BaseDdnsApplication, self).init_arg_parser()
|
|
180
206
|
|
|
181
207
|
# -------------------------------------------------------------------------
|
|
@@ -225,6 +251,9 @@ class BaseDdnsApplication(FbConfigApplication):
|
|
|
225
251
|
logging.getLogger('requests').setLevel(logging.WARNING)
|
|
226
252
|
self.loglevel_requests_set = True
|
|
227
253
|
|
|
254
|
+
if self.args.insecure:
|
|
255
|
+
self.verify_server_cert = False
|
|
256
|
+
|
|
228
257
|
self.initialized = True
|
|
229
258
|
|
|
230
259
|
# -------------------------------------------------------------------------
|
|
@@ -251,6 +280,15 @@ class BaseDdnsApplication(FbConfigApplication):
|
|
|
251
280
|
self, url, method='GET', data=None, headers=None, may_simulate=False,
|
|
252
281
|
return_json=True):
|
|
253
282
|
"""Perform the underlying Web request."""
|
|
283
|
+
verify = None
|
|
284
|
+
if url.startswith('https://'):
|
|
285
|
+
if self.verify_server_cert:
|
|
286
|
+
verify = True
|
|
287
|
+
else:
|
|
288
|
+
verify = False
|
|
289
|
+
urllib3.disable_warnings()
|
|
290
|
+
LOG.debug(f'Verifying remote server certificate: {verify!r}.')
|
|
291
|
+
|
|
254
292
|
if headers is None:
|
|
255
293
|
headers = {}
|
|
256
294
|
|
|
@@ -282,7 +320,7 @@ class BaseDdnsApplication(FbConfigApplication):
|
|
|
282
320
|
|
|
283
321
|
session = requests.Session()
|
|
284
322
|
response = session.request(
|
|
285
|
-
method, url, data=data, headers=headers, timeout=self.cfg.timeout)
|
|
323
|
+
method, url, data=data, headers=headers, timeout=self.cfg.timeout, verify=verify)
|
|
286
324
|
|
|
287
325
|
except (
|
|
288
326
|
socket.timeout, urllib3.exceptions.ConnectTimeoutError,
|
|
@@ -14,7 +14,7 @@ import signal
|
|
|
14
14
|
# Own modules
|
|
15
15
|
from .xlate import XLATOR
|
|
16
16
|
|
|
17
|
-
__version__ = '2.
|
|
17
|
+
__version__ = '2.4.0'
|
|
18
18
|
|
|
19
19
|
_ = XLATOR.gettext
|
|
20
20
|
ngettext = XLATOR.ngettext
|
|
@@ -150,6 +150,13 @@ class AbortAppError(FbAppError):
|
|
|
150
150
|
pass
|
|
151
151
|
|
|
152
152
|
|
|
153
|
+
# =============================================================================
|
|
154
|
+
class ExitAppError(FbAppError):
|
|
155
|
+
"""Special exception class exiting the application."""
|
|
156
|
+
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
|
|
153
160
|
# =============================================================================
|
|
154
161
|
class FbCfgAppError(FbAppError):
|
|
155
162
|
"""Generic exception class for all configured application classes."""
|
|
@@ -51,12 +51,17 @@ import six
|
|
|
51
51
|
from . import DEFAULT_TERMINAL_HEIGHT, DEFAULT_TERMINAL_WIDTH
|
|
52
52
|
from .common import caller_search_path, encode_or_bust, pp, to_bool, to_str
|
|
53
53
|
from .common import indent, is_sequence
|
|
54
|
-
from .errors import AbortAppError
|
|
55
|
-
from .errors import
|
|
54
|
+
from .errors import AbortAppError
|
|
55
|
+
from .errors import ExitAppError
|
|
56
|
+
from .errors import InterruptError
|
|
57
|
+
from .errors import IoTimeoutError
|
|
58
|
+
from .errors import ReadTimeoutError
|
|
59
|
+
from .errors import TimeoutOnPromptError
|
|
60
|
+
from .errors import WriteTimeoutError
|
|
56
61
|
from .obj import FbBaseObject
|
|
57
62
|
from .xlate import XLATOR, format_list
|
|
58
63
|
|
|
59
|
-
__version__ = '2.
|
|
64
|
+
__version__ = '2.3.0'
|
|
60
65
|
LOG = logging.getLogger(__name__)
|
|
61
66
|
|
|
62
67
|
_ = XLATOR.gettext
|
|
@@ -941,7 +946,9 @@ class HandlingObject(FbBaseObject):
|
|
|
941
946
|
return
|
|
942
947
|
|
|
943
948
|
# -------------------------------------------------------------------------
|
|
944
|
-
def get_password(
|
|
949
|
+
def get_password(
|
|
950
|
+
self, first_prompt=None, second_prompt=None, may_empty=True, repeat=True,
|
|
951
|
+
raise_on_exit=False):
|
|
945
952
|
"""
|
|
946
953
|
Ask the user for a password on the console.
|
|
947
954
|
|
|
@@ -959,6 +966,9 @@ class HandlingObject(FbBaseObject):
|
|
|
959
966
|
@param repeat: Asking for the password a second time, which must be equal
|
|
960
967
|
to the first given password.
|
|
961
968
|
@type repeat: bool
|
|
969
|
+
@param raise_on_exit: raising an ExitAppError instead of exiting the application
|
|
970
|
+
in different situations
|
|
971
|
+
@type raise_on_exit: bool
|
|
962
972
|
|
|
963
973
|
@return: The entered password
|
|
964
974
|
@rtype: str
|
|
@@ -977,10 +987,11 @@ class HandlingObject(FbBaseObject):
|
|
|
977
987
|
|
|
978
988
|
if not self.quiet:
|
|
979
989
|
print()
|
|
980
|
-
ret_passwd = self._get_password(first_prompt, may_empty)
|
|
990
|
+
ret_passwd = self._get_password(first_prompt, may_empty, raise_on_exit=raise_on_exit)
|
|
981
991
|
if ret_passwd:
|
|
982
992
|
if repeat:
|
|
983
|
-
second_passwd = self._get_password(
|
|
993
|
+
second_passwd = self._get_password(
|
|
994
|
+
second_prompt, may_empty=False, raise_on_exit=raise_on_exit)
|
|
984
995
|
if ret_passwd != second_passwd:
|
|
985
996
|
msg = _('The entered passwords does not match.')
|
|
986
997
|
LOG.error(msg)
|
|
@@ -990,7 +1001,7 @@ class HandlingObject(FbBaseObject):
|
|
|
990
1001
|
return ret_passwd
|
|
991
1002
|
|
|
992
1003
|
# -------------------------------------------------------------------------
|
|
993
|
-
def _get_password(self, prompt, may_empty=True):
|
|
1004
|
+
def _get_password(self, prompt, may_empty=True, raise_on_exit=False):
|
|
994
1005
|
|
|
995
1006
|
def passwd_alarm_caller(signum, sigframe):
|
|
996
1007
|
raise TimeoutOnPromptError(self.prompt_timeout)
|
|
@@ -1021,13 +1032,19 @@ class HandlingObject(FbBaseObject):
|
|
|
1021
1032
|
|
|
1022
1033
|
except (TimeoutOnPromptError, AbortAppError) as e:
|
|
1023
1034
|
msg = _('Got a {}:').format(e.__class__.__name__) + ' ' + str(e)
|
|
1024
|
-
|
|
1025
|
-
|
|
1035
|
+
if raise_on_exit:
|
|
1036
|
+
raise ExitAppError(msg)
|
|
1037
|
+
else:
|
|
1038
|
+
LOG.error(msg)
|
|
1039
|
+
sys.exit(10)
|
|
1026
1040
|
|
|
1027
1041
|
except KeyboardInterrupt:
|
|
1028
1042
|
msg = _('Got a {}:').format('KeyboardInterrupt') + ' ' + msg_intr
|
|
1029
|
-
|
|
1030
|
-
|
|
1043
|
+
if raise_on_exit:
|
|
1044
|
+
raise ExitAppError(msg)
|
|
1045
|
+
else:
|
|
1046
|
+
LOG.error(msg)
|
|
1047
|
+
sys.exit(10)
|
|
1031
1048
|
|
|
1032
1049
|
finally:
|
|
1033
1050
|
signal.alarm(0)
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
__author__ = 'Frank Brehm <frank@brehm-online.com>'
|
|
12
12
|
__copyright__ = '(C) 2024 by Frank Brehm, Berlin'
|
|
13
13
|
__contact__ = 'frank@brehm-online.com'
|
|
14
|
-
__version__ = '2.
|
|
14
|
+
__version__ = '2.7.0'
|
|
15
15
|
__license__ = 'LGPL3+'
|
|
16
16
|
|
|
17
17
|
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
@summary: The module for the show-spinner application class.
|
|
5
|
+
|
|
6
|
+
@author: Frank Brehm
|
|
7
|
+
@contact: frank.brehm@pixelpark.com
|
|
8
|
+
@copyright: © 2024 by Frank Brehm, Berlin
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import absolute_import
|
|
11
|
+
|
|
12
|
+
# Standard modules
|
|
13
|
+
import copy
|
|
14
|
+
import logging
|
|
15
|
+
import random
|
|
16
|
+
import signal
|
|
17
|
+
import sys
|
|
18
|
+
import time
|
|
19
|
+
|
|
20
|
+
# Third party modules
|
|
21
|
+
|
|
22
|
+
# Own modules
|
|
23
|
+
from . import __version__ as __global_version__
|
|
24
|
+
from .app import BaseApplication
|
|
25
|
+
from .app import SIGNAL_NAMES
|
|
26
|
+
from .spinner import CycleList
|
|
27
|
+
from .spinner import Spinner
|
|
28
|
+
from .xlate import XLATOR
|
|
29
|
+
|
|
30
|
+
__version__ = '0.2.0'
|
|
31
|
+
LOG = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
_ = XLATOR.gettext
|
|
34
|
+
ngettext = XLATOR.ngettext
|
|
35
|
+
|
|
36
|
+
# =============================================================================
|
|
37
|
+
class ShowSpinnerApplication(BaseApplication):
|
|
38
|
+
"""Class for the show-spinner application object."""
|
|
39
|
+
|
|
40
|
+
default_show_time = 6
|
|
41
|
+
default_spinner = 'random'
|
|
42
|
+
default_prompt = _('Waiting ... ')
|
|
43
|
+
|
|
44
|
+
# -------------------------------------------------------------------------
|
|
45
|
+
def __init__(
|
|
46
|
+
self, verbose=0, version=__global_version__, *arg, **kwargs):
|
|
47
|
+
"""Initialise of the show-spinnerapplication object."""
|
|
48
|
+
desc = _(
|
|
49
|
+
'Shows one or more spinners, and their names, if multiple spinners should be shown. '
|
|
50
|
+
'If no spinner is given, a random spinner will be displayed.')
|
|
51
|
+
|
|
52
|
+
self._show_time = None
|
|
53
|
+
self._prompt = self.default_prompt
|
|
54
|
+
|
|
55
|
+
self.spinners = []
|
|
56
|
+
self.all_spinners = sorted(CycleList.keys(), key=str.lower)
|
|
57
|
+
|
|
58
|
+
super(ShowSpinnerApplication, self).__init__(
|
|
59
|
+
description=desc,
|
|
60
|
+
verbose=verbose,
|
|
61
|
+
version=version,
|
|
62
|
+
*arg, **kwargs
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
self.initialized = True
|
|
66
|
+
|
|
67
|
+
# -----------------------------------------------------------
|
|
68
|
+
@property
|
|
69
|
+
def show_time(self):
|
|
70
|
+
"""Give the number of seconds for displaying each spinner."""
|
|
71
|
+
return self._show_time
|
|
72
|
+
|
|
73
|
+
@show_time.setter
|
|
74
|
+
def show_time(self, value):
|
|
75
|
+
if value is None:
|
|
76
|
+
self._show_time = None
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
v = float(value)
|
|
80
|
+
if v < 0:
|
|
81
|
+
msg = _(
|
|
82
|
+
'A negative time for showing the spinner {v!r} is not allowed.').format(
|
|
83
|
+
value)
|
|
84
|
+
raise ValueError(msg)
|
|
85
|
+
|
|
86
|
+
if v == 0:
|
|
87
|
+
self._show_time = None
|
|
88
|
+
else:
|
|
89
|
+
self._show_time = v
|
|
90
|
+
|
|
91
|
+
# -----------------------------------------------------------
|
|
92
|
+
@property
|
|
93
|
+
def prompt(self):
|
|
94
|
+
"""Give the prompt displayed before the spinner."""
|
|
95
|
+
return self._prompt
|
|
96
|
+
|
|
97
|
+
@prompt.setter
|
|
98
|
+
def prompt(self, value):
|
|
99
|
+
if value is None:
|
|
100
|
+
self._prompt = ''
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
self._prompt = str(value)
|
|
104
|
+
|
|
105
|
+
# -------------------------------------------------------------------------
|
|
106
|
+
def as_dict(self, short=True):
|
|
107
|
+
"""
|
|
108
|
+
Transform the elements of the object into a dict.
|
|
109
|
+
|
|
110
|
+
@param short: don't include local properties in resulting dict.
|
|
111
|
+
@type short: bool
|
|
112
|
+
|
|
113
|
+
@return: structure as dict
|
|
114
|
+
@rtype: dict
|
|
115
|
+
"""
|
|
116
|
+
res = super(ShowSpinnerApplication, self).as_dict(short=short)
|
|
117
|
+
|
|
118
|
+
res['prompt'] = self.prompt
|
|
119
|
+
res['show_time'] = self.show_time
|
|
120
|
+
|
|
121
|
+
return res
|
|
122
|
+
|
|
123
|
+
# -------------------------------------------------------------------------
|
|
124
|
+
def init_arg_parser(self):
|
|
125
|
+
"""Initialise the argument parser."""
|
|
126
|
+
super(ShowSpinnerApplication, self).init_arg_parser()
|
|
127
|
+
|
|
128
|
+
app_group = self.arg_parser.add_argument_group(_('Options for {}').format(self.appname))
|
|
129
|
+
|
|
130
|
+
app_group.add_argument(
|
|
131
|
+
'-P', '--prompt', dest='prompt',
|
|
132
|
+
help=_(
|
|
133
|
+
'The prompt displayed before the spinner, if only one spinner should be shown. '
|
|
134
|
+
'Default: {!r}.').format(self.default_prompt),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
app_group.add_argument(
|
|
138
|
+
'-t', '--time', dest='time', metavar=_('SECONDS'), type=float,
|
|
139
|
+
help=_(
|
|
140
|
+
'The time in seconds for displaying each spinner.').format(
|
|
141
|
+
self.default_show_time),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
app_group.add_argument(
|
|
145
|
+
'spinnerei', metavar=_('SPINNER'), type=str, nargs='*',
|
|
146
|
+
help=_(
|
|
147
|
+
'The spinners, which should be displayed. If not given, a random spinner will be '
|
|
148
|
+
'displayed, which is the same as giving {rand!r} as the name of the spinner. '
|
|
149
|
+
'If giving {list!r}, a simple list of all available spinners will be shown, '
|
|
150
|
+
'without displaying them. If giving {all!r}, all available spinners will be '
|
|
151
|
+
'shown.').format(rand='random', list='list', all='all'),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# -------------------------------------------------------------------------
|
|
155
|
+
def perform_arg_parser(self):
|
|
156
|
+
"""Parse the command line options."""
|
|
157
|
+
if self.args.prompt is not None:
|
|
158
|
+
self.prompt = self.args.prompt
|
|
159
|
+
|
|
160
|
+
if self.args.time is not None:
|
|
161
|
+
try:
|
|
162
|
+
self.show_time = self.args.time
|
|
163
|
+
except (ValueError, TypeError) as e:
|
|
164
|
+
LOG.error(str(e))
|
|
165
|
+
self.arg_parser.print_usage(sys.stdout)
|
|
166
|
+
self.exit(1)
|
|
167
|
+
|
|
168
|
+
all_spinners = ['random'] + self.all_spinners
|
|
169
|
+
|
|
170
|
+
if self.args.spinnerei:
|
|
171
|
+
for spinner in self.args.spinnerei:
|
|
172
|
+
if spinner == 'all':
|
|
173
|
+
if len(self.spinners):
|
|
174
|
+
msg = _('You may give {!r} only as the only spinner.').format('all')
|
|
175
|
+
LOG.error(msg)
|
|
176
|
+
self.arg_parser.print_usage(sys.stdout)
|
|
177
|
+
self.exit(1)
|
|
178
|
+
self.spinners.append(spinner)
|
|
179
|
+
if not self.show_time:
|
|
180
|
+
self.show_time = self.default_show_time
|
|
181
|
+
continue
|
|
182
|
+
|
|
183
|
+
if spinner == 'list':
|
|
184
|
+
if len(self.spinners):
|
|
185
|
+
msg = _('You may give {!r} only as the only spinner.').format('list')
|
|
186
|
+
LOG.error(msg)
|
|
187
|
+
self.arg_parser.print_usage(sys.stdout)
|
|
188
|
+
self.exit(1)
|
|
189
|
+
self.spinners.append(spinner)
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
if spinner not in all_spinners:
|
|
193
|
+
msg = _('Invalid spinner {!r} given.').format(spinner)
|
|
194
|
+
LOG.error(msg)
|
|
195
|
+
self.arg_parser.print_usage(sys.stdout)
|
|
196
|
+
self.exit(1)
|
|
197
|
+
|
|
198
|
+
self.spinners.append(spinner)
|
|
199
|
+
else:
|
|
200
|
+
self.spinners.append(self.default_spinner)
|
|
201
|
+
|
|
202
|
+
# -------------------------------------------------------------------------
|
|
203
|
+
def _run(self):
|
|
204
|
+
"""Run the application."""
|
|
205
|
+
prompt = self.prompt
|
|
206
|
+
|
|
207
|
+
show_time = 1
|
|
208
|
+
if self.show_time:
|
|
209
|
+
show_time = self.show_time
|
|
210
|
+
|
|
211
|
+
if 'list' in self.spinners:
|
|
212
|
+
self.list_spinners()
|
|
213
|
+
self.exit(0)
|
|
214
|
+
|
|
215
|
+
if 'all' in self.spinners:
|
|
216
|
+
self.spinners = copy.copy(self.all_spinners)
|
|
217
|
+
|
|
218
|
+
if len(self.spinners) > 1 or ('random' in self.spinners and self.args.prompt is None):
|
|
219
|
+
prompt = _('Spinner {!r}: ')
|
|
220
|
+
|
|
221
|
+
# ---------------------
|
|
222
|
+
def _signal_handler(signum, frame):
|
|
223
|
+
|
|
224
|
+
signame = '{}'.format(signum)
|
|
225
|
+
msg = _('Got a signal {}.').format(signum)
|
|
226
|
+
if signum in SIGNAL_NAMES:
|
|
227
|
+
signame = SIGNAL_NAMES[signum]
|
|
228
|
+
msg = _('Got a signal {n!r} ({s}).').format(
|
|
229
|
+
n=signame, s=signum)
|
|
230
|
+
LOG.debug(msg)
|
|
231
|
+
|
|
232
|
+
if signum in (
|
|
233
|
+
signal.SIGHUP, signal.SIGINT, signal.SIGABRT,
|
|
234
|
+
signal.SIGTERM, signal.SIGKILL, signal.SIGQUIT):
|
|
235
|
+
print()
|
|
236
|
+
if self.verbose > 1:
|
|
237
|
+
LOG.info(_('Exit on signal {n!r} ({s}).').format(n=signame, s=signum))
|
|
238
|
+
self.exit(0)
|
|
239
|
+
|
|
240
|
+
# ------------------------
|
|
241
|
+
old_handlers = {}
|
|
242
|
+
|
|
243
|
+
if self.verbose > 2:
|
|
244
|
+
LOG.debug(_('Tweaking signal handlers.'))
|
|
245
|
+
for signum in (
|
|
246
|
+
signal.SIGHUP, signal.SIGINT, signal.SIGABRT,
|
|
247
|
+
signal.SIGTERM, signal.SIGQUIT):
|
|
248
|
+
if self.verbose > 3:
|
|
249
|
+
signame = SIGNAL_NAMES[signum]
|
|
250
|
+
LOG.debug(_('Setting signal handler for {n!r} ({s}).').format(
|
|
251
|
+
n=signame, s=signum))
|
|
252
|
+
old_handlers[signum] = signal.signal(signum, _signal_handler)
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
for spinner_name in self.spinners:
|
|
256
|
+
if spinner_name == 'random':
|
|
257
|
+
spinner_name = self._get_random_spinner_name()
|
|
258
|
+
print(prompt.format(spinner_name), end='', flush=True)
|
|
259
|
+
|
|
260
|
+
with Spinner('', spinner_name):
|
|
261
|
+
if self.show_time:
|
|
262
|
+
time.sleep(show_time)
|
|
263
|
+
else:
|
|
264
|
+
while True:
|
|
265
|
+
time.sleep(show_time)
|
|
266
|
+
print()
|
|
267
|
+
finally:
|
|
268
|
+
if self.verbose > 2:
|
|
269
|
+
LOG.debug(_('Restoring original signal handlers.'))
|
|
270
|
+
for signum in old_handlers.keys():
|
|
271
|
+
signal.signal(signum, old_handlers[signum])
|
|
272
|
+
|
|
273
|
+
self.exit(0)
|
|
274
|
+
|
|
275
|
+
# -------------------------------------------------------------------------
|
|
276
|
+
def _get_random_spinner_name(self):
|
|
277
|
+
|
|
278
|
+
randomizer = random.SystemRandom()
|
|
279
|
+
return randomizer.choice(list(CycleList.keys()))
|
|
280
|
+
|
|
281
|
+
# -------------------------------------------------------------------------
|
|
282
|
+
def list_spinners(self):
|
|
283
|
+
"""Print out a list of all available spinners."""
|
|
284
|
+
if self.verbose:
|
|
285
|
+
title = _('All available spinners:')
|
|
286
|
+
print(title)
|
|
287
|
+
print('-' * len(title))
|
|
288
|
+
|
|
289
|
+
for spinner_name in self.all_spinners:
|
|
290
|
+
print(spinner_name)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# =============================================================================
|
|
294
|
+
if __name__ == '__main__':
|
|
295
|
+
|
|
296
|
+
pass
|
|
297
|
+
|
|
298
|
+
# =============================================================================
|
|
299
|
+
|
|
300
|
+
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
|
|
@@ -5,6 +5,7 @@ setup.cfg
|
|
|
5
5
|
setup.py
|
|
6
6
|
bin/get-file-to-remove
|
|
7
7
|
bin/myip
|
|
8
|
+
bin/show-spinner
|
|
8
9
|
bin/update-ddns
|
|
9
10
|
lib/fb_tools/__init__.py
|
|
10
11
|
lib/fb_tools/app.py
|
|
@@ -22,6 +23,7 @@ lib/fb_tools/mailaddress.py
|
|
|
22
23
|
lib/fb_tools/merge.py
|
|
23
24
|
lib/fb_tools/obj.py
|
|
24
25
|
lib/fb_tools/pidfile.py
|
|
26
|
+
lib/fb_tools/show_spinner_app.py
|
|
25
27
|
lib/fb_tools/spinner.py
|
|
26
28
|
lib/fb_tools/xlate.py
|
|
27
29
|
lib/fb_tools.egg-info/PKG-INFO
|
|
Binary file
|