meerschaum 2.2.6__py3-none-any.whl → 2.3.0.dev1__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.
- meerschaum/__init__.py +4 -1
- meerschaum/__main__.py +10 -5
- meerschaum/_internal/arguments/_parser.py +44 -15
- meerschaum/_internal/entry.py +35 -14
- meerschaum/_internal/shell/Shell.py +155 -53
- meerschaum/_internal/shell/updates.py +175 -0
- meerschaum/actions/api.py +12 -12
- meerschaum/actions/attach.py +95 -0
- meerschaum/actions/delete.py +35 -26
- meerschaum/actions/register.py +19 -5
- meerschaum/actions/show.py +119 -148
- meerschaum/actions/start.py +85 -75
- meerschaum/actions/stop.py +68 -39
- meerschaum/actions/sync.py +3 -3
- meerschaum/actions/upgrade.py +28 -36
- meerschaum/api/_events.py +18 -1
- meerschaum/api/_oauth2.py +2 -0
- meerschaum/api/_websockets.py +2 -2
- meerschaum/api/dash/jobs.py +5 -2
- meerschaum/api/routes/__init__.py +1 -0
- meerschaum/api/routes/_actions.py +122 -44
- meerschaum/api/routes/_jobs.py +340 -0
- meerschaum/api/routes/_pipes.py +25 -25
- meerschaum/config/_default.py +1 -0
- meerschaum/config/_formatting.py +1 -0
- meerschaum/config/_paths.py +5 -0
- meerschaum/config/_shell.py +84 -67
- meerschaum/config/_version.py +1 -1
- meerschaum/config/static/__init__.py +9 -0
- meerschaum/connectors/__init__.py +9 -11
- meerschaum/connectors/api/APIConnector.py +18 -1
- meerschaum/connectors/api/_actions.py +60 -71
- meerschaum/connectors/api/_jobs.py +260 -0
- meerschaum/connectors/api/_misc.py +1 -1
- meerschaum/connectors/api/_request.py +13 -9
- meerschaum/connectors/parse.py +23 -7
- meerschaum/core/Pipe/_sync.py +3 -0
- meerschaum/plugins/__init__.py +89 -5
- meerschaum/utils/daemon/Daemon.py +333 -149
- meerschaum/utils/daemon/FileDescriptorInterceptor.py +19 -10
- meerschaum/utils/daemon/RotatingFile.py +18 -7
- meerschaum/utils/daemon/StdinFile.py +110 -0
- meerschaum/utils/daemon/__init__.py +40 -27
- meerschaum/utils/formatting/__init__.py +83 -37
- meerschaum/utils/formatting/_jobs.py +118 -51
- meerschaum/utils/formatting/_shell.py +6 -0
- meerschaum/utils/jobs/_Job.py +684 -0
- meerschaum/utils/jobs/__init__.py +245 -0
- meerschaum/utils/misc.py +18 -17
- meerschaum/utils/packages/__init__.py +21 -15
- meerschaum/utils/packages/_packages.py +2 -2
- meerschaum/utils/prompt.py +20 -7
- meerschaum/utils/schedule.py +21 -15
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dev1.dist-info}/METADATA +9 -9
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dev1.dist-info}/RECORD +61 -54
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dev1.dist-info}/WHEEL +1 -1
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dev1.dist-info}/LICENSE +0 -0
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dev1.dist-info}/NOTICE +0 -0
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dev1.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dev1.dist-info}/top_level.txt +0 -0
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dev1.dist-info}/zip-safe +0 -0
@@ -9,10 +9,12 @@ Intercept OS-level file descriptors.
|
|
9
9
|
import os
|
10
10
|
import select
|
11
11
|
import traceback
|
12
|
+
import errno
|
12
13
|
from threading import Event
|
13
14
|
from datetime import datetime
|
14
15
|
from meerschaum.utils.typing import Callable
|
15
16
|
from meerschaum.utils.warnings import warn
|
17
|
+
from meerschaum.config.paths import DAEMON_ERROR_LOG_PATH
|
16
18
|
|
17
19
|
FD_CLOSED: int = 9
|
18
20
|
STOP_READING_FD_EVENT: Event = Event()
|
@@ -65,8 +67,13 @@ class FileDescriptorInterceptor:
|
|
65
67
|
except BlockingIOError:
|
66
68
|
continue
|
67
69
|
except OSError as e:
|
68
|
-
|
69
|
-
|
70
|
+
if e.errno == errno.EBADF:
|
71
|
+
### File descriptor is closed.
|
72
|
+
pass
|
73
|
+
elif e.errno == errno.EINTR:
|
74
|
+
continue # Interrupted system call, just try again
|
75
|
+
else:
|
76
|
+
warn(f"OSError in FileDescriptorInterceptor: {e}")
|
70
77
|
break
|
71
78
|
|
72
79
|
try:
|
@@ -86,9 +93,11 @@ class FileDescriptorInterceptor:
|
|
86
93
|
else data.replace(b'\n', b'\n' + injected_bytes)
|
87
94
|
)
|
88
95
|
os.write(self.new_file_descriptor, modified_data)
|
89
|
-
except
|
90
|
-
|
91
|
-
|
96
|
+
except (BrokenPipeError, OSError):
|
97
|
+
break
|
98
|
+
except Exception:
|
99
|
+
with open(DAEMON_ERROR_LOG_PATH, 'a+', encoding='utf-8') as f:
|
100
|
+
f.write(traceback.format_exc())
|
92
101
|
break
|
93
102
|
|
94
103
|
|
@@ -103,7 +112,7 @@ class FileDescriptorInterceptor:
|
|
103
112
|
except OSError as e:
|
104
113
|
if e.errno != FD_CLOSED:
|
105
114
|
warn(
|
106
|
-
|
115
|
+
"Error while trying to close the duplicated file descriptor:\n"
|
107
116
|
+ f"{traceback.format_exc()}"
|
108
117
|
)
|
109
118
|
|
@@ -112,7 +121,7 @@ class FileDescriptorInterceptor:
|
|
112
121
|
except OSError as e:
|
113
122
|
if e.errno != FD_CLOSED:
|
114
123
|
warn(
|
115
|
-
|
124
|
+
"Error while trying to close the write-pipe "
|
116
125
|
+ "to the intercepted file descriptor:\n"
|
117
126
|
+ f"{traceback.format_exc()}"
|
118
127
|
)
|
@@ -121,7 +130,7 @@ class FileDescriptorInterceptor:
|
|
121
130
|
except OSError as e:
|
122
131
|
if e.errno != FD_CLOSED:
|
123
132
|
warn(
|
124
|
-
|
133
|
+
"Error while trying to close the read-pipe "
|
125
134
|
+ "to the intercepted file descriptor:\n"
|
126
135
|
+ f"{traceback.format_exc()}"
|
127
136
|
)
|
@@ -131,7 +140,7 @@ class FileDescriptorInterceptor:
|
|
131
140
|
except OSError as e:
|
132
141
|
if e.errno != FD_CLOSED:
|
133
142
|
warn(
|
134
|
-
|
143
|
+
"Error while trying to close the signal-read-pipe "
|
135
144
|
+ "to the intercepted file descriptor:\n"
|
136
145
|
+ f"{traceback.format_exc()}"
|
137
146
|
)
|
@@ -141,7 +150,7 @@ class FileDescriptorInterceptor:
|
|
141
150
|
except OSError as e:
|
142
151
|
if e.errno != FD_CLOSED:
|
143
152
|
warn(
|
144
|
-
|
153
|
+
"Error while trying to close the signal-write-pipe "
|
145
154
|
+ "to the intercepted file descriptor:\n"
|
146
155
|
+ f"{traceback.format_exc()}"
|
147
156
|
)
|
@@ -63,6 +63,9 @@ class RotatingFile(io.IOBase):
|
|
63
63
|
|
64
64
|
write_timestamps: bool, default False
|
65
65
|
If `True`, prepend the current UTC timestamp to each line of the file.
|
66
|
+
|
67
|
+
timestamp_format: str, default '%Y-%m-%d %H:%M'
|
68
|
+
If `write_timestamps` is `True`, use this format for the timestamps.
|
66
69
|
"""
|
67
70
|
self.file_path = pathlib.Path(file_path)
|
68
71
|
if num_files_to_keep is None:
|
@@ -232,10 +235,10 @@ class RotatingFile(io.IOBase):
|
|
232
235
|
|
233
236
|
|
234
237
|
def refresh_files(
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
238
|
+
self,
|
239
|
+
potential_new_len: int = 0,
|
240
|
+
start_interception: bool = False,
|
241
|
+
) -> '_io.TextUIWrapper':
|
239
242
|
"""
|
240
243
|
Check the state of the subfiles.
|
241
244
|
If the latest subfile is too large, create a new file and delete old ones.
|
@@ -339,7 +342,7 @@ class RotatingFile(io.IOBase):
|
|
339
342
|
|
340
343
|
def get_timestamp_prefix_str(self) -> str:
|
341
344
|
"""
|
342
|
-
Return the current minute
|
345
|
+
Return the current minute prefix string.
|
343
346
|
"""
|
344
347
|
return datetime.now(timezone.utc).strftime(self.timestamp_format) + ' | '
|
345
348
|
|
@@ -371,6 +374,9 @@ class RotatingFile(io.IOBase):
|
|
371
374
|
self._current_file_obj.write(data)
|
372
375
|
if suffix_str:
|
373
376
|
self._current_file_obj.write(suffix_str)
|
377
|
+
except BrokenPipeError:
|
378
|
+
warn("BrokenPipeError encountered. The daemon may have been terminated.")
|
379
|
+
return
|
374
380
|
except Exception as e:
|
375
381
|
warn(f"Failed to write to subfile:\n{traceback.format_exc()}")
|
376
382
|
self.flush()
|
@@ -565,7 +571,8 @@ class RotatingFile(io.IOBase):
|
|
565
571
|
return
|
566
572
|
|
567
573
|
self._cursor = (max_ix, position)
|
568
|
-
self._current_file_obj
|
574
|
+
if self._current_file_obj is not None:
|
575
|
+
self._current_file_obj.seek(position)
|
569
576
|
|
570
577
|
|
571
578
|
def flush(self) -> None:
|
@@ -581,10 +588,14 @@ class RotatingFile(io.IOBase):
|
|
581
588
|
if self.redirect_streams:
|
582
589
|
try:
|
583
590
|
sys.stdout.flush()
|
591
|
+
except BrokenPipeError:
|
592
|
+
pass
|
584
593
|
except Exception as e:
|
585
594
|
warn(f"Failed to flush STDOUT:\n{traceback.format_exc()}")
|
586
595
|
try:
|
587
596
|
sys.stderr.flush()
|
597
|
+
except BrokenPipeError:
|
598
|
+
pass
|
588
599
|
except Exception as e:
|
589
600
|
warn(f"Failed to flush STDERR:\n{traceback.format_exc()}")
|
590
601
|
|
@@ -596,7 +607,6 @@ class RotatingFile(io.IOBase):
|
|
596
607
|
if not self.write_timestamps:
|
597
608
|
return
|
598
609
|
|
599
|
-
threads = self.__dict__.get('_interceptor_threads', [])
|
600
610
|
self._stdout_interceptor = FileDescriptorInterceptor(
|
601
611
|
sys.stdout.fileno(),
|
602
612
|
self.get_timestamp_prefix_str,
|
@@ -639,6 +649,7 @@ class RotatingFile(io.IOBase):
|
|
639
649
|
"""
|
640
650
|
if not self.write_timestamps:
|
641
651
|
return
|
652
|
+
|
642
653
|
interceptors = self.__dict__.get('_interceptors', [])
|
643
654
|
interceptor_threads = self.__dict__.get('_interceptor_threads', [])
|
644
655
|
|
@@ -0,0 +1,110 @@
|
|
1
|
+
#! /usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# vim:fenc=utf-8
|
4
|
+
|
5
|
+
"""
|
6
|
+
Create a file manager to pass STDIN to the Daemon.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import io
|
10
|
+
import pathlib
|
11
|
+
import time
|
12
|
+
import os
|
13
|
+
import selectors
|
14
|
+
import traceback
|
15
|
+
|
16
|
+
from meerschaum.utils.typing import Optional
|
17
|
+
from meerschaum.utils.warnings import warn
|
18
|
+
|
19
|
+
|
20
|
+
class StdinFile(io.TextIOBase):
|
21
|
+
"""
|
22
|
+
Redirect user input into a Daemon's context.
|
23
|
+
"""
|
24
|
+
def __init__(
|
25
|
+
self,
|
26
|
+
file_path: pathlib.Path,
|
27
|
+
lock_file_path: Optional[pathlib.Path] = None,
|
28
|
+
):
|
29
|
+
self.file_path = file_path
|
30
|
+
self.blocking_file_path = (
|
31
|
+
lock_file_path
|
32
|
+
if lock_file_path is not None
|
33
|
+
else (file_path.parent / (file_path.name + '.block'))
|
34
|
+
)
|
35
|
+
self._file_handler = None
|
36
|
+
self._fd = None
|
37
|
+
self.sel = selectors.DefaultSelector()
|
38
|
+
|
39
|
+
@property
|
40
|
+
def file_handler(self):
|
41
|
+
"""
|
42
|
+
Return the read file handler to the provided file path.
|
43
|
+
"""
|
44
|
+
if self._file_handler is not None:
|
45
|
+
return self._file_handler
|
46
|
+
|
47
|
+
if self.file_path.exists():
|
48
|
+
self.file_path.unlink()
|
49
|
+
|
50
|
+
os.mkfifo(self.file_path.as_posix(), mode=0o600)
|
51
|
+
|
52
|
+
self._fd = os.open(self.file_path, os.O_RDONLY | os.O_NONBLOCK)
|
53
|
+
self._file_handler = os.fdopen(self._fd, 'rb', buffering=0)
|
54
|
+
self.sel.register(self._file_handler, selectors.EVENT_READ)
|
55
|
+
return self._file_handler
|
56
|
+
|
57
|
+
def write(self, data):
|
58
|
+
if isinstance(data, str):
|
59
|
+
data = data.encode('utf-8')
|
60
|
+
|
61
|
+
with open(self.file_path, 'wb') as f:
|
62
|
+
f.write(data)
|
63
|
+
|
64
|
+
def fileno(self):
|
65
|
+
fileno = self.file_handler.fileno()
|
66
|
+
return fileno
|
67
|
+
|
68
|
+
def read(self, size=-1):
|
69
|
+
"""
|
70
|
+
Read from the FIFO pipe, blocking on EOFError.
|
71
|
+
"""
|
72
|
+
_ = self.file_handler
|
73
|
+
while True:
|
74
|
+
try:
|
75
|
+
data = self._file_handler.read(size)
|
76
|
+
if data:
|
77
|
+
try:
|
78
|
+
if self.blocking_file_path.exists():
|
79
|
+
self.blocking_file_path.unlink()
|
80
|
+
except Exception:
|
81
|
+
warn(traceback.format_exc())
|
82
|
+
return data.decode('utf-8')
|
83
|
+
except (OSError, EOFError):
|
84
|
+
pass
|
85
|
+
|
86
|
+
self.blocking_file_path.touch()
|
87
|
+
time.sleep(0.1)
|
88
|
+
|
89
|
+
def readline(self, size=-1):
|
90
|
+
line = ''
|
91
|
+
while True:
|
92
|
+
data = self.read(1)
|
93
|
+
if not data or data == '\n':
|
94
|
+
break
|
95
|
+
line += data
|
96
|
+
|
97
|
+
return line
|
98
|
+
|
99
|
+
def close(self):
|
100
|
+
if self._file_handler is not None:
|
101
|
+
self.sel.unregister(self._file_handler)
|
102
|
+
self._file_handler.close()
|
103
|
+
os.close(self._fd)
|
104
|
+
self._file_handler = None
|
105
|
+
self._fd = None
|
106
|
+
|
107
|
+
super().close()
|
108
|
+
|
109
|
+
def is_open(self):
|
110
|
+
return self._file_handler is not None
|
@@ -67,19 +67,17 @@ def daemon_entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
|
|
67
67
|
if daemon.status == 'running':
|
68
68
|
return True, f"Daemon '{daemon}' is already running."
|
69
69
|
return daemon.run(
|
70
|
-
debug
|
71
|
-
allow_dirty_run
|
70
|
+
debug=debug,
|
71
|
+
allow_dirty_run=True,
|
72
72
|
)
|
73
73
|
|
74
74
|
success_tuple = run_daemon(
|
75
75
|
entry,
|
76
76
|
filtered_sysargs,
|
77
|
-
daemon_id
|
78
|
-
label
|
79
|
-
keep_daemon_output
|
77
|
+
daemon_id=_args.get('name', None) if _args else None,
|
78
|
+
label=label,
|
79
|
+
keep_daemon_output=('--rm' not in (sysargs or [])),
|
80
80
|
)
|
81
|
-
if not isinstance(success_tuple, tuple):
|
82
|
-
success_tuple = False, str(success_tuple)
|
83
81
|
return success_tuple
|
84
82
|
|
85
83
|
|
@@ -109,25 +107,25 @@ def daemon_action(**kw) -> SuccessTuple:
|
|
109
107
|
|
110
108
|
|
111
109
|
def run_daemon(
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
110
|
+
func: Callable[[Any], Any],
|
111
|
+
*args,
|
112
|
+
daemon_id: Optional[str] = None,
|
113
|
+
keep_daemon_output: bool = True,
|
114
|
+
allow_dirty_run: bool = False,
|
115
|
+
label: Optional[str] = None,
|
116
|
+
**kw
|
117
|
+
) -> Any:
|
120
118
|
"""Execute a function as a daemon."""
|
121
119
|
daemon = Daemon(
|
122
120
|
func,
|
123
|
-
daemon_id
|
124
|
-
target_args
|
125
|
-
target_kw
|
126
|
-
label
|
121
|
+
daemon_id=daemon_id,
|
122
|
+
target_args=[arg for arg in args],
|
123
|
+
target_kw=kw,
|
124
|
+
label=label,
|
127
125
|
)
|
128
126
|
return daemon.run(
|
129
|
-
keep_daemon_output
|
130
|
-
allow_dirty_run
|
127
|
+
keep_daemon_output=keep_daemon_output,
|
128
|
+
allow_dirty_run=allow_dirty_run,
|
131
129
|
)
|
132
130
|
|
133
131
|
|
@@ -183,7 +181,11 @@ def get_daemon_ids() -> List[str]:
|
|
183
181
|
"""
|
184
182
|
Return the IDs of all daemons on disk.
|
185
183
|
"""
|
186
|
-
return
|
184
|
+
return [
|
185
|
+
daemon_dir
|
186
|
+
for daemon_dir in sorted(os.listdir(DAEMON_RESOURCES_PATH))
|
187
|
+
if (DAEMON_RESOURCES_PATH / daemon_dir / 'properties.json').exists()
|
188
|
+
]
|
187
189
|
|
188
190
|
|
189
191
|
def get_running_daemons(daemons: Optional[List[Daemon]] = None) -> List[Daemon]:
|
@@ -227,10 +229,11 @@ def get_stopped_daemons(daemons: Optional[List[Daemon]] = None) -> List[Daemon]:
|
|
227
229
|
|
228
230
|
|
229
231
|
def get_filtered_daemons(
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
"""
|
232
|
+
filter_list: Optional[List[str]] = None,
|
233
|
+
warn: bool = False,
|
234
|
+
) -> List[Daemon]:
|
235
|
+
"""
|
236
|
+
Return a list of `Daemons` filtered by a list of `daemon_ids`.
|
234
237
|
Only `Daemons` that exist are returned.
|
235
238
|
|
236
239
|
If `filter_list` is `None` or empty, return all `Daemons` (from `get_daemons()`).
|
@@ -252,13 +255,14 @@ def get_filtered_daemons(
|
|
252
255
|
if not filter_list:
|
253
256
|
daemons = get_daemons()
|
254
257
|
return [d for d in daemons if not d.hidden]
|
258
|
+
|
255
259
|
from meerschaum.utils.warnings import warn as _warn
|
256
260
|
daemons = []
|
257
261
|
for d_id in filter_list:
|
258
262
|
try:
|
259
263
|
d = Daemon(daemon_id=d_id)
|
260
264
|
_exists = d.path.exists()
|
261
|
-
except Exception
|
265
|
+
except Exception:
|
262
266
|
_exists = False
|
263
267
|
if not _exists:
|
264
268
|
if warn:
|
@@ -268,3 +272,12 @@ def get_filtered_daemons(
|
|
268
272
|
pass
|
269
273
|
daemons.append(d)
|
270
274
|
return daemons
|
275
|
+
|
276
|
+
|
277
|
+
def running_in_daemon() -> bool:
|
278
|
+
"""
|
279
|
+
Return whether the current thread is running in a Daemon context.
|
280
|
+
"""
|
281
|
+
from meerschaum.config.static import STATIC_CONFIG
|
282
|
+
daemon_env_var = STATIC_CONFIG['environment']['daemon_id']
|
283
|
+
return daemon_env_var in os.environ
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
10
10
|
import platform
|
11
11
|
import os
|
12
12
|
import sys
|
13
|
+
import meerschaum as mrsm
|
13
14
|
from meerschaum.utils.typing import Optional, Union, Any, Dict
|
14
15
|
from meerschaum.utils.formatting._shell import make_header
|
15
16
|
from meerschaum.utils.formatting._pprint import pprint
|
@@ -27,12 +28,14 @@ _attrs = {
|
|
27
28
|
'ANSI': None,
|
28
29
|
'UNICODE': None,
|
29
30
|
'CHARSET': None,
|
31
|
+
'RESET': '\033[0m',
|
30
32
|
}
|
31
33
|
__all__ = sorted([
|
32
|
-
'ANSI', 'CHARSET', 'UNICODE',
|
34
|
+
'ANSI', 'CHARSET', 'UNICODE', 'RESET',
|
33
35
|
'colored',
|
34
36
|
'translate_rich_to_termcolor',
|
35
37
|
'get_console',
|
38
|
+
'format_success_tuple',
|
36
39
|
'print_tuple',
|
37
40
|
'print_options',
|
38
41
|
'fill_ansi',
|
@@ -222,16 +225,17 @@ def get_console():
|
|
222
225
|
|
223
226
|
|
224
227
|
def print_tuple(
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
228
|
+
tup: mrsm.SuccessTuple,
|
229
|
+
skip_common: bool = True,
|
230
|
+
common_only: bool = False,
|
231
|
+
upper_padding: int = 0,
|
232
|
+
lower_padding: int = 0,
|
233
|
+
left_padding: int = 1,
|
234
|
+
calm: bool = False,
|
235
|
+
_progress: Optional['rich.progress.Progress'] = None,
|
236
|
+
) -> None:
|
233
237
|
"""
|
234
|
-
|
238
|
+
Format `meerschaum.utils.typing.SuccessTuple`.
|
235
239
|
|
236
240
|
Parameters
|
237
241
|
----------
|
@@ -247,24 +251,18 @@ def print_tuple(
|
|
247
251
|
lower_padding: int, default 0
|
248
252
|
How many newlines to append to the message.
|
249
253
|
|
254
|
+
left_padding: int, default 1
|
255
|
+
How mant spaces to preprend to the message.
|
256
|
+
|
250
257
|
calm: bool, default False
|
251
258
|
If `True`, use the default emoji and color scheme.
|
259
|
+
|
252
260
|
"""
|
253
261
|
from meerschaum.config.static import STATIC_CONFIG
|
254
|
-
|
255
|
-
try:
|
256
|
-
status = 'success' if tup[0] else 'failure'
|
257
|
-
except TypeError:
|
258
|
-
status = 'failure'
|
259
|
-
tup = None, None
|
260
|
-
|
261
|
-
if calm:
|
262
|
-
status += '_calm'
|
262
|
+
do_print = True
|
263
263
|
|
264
264
|
omit_messages = STATIC_CONFIG['system']['success']['ignore']
|
265
265
|
|
266
|
-
do_print = True
|
267
|
-
|
268
266
|
if common_only:
|
269
267
|
skip_common = False
|
270
268
|
do_print = tup[1] in omit_messages
|
@@ -272,22 +270,70 @@ def print_tuple(
|
|
272
270
|
if skip_common:
|
273
271
|
do_print = tup[1] not in omit_messages
|
274
272
|
|
275
|
-
if do_print:
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
273
|
+
if not do_print:
|
274
|
+
return
|
275
|
+
|
276
|
+
print(format_success_tuple(
|
277
|
+
tup,
|
278
|
+
upper_padding=upper_padding,
|
279
|
+
lower_padding=lower_padding,
|
280
|
+
calm=calm,
|
281
|
+
_progress=_progress,
|
282
|
+
))
|
283
|
+
|
284
|
+
|
285
|
+
def format_success_tuple(
|
286
|
+
tup: mrsm.SuccessTuple,
|
287
|
+
upper_padding: int = 0,
|
288
|
+
lower_padding: int = 0,
|
289
|
+
left_padding: int = 1,
|
290
|
+
calm: bool = False,
|
291
|
+
_progress: Optional['rich.progress.Progress'] = None,
|
292
|
+
) -> str:
|
293
|
+
"""
|
294
|
+
Format `meerschaum.utils.typing.SuccessTuple`.
|
295
|
+
|
296
|
+
Parameters
|
297
|
+
----------
|
298
|
+
upper_padding: int, default 0
|
299
|
+
How many newlines to prepend to the message.
|
300
|
+
|
301
|
+
lower_padding: int, default 0
|
302
|
+
How many newlines to append to the message.
|
303
|
+
|
304
|
+
left_padding: int, default 1
|
305
|
+
How mant spaces to preprend to the message.
|
306
|
+
|
307
|
+
calm: bool, default False
|
308
|
+
If `True`, use the default emoji and color scheme.
|
309
|
+
"""
|
310
|
+
from meerschaum.config.static import STATIC_CONFIG
|
311
|
+
_init()
|
312
|
+
try:
|
313
|
+
status = 'success' if tup[0] else 'failure'
|
314
|
+
except TypeError:
|
315
|
+
status = 'failure'
|
316
|
+
tup = None, None
|
317
|
+
|
318
|
+
if calm:
|
319
|
+
status += '_calm'
|
320
|
+
|
321
|
+
ANSI, CHARSET = __getattr__('ANSI'), __getattr__('CHARSET')
|
322
|
+
from meerschaum.config import get_config
|
323
|
+
status_config = get_config('formatting', status, patch=True)
|
324
|
+
|
325
|
+
msg = (' ' * left_padding) + status_config[CHARSET]['icon'] + ' ' + str(tup[1])
|
326
|
+
lines = msg.split('\n')
|
327
|
+
lines = [lines[0]] + [
|
328
|
+
((' ' + line if not line.startswith(' ') else line))
|
329
|
+
for line in lines[1:]
|
330
|
+
]
|
331
|
+
if ANSI:
|
332
|
+
lines[0] = fill_ansi(highlight_pipes(lines[0]), **status_config['ansi']['rich'])
|
333
|
+
|
334
|
+
msg = '\n'.join(lines)
|
335
|
+
msg = ('\n' * upper_padding) + msg + ('\n' * lower_padding)
|
336
|
+
return msg
|
291
337
|
|
292
338
|
|
293
339
|
def print_options(
|