meerschaum 2.2.7__py3-none-any.whl → 2.3.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.
- meerschaum/__init__.py +6 -1
- meerschaum/__main__.py +0 -5
- meerschaum/_internal/arguments/__init__.py +1 -1
- meerschaum/_internal/arguments/_parse_arguments.py +72 -6
- meerschaum/_internal/arguments/_parser.py +45 -15
- meerschaum/_internal/docs/index.py +265 -8
- meerschaum/_internal/entry.py +154 -24
- meerschaum/_internal/shell/Shell.py +264 -77
- meerschaum/actions/__init__.py +29 -17
- meerschaum/actions/api.py +12 -12
- meerschaum/actions/attach.py +113 -0
- meerschaum/actions/copy.py +68 -41
- meerschaum/actions/delete.py +112 -50
- meerschaum/actions/edit.py +3 -3
- meerschaum/actions/install.py +40 -32
- meerschaum/actions/pause.py +44 -27
- meerschaum/actions/restart.py +107 -0
- meerschaum/actions/show.py +130 -159
- meerschaum/actions/start.py +161 -100
- meerschaum/actions/stop.py +78 -42
- meerschaum/api/_events.py +25 -1
- meerschaum/api/_oauth2.py +2 -0
- meerschaum/api/_websockets.py +2 -2
- meerschaum/api/dash/callbacks/jobs.py +36 -44
- meerschaum/api/dash/jobs.py +89 -78
- meerschaum/api/routes/__init__.py +1 -0
- meerschaum/api/routes/_actions.py +148 -17
- meerschaum/api/routes/_jobs.py +407 -0
- meerschaum/api/routes/_pipes.py +5 -5
- meerschaum/config/_default.py +1 -0
- meerschaum/config/_jobs.py +1 -1
- meerschaum/config/_paths.py +7 -0
- meerschaum/config/_shell.py +8 -3
- meerschaum/config/_version.py +1 -1
- meerschaum/config/static/__init__.py +17 -0
- meerschaum/connectors/Connector.py +13 -7
- meerschaum/connectors/__init__.py +28 -15
- meerschaum/connectors/api/APIConnector.py +27 -1
- meerschaum/connectors/api/_actions.py +71 -6
- meerschaum/connectors/api/_jobs.py +368 -0
- meerschaum/connectors/api/_pipes.py +85 -84
- meerschaum/connectors/parse.py +27 -15
- meerschaum/core/Pipe/_bootstrap.py +16 -8
- meerschaum/jobs/_Executor.py +69 -0
- meerschaum/jobs/_Job.py +899 -0
- meerschaum/jobs/__init__.py +396 -0
- meerschaum/jobs/systemd.py +694 -0
- meerschaum/plugins/__init__.py +97 -12
- meerschaum/utils/daemon/Daemon.py +276 -30
- meerschaum/utils/daemon/FileDescriptorInterceptor.py +5 -5
- meerschaum/utils/daemon/RotatingFile.py +14 -7
- meerschaum/utils/daemon/StdinFile.py +121 -0
- meerschaum/utils/daemon/__init__.py +15 -7
- meerschaum/utils/daemon/_names.py +15 -13
- meerschaum/utils/formatting/__init__.py +2 -1
- meerschaum/utils/formatting/_jobs.py +115 -62
- meerschaum/utils/formatting/_shell.py +6 -0
- meerschaum/utils/misc.py +41 -22
- meerschaum/utils/packages/_packages.py +9 -6
- meerschaum/utils/process.py +9 -9
- meerschaum/utils/prompt.py +16 -8
- meerschaum/utils/venv/__init__.py +2 -2
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/METADATA +22 -25
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/RECORD +70 -61
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/WHEEL +1 -1
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/LICENSE +0 -0
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/NOTICE +0 -0
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/top_level.txt +0 -0
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/zip-safe +0 -0
@@ -112,7 +112,7 @@ class FileDescriptorInterceptor:
|
|
112
112
|
except OSError as e:
|
113
113
|
if e.errno != FD_CLOSED:
|
114
114
|
warn(
|
115
|
-
|
115
|
+
"Error while trying to close the duplicated file descriptor:\n"
|
116
116
|
+ f"{traceback.format_exc()}"
|
117
117
|
)
|
118
118
|
|
@@ -121,7 +121,7 @@ class FileDescriptorInterceptor:
|
|
121
121
|
except OSError as e:
|
122
122
|
if e.errno != FD_CLOSED:
|
123
123
|
warn(
|
124
|
-
|
124
|
+
"Error while trying to close the write-pipe "
|
125
125
|
+ "to the intercepted file descriptor:\n"
|
126
126
|
+ f"{traceback.format_exc()}"
|
127
127
|
)
|
@@ -130,7 +130,7 @@ class FileDescriptorInterceptor:
|
|
130
130
|
except OSError as e:
|
131
131
|
if e.errno != FD_CLOSED:
|
132
132
|
warn(
|
133
|
-
|
133
|
+
"Error while trying to close the read-pipe "
|
134
134
|
+ "to the intercepted file descriptor:\n"
|
135
135
|
+ f"{traceback.format_exc()}"
|
136
136
|
)
|
@@ -140,7 +140,7 @@ class FileDescriptorInterceptor:
|
|
140
140
|
except OSError as e:
|
141
141
|
if e.errno != FD_CLOSED:
|
142
142
|
warn(
|
143
|
-
|
143
|
+
"Error while trying to close the signal-read-pipe "
|
144
144
|
+ "to the intercepted file descriptor:\n"
|
145
145
|
+ f"{traceback.format_exc()}"
|
146
146
|
)
|
@@ -150,7 +150,7 @@ class FileDescriptorInterceptor:
|
|
150
150
|
except OSError as e:
|
151
151
|
if e.errno != FD_CLOSED:
|
152
152
|
warn(
|
153
|
-
|
153
|
+
"Error while trying to close the signal-write-pipe "
|
154
154
|
+ "to the intercepted file descriptor:\n"
|
155
155
|
+ f"{traceback.format_exc()}"
|
156
156
|
)
|
@@ -38,7 +38,7 @@ class RotatingFile(io.IOBase):
|
|
38
38
|
max_file_size: Optional[int] = None,
|
39
39
|
redirect_streams: bool = False,
|
40
40
|
write_timestamps: bool = False,
|
41
|
-
timestamp_format: str =
|
41
|
+
timestamp_format: Optional[str] = None,
|
42
42
|
):
|
43
43
|
"""
|
44
44
|
Create a file-like object which manages other files.
|
@@ -63,12 +63,18 @@ 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 None
|
68
|
+
If `write_timestamps` is `True`, use this format for the timestamps.
|
69
|
+
Defaults to `'%Y-%m-%d %H:%M'`.
|
66
70
|
"""
|
67
71
|
self.file_path = pathlib.Path(file_path)
|
68
72
|
if num_files_to_keep is None:
|
69
73
|
num_files_to_keep = get_config('jobs', 'logs', 'num_files_to_keep')
|
70
74
|
if max_file_size is None:
|
71
75
|
max_file_size = get_config('jobs', 'logs', 'max_file_size')
|
76
|
+
if timestamp_format is None:
|
77
|
+
timestamp_format = get_config('jobs', 'logs', 'timestamps', 'format')
|
72
78
|
if num_files_to_keep < 2:
|
73
79
|
raise ValueError("At least 2 files must be kept.")
|
74
80
|
if max_file_size < 1:
|
@@ -232,10 +238,10 @@ class RotatingFile(io.IOBase):
|
|
232
238
|
|
233
239
|
|
234
240
|
def refresh_files(
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
241
|
+
self,
|
242
|
+
potential_new_len: int = 0,
|
243
|
+
start_interception: bool = False,
|
244
|
+
) -> '_io.TextUIWrapper':
|
239
245
|
"""
|
240
246
|
Check the state of the subfiles.
|
241
247
|
If the latest subfile is too large, create a new file and delete old ones.
|
@@ -339,7 +345,7 @@ class RotatingFile(io.IOBase):
|
|
339
345
|
|
340
346
|
def get_timestamp_prefix_str(self) -> str:
|
341
347
|
"""
|
342
|
-
Return the current minute
|
348
|
+
Return the current minute prefix string.
|
343
349
|
"""
|
344
350
|
return datetime.now(timezone.utc).strftime(self.timestamp_format) + ' | '
|
345
351
|
|
@@ -568,7 +574,8 @@ class RotatingFile(io.IOBase):
|
|
568
574
|
return
|
569
575
|
|
570
576
|
self._cursor = (max_ix, position)
|
571
|
-
self._current_file_obj
|
577
|
+
if self._current_file_obj is not None:
|
578
|
+
self._current_file_obj.seek(position)
|
572
579
|
|
573
580
|
|
574
581
|
def flush(self) -> None:
|
@@ -0,0 +1,121 @@
|
|
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, Union
|
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: Union[pathlib.Path, str],
|
27
|
+
lock_file_path: Optional[pathlib.Path] = None,
|
28
|
+
):
|
29
|
+
if isinstance(file_path, str):
|
30
|
+
file_path = pathlib.Path(file_path)
|
31
|
+
|
32
|
+
self.file_path = file_path
|
33
|
+
self.blocking_file_path = (
|
34
|
+
lock_file_path
|
35
|
+
if lock_file_path is not None
|
36
|
+
else (file_path.parent / (file_path.name + '.block'))
|
37
|
+
)
|
38
|
+
self._file_handler = None
|
39
|
+
self._fd = None
|
40
|
+
self.sel = selectors.DefaultSelector()
|
41
|
+
|
42
|
+
@property
|
43
|
+
def file_handler(self):
|
44
|
+
"""
|
45
|
+
Return the read file handler to the provided file path.
|
46
|
+
"""
|
47
|
+
if self._file_handler is not None:
|
48
|
+
return self._file_handler
|
49
|
+
|
50
|
+
if self.file_path.exists():
|
51
|
+
self.file_path.unlink()
|
52
|
+
|
53
|
+
self.file_path.parent.mkdir(parents=True, exist_ok=True)
|
54
|
+
os.mkfifo(self.file_path.as_posix(), mode=0o600)
|
55
|
+
|
56
|
+
self._fd = os.open(self.file_path, os.O_RDONLY | os.O_NONBLOCK)
|
57
|
+
self._file_handler = os.fdopen(self._fd, 'rb', buffering=0)
|
58
|
+
self.sel.register(self._file_handler, selectors.EVENT_READ)
|
59
|
+
return self._file_handler
|
60
|
+
|
61
|
+
def write(self, data):
|
62
|
+
if isinstance(data, str):
|
63
|
+
data = data.encode('utf-8')
|
64
|
+
|
65
|
+
with open(self.file_path, 'wb') as f:
|
66
|
+
f.write(data)
|
67
|
+
|
68
|
+
def fileno(self):
|
69
|
+
fileno = self.file_handler.fileno()
|
70
|
+
return fileno
|
71
|
+
|
72
|
+
def read(self, size=-1):
|
73
|
+
"""
|
74
|
+
Read from the FIFO pipe, blocking on EOFError.
|
75
|
+
"""
|
76
|
+
_ = self.file_handler
|
77
|
+
while True:
|
78
|
+
try:
|
79
|
+
data = self._file_handler.read(size)
|
80
|
+
if data:
|
81
|
+
try:
|
82
|
+
if self.blocking_file_path.exists():
|
83
|
+
self.blocking_file_path.unlink()
|
84
|
+
except Exception:
|
85
|
+
warn(traceback.format_exc())
|
86
|
+
return data.decode('utf-8')
|
87
|
+
except (OSError, EOFError):
|
88
|
+
pass
|
89
|
+
|
90
|
+
self.blocking_file_path.touch()
|
91
|
+
time.sleep(0.1)
|
92
|
+
|
93
|
+
def readline(self, size=-1):
|
94
|
+
line = ''
|
95
|
+
while True:
|
96
|
+
data = self.read(1)
|
97
|
+
if not data or data == '\n':
|
98
|
+
break
|
99
|
+
line += data
|
100
|
+
|
101
|
+
return line
|
102
|
+
|
103
|
+
def close(self):
|
104
|
+
if self._file_handler is not None:
|
105
|
+
self.sel.unregister(self._file_handler)
|
106
|
+
self._file_handler.close()
|
107
|
+
os.close(self._fd)
|
108
|
+
self._file_handler = None
|
109
|
+
self._fd = None
|
110
|
+
|
111
|
+
super().close()
|
112
|
+
|
113
|
+
def is_open(self):
|
114
|
+
return self._file_handler is not None
|
115
|
+
|
116
|
+
|
117
|
+
def __str__(self) -> str:
|
118
|
+
return f"StdinFile('{self.file_path}')"
|
119
|
+
|
120
|
+
def __repr__(self) -> str:
|
121
|
+
return str(self)
|
@@ -10,9 +10,11 @@ from __future__ import annotations
|
|
10
10
|
import os, pathlib, shutil, json, datetime, threading, shlex
|
11
11
|
from meerschaum.utils.typing import SuccessTuple, List, Optional, Callable, Any, Dict
|
12
12
|
from meerschaum.config._paths import DAEMON_RESOURCES_PATH
|
13
|
+
from meerschaum.utils.daemon.StdinFile import StdinFile
|
13
14
|
from meerschaum.utils.daemon.Daemon import Daemon
|
14
15
|
from meerschaum.utils.daemon.RotatingFile import RotatingFile
|
15
16
|
from meerschaum.utils.daemon.FileDescriptorInterceptor import FileDescriptorInterceptor
|
17
|
+
from meerschaum.utils.daemon._names import get_new_daemon_name
|
16
18
|
|
17
19
|
|
18
20
|
def daemon_entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
|
@@ -76,7 +78,7 @@ def daemon_entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
|
|
76
78
|
filtered_sysargs,
|
77
79
|
daemon_id=_args.get('name', None) if _args else None,
|
78
80
|
label=label,
|
79
|
-
keep_daemon_output=('--rm' not in sysargs),
|
81
|
+
keep_daemon_output=('--rm' not in (sysargs or [])),
|
80
82
|
)
|
81
83
|
return success_tuple
|
82
84
|
|
@@ -181,7 +183,11 @@ def get_daemon_ids() -> List[str]:
|
|
181
183
|
"""
|
182
184
|
Return the IDs of all daemons on disk.
|
183
185
|
"""
|
184
|
-
return
|
186
|
+
return [
|
187
|
+
daemon_dir
|
188
|
+
for daemon_dir in sorted(os.listdir(DAEMON_RESOURCES_PATH))
|
189
|
+
if (DAEMON_RESOURCES_PATH / daemon_dir / 'properties.json').exists()
|
190
|
+
]
|
185
191
|
|
186
192
|
|
187
193
|
def get_running_daemons(daemons: Optional[List[Daemon]] = None) -> List[Daemon]:
|
@@ -225,10 +231,11 @@ def get_stopped_daemons(daemons: Optional[List[Daemon]] = None) -> List[Daemon]:
|
|
225
231
|
|
226
232
|
|
227
233
|
def get_filtered_daemons(
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
"""
|
234
|
+
filter_list: Optional[List[str]] = None,
|
235
|
+
warn: bool = False,
|
236
|
+
) -> List[Daemon]:
|
237
|
+
"""
|
238
|
+
Return a list of `Daemons` filtered by a list of `daemon_ids`.
|
232
239
|
Only `Daemons` that exist are returned.
|
233
240
|
|
234
241
|
If `filter_list` is `None` or empty, return all `Daemons` (from `get_daemons()`).
|
@@ -250,13 +257,14 @@ def get_filtered_daemons(
|
|
250
257
|
if not filter_list:
|
251
258
|
daemons = get_daemons()
|
252
259
|
return [d for d in daemons if not d.hidden]
|
260
|
+
|
253
261
|
from meerschaum.utils.warnings import warn as _warn
|
254
262
|
daemons = []
|
255
263
|
for d_id in filter_list:
|
256
264
|
try:
|
257
265
|
d = Daemon(daemon_id=d_id)
|
258
266
|
_exists = d.path.exists()
|
259
|
-
except Exception
|
267
|
+
except Exception:
|
260
268
|
_exists = False
|
261
269
|
if not _exists:
|
262
270
|
if warn:
|
@@ -18,7 +18,8 @@ _bank: Dict[str, Dict[str, List[str]]] = {
|
|
18
18
|
'bright', 'dark', 'neon',
|
19
19
|
],
|
20
20
|
'sizes': [
|
21
|
-
'big', 'small', 'large', 'huge', 'tiny', 'long', 'short', '
|
21
|
+
'big', 'small', 'large', 'huge', 'tiny', 'long', 'short', 'average', 'mini', 'micro',
|
22
|
+
'maximum', 'minimum', 'median',
|
22
23
|
],
|
23
24
|
'personalities': [
|
24
25
|
'groovy', 'cool', 'awesome', 'nice', 'fantastic', 'sweet', 'great', 'amazing',
|
@@ -26,29 +27,36 @@ _bank: Dict[str, Dict[str, List[str]]] = {
|
|
26
27
|
],
|
27
28
|
'emotions': [
|
28
29
|
'angry', 'happy', 'excited', 'suspicious', 'sad', 'thankful', 'grateful', 'satisfied',
|
30
|
+
'peaceful', 'ferocious', 'content',
|
29
31
|
],
|
30
32
|
'sensations': [
|
31
33
|
'sleepy', 'awake', 'alert', 'thirsty', 'comfy', 'warm', 'cold', 'chilly', 'soft',
|
32
|
-
'smooth', 'chunky',
|
34
|
+
'smooth', 'chunky', 'hungry',
|
33
35
|
],
|
34
36
|
'materials': [
|
35
37
|
'golden', 'silver', 'metal', 'plastic', 'wool', 'wooden', 'nylon', 'fuzzy', 'silky',
|
38
|
+
'suede', 'vinyl',
|
36
39
|
],
|
37
40
|
'qualities': [
|
38
41
|
'expensive', 'cheap', 'premier', 'best', 'favorite', 'better', 'good', 'affordable',
|
42
|
+
'organic', 'electric',
|
39
43
|
],
|
40
44
|
},
|
41
45
|
'nouns' : {
|
42
46
|
'animals': [
|
43
|
-
'mouse', 'fox', 'horse', '
|
47
|
+
'mouse', 'fox', 'horse', 'pig', 'hippo', 'elephant' , 'tiger', 'deer', 'salmon',
|
44
48
|
'gerbil', 'snake', 'turtle', 'rhino', 'dog', 'cat', 'giraffe', 'rabbit', 'squirrel',
|
45
49
|
'unicorn', 'lizard', 'lion', 'bear', 'gazelle', 'whale', 'dolphin', 'fish', 'butterfly',
|
46
50
|
'ladybug', 'fly', 'shrimp', 'flamingo', 'parrot', 'tuna', 'panda', 'lemur', 'duck',
|
47
51
|
'seal', 'walrus', 'seagull', 'iguana', 'salamander', 'kitten', 'puppy', 'octopus',
|
48
52
|
],
|
53
|
+
'weather': [
|
54
|
+
'rain', 'sun', 'snow', 'wind', 'tornado', 'hurricane', 'blizzard', 'monsoon', 'storm',
|
55
|
+
'shower', 'hail',
|
56
|
+
],
|
49
57
|
'plants': [
|
50
58
|
'tree', 'flower', 'vine', 'fern', 'palm', 'palmetto', 'oak', 'pine', 'rose', 'lily',
|
51
|
-
'ivy',
|
59
|
+
'ivy', 'leaf', 'shrubbery', 'acorn', 'fruit',
|
52
60
|
],
|
53
61
|
'foods': [
|
54
62
|
'pizza', 'sushi', 'apple', 'banana', 'sandwich', 'burger', 'taco', 'bratwurst',
|
@@ -74,8 +82,6 @@ _bank: Dict[str, Dict[str, List[str]]] = {
|
|
74
82
|
},
|
75
83
|
}
|
76
84
|
|
77
|
-
_disallow_combinations: List[Tuple[str, str]] = []
|
78
|
-
|
79
85
|
_adjectives: List[str]= []
|
80
86
|
for category, items in _bank['adjectives'].items():
|
81
87
|
_adjectives += items
|
@@ -84,7 +90,7 @@ _nouns: List[str] = []
|
|
84
90
|
for category, items in _bank['nouns'].items():
|
85
91
|
_nouns += items
|
86
92
|
|
87
|
-
def generate_random_name(separator: str = '
|
93
|
+
def generate_random_name(separator: str = '-'):
|
88
94
|
"""
|
89
95
|
Return a random adjective and noun combination.
|
90
96
|
|
@@ -96,12 +102,8 @@ def generate_random_name(separator: str = '_'):
|
|
96
102
|
-------
|
97
103
|
A string containing an random adjective and random noun.
|
98
104
|
"""
|
99
|
-
|
100
|
-
|
101
|
-
noun_category = random.choice(list(_bank['nouns'].keys()))
|
102
|
-
if (adjective_category, noun_category) in _disallow_combinations:
|
103
|
-
continue
|
104
|
-
break
|
105
|
+
adjective_category = random.choice(list(_bank['adjectives'].keys()))
|
106
|
+
noun_category = random.choice(list(_bank['nouns'].keys()))
|
105
107
|
return (
|
106
108
|
random.choice(_bank['adjectives'][adjective_category])
|
107
109
|
+ separator
|
@@ -28,9 +28,10 @@ _attrs = {
|
|
28
28
|
'ANSI': None,
|
29
29
|
'UNICODE': None,
|
30
30
|
'CHARSET': None,
|
31
|
+
'RESET': '\033[0m',
|
31
32
|
}
|
32
33
|
__all__ = sorted([
|
33
|
-
'ANSI', 'CHARSET', 'UNICODE',
|
34
|
+
'ANSI', 'CHARSET', 'UNICODE', 'RESET',
|
34
35
|
'colored',
|
35
36
|
'translate_rich_to_termcolor',
|
36
37
|
'get_console',
|
@@ -7,46 +7,59 @@ Print jobs information.
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
|
+
|
11
|
+
from datetime import datetime, timezone
|
12
|
+
|
10
13
|
import meerschaum as mrsm
|
11
|
-
from meerschaum.utils.typing import List, Optional, Any, is_success_tuple
|
12
|
-
from meerschaum.
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
from meerschaum.utils.typing import List, Optional, Any, is_success_tuple, Dict
|
15
|
+
from meerschaum.jobs import (
|
16
|
+
Job,
|
17
|
+
get_jobs,
|
18
|
+
get_running_jobs,
|
19
|
+
get_stopped_jobs,
|
20
|
+
get_paused_jobs,
|
21
|
+
get_executor_keys_from_context,
|
18
22
|
)
|
23
|
+
from meerschaum.config import get_config
|
24
|
+
|
19
25
|
|
20
26
|
def pprint_jobs(
|
21
|
-
|
27
|
+
jobs: Dict[str, Job],
|
22
28
|
nopretty: bool = False,
|
23
29
|
):
|
24
30
|
"""Pretty-print a list of Daemons."""
|
25
31
|
from meerschaum.utils.formatting import make_header
|
32
|
+
from meerschaum.utils.misc import items_str
|
26
33
|
|
27
|
-
|
28
|
-
|
29
|
-
|
34
|
+
running_jobs = get_running_jobs(jobs=jobs)
|
35
|
+
paused_jobs = get_paused_jobs(jobs=jobs)
|
36
|
+
stopped_jobs = get_stopped_jobs(jobs=jobs)
|
37
|
+
executor_keys_list = list(set(
|
38
|
+
[
|
39
|
+
job.executor_keys.replace('systemd:main', 'systemd')
|
40
|
+
for job in jobs.values()
|
41
|
+
]
|
42
|
+
)) if jobs else [get_executor_keys_from_context()]
|
30
43
|
|
31
44
|
def _nopretty_print():
|
32
45
|
from meerschaum.utils.misc import print_options
|
33
|
-
if
|
46
|
+
if running_jobs:
|
34
47
|
if not nopretty:
|
35
48
|
print('\n' + make_header('Running jobs'))
|
36
|
-
for
|
37
|
-
pprint_job(
|
49
|
+
for name, job in running_jobs.items():
|
50
|
+
pprint_job(job, nopretty=nopretty)
|
38
51
|
|
39
|
-
if
|
52
|
+
if paused_jobs:
|
40
53
|
if not nopretty:
|
41
54
|
print('\n' + make_header('Paused jobs'))
|
42
|
-
for
|
43
|
-
pprint_job(
|
55
|
+
for name, job in paused_jobs.items():
|
56
|
+
pprint_job(job, nopretty=nopretty)
|
44
57
|
|
45
|
-
if
|
58
|
+
if stopped_jobs:
|
46
59
|
if not nopretty:
|
47
60
|
print('\n' + make_header('Stopped jobs'))
|
48
|
-
for
|
49
|
-
pprint_job(
|
61
|
+
for name, job in stopped_jobs.items():
|
62
|
+
pprint_job(job, nopretty=nopretty)
|
50
63
|
|
51
64
|
def _pretty_print():
|
52
65
|
from meerschaum.utils.formatting import get_console, UNICODE, ANSI, format_success_tuple
|
@@ -56,19 +69,25 @@ def pprint_jobs(
|
|
56
69
|
'rich.table', 'rich.text', 'rich.box', 'rich.json', 'rich.panel', 'rich.console',
|
57
70
|
)
|
58
71
|
table = rich_table.Table(
|
59
|
-
title
|
60
|
-
|
61
|
-
|
62
|
-
|
72
|
+
title=rich_text.Text(
|
73
|
+
f"\nJobs on Executor"
|
74
|
+
+ ('s' if len(executor_keys_list) != 1 else '')
|
75
|
+
+ f" {items_str(executor_keys_list)}"
|
76
|
+
),
|
77
|
+
box=(rich_box.ROUNDED if UNICODE else rich_box.ASCII),
|
78
|
+
show_lines=True,
|
79
|
+
show_header=ANSI,
|
63
80
|
)
|
64
81
|
table.add_column("Name", justify='right', style=('magenta' if ANSI else ''))
|
82
|
+
table.add_column(
|
83
|
+
"Executor",
|
84
|
+
style=(get_config('shell', 'ansi', 'executor', 'rich', 'style') if ANSI else ''),
|
85
|
+
)
|
65
86
|
table.add_column("Command")
|
66
87
|
table.add_column("Status")
|
67
88
|
|
68
|
-
def get_success_text(
|
69
|
-
success_tuple =
|
70
|
-
if isinstance(success_tuple, list):
|
71
|
-
success_tuple = tuple(success_tuple)
|
89
|
+
def get_success_text(job):
|
90
|
+
success_tuple = job.result
|
72
91
|
if not is_success_tuple(success_tuple):
|
73
92
|
return rich_text.Text('')
|
74
93
|
|
@@ -92,39 +111,56 @@ def pprint_jobs(
|
|
92
111
|
return rich_text.Text('\n') + success_tuple_text
|
93
112
|
|
94
113
|
|
95
|
-
for
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
rich_console.Group(
|
102
|
-
rich_text.Text(d.status, style=('green' if ANSI else '')),
|
114
|
+
for name, job in running_jobs.items():
|
115
|
+
status_group = [
|
116
|
+
(
|
117
|
+
rich_text.Text(job.status, style=('green' if ANSI else ''))
|
118
|
+
if not job.is_blocking_on_stdin()
|
119
|
+
else rich_text.Text('waiting for input', style=('yellow' if ANSI else ''))
|
103
120
|
),
|
121
|
+
] + ([rich_text.Text(f"PID: {pid}")] if (pid := job.pid) else [])
|
122
|
+
|
123
|
+
if job.restart:
|
124
|
+
status_group.append(rich_text.Text('(restarts)'))
|
125
|
+
|
126
|
+
table.add_row(
|
127
|
+
job.name,
|
128
|
+
job.executor_keys.replace('systemd:main', 'systemd'),
|
129
|
+
job.label,
|
130
|
+
rich_console.Group(*status_group),
|
104
131
|
)
|
105
132
|
|
106
|
-
for
|
107
|
-
|
108
|
-
|
133
|
+
for name, job in paused_jobs.items():
|
134
|
+
status_group = [
|
135
|
+
rich_text.Text(job.status, style=('yellow' if ANSI else '')),
|
136
|
+
]
|
137
|
+
if job.restart:
|
138
|
+
status_group.append(rich_text.Text('(restarts)'))
|
139
|
+
|
109
140
|
table.add_row(
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
),
|
141
|
+
job.name,
|
142
|
+
job.executor_keys.replace('systemd:main', 'systemd'),
|
143
|
+
job.label,
|
144
|
+
rich_console.Group(*status_group),
|
115
145
|
)
|
116
146
|
|
117
|
-
for
|
118
|
-
|
119
|
-
|
147
|
+
for name, job in stopped_jobs.items():
|
148
|
+
status_group = [
|
149
|
+
rich_text.Text(job.status, style=('red' if ANSI else '')),
|
150
|
+
]
|
151
|
+
if job.restart:
|
152
|
+
if job.stop_time is None:
|
153
|
+
status_group.append(rich_text.Text('(restarts)'))
|
154
|
+
else:
|
155
|
+
status_group.append(rich_text.Text('(start to resume restarts)'))
|
156
|
+
|
157
|
+
status_group.append(get_success_text(job))
|
120
158
|
|
121
159
|
table.add_row(
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
get_success_text(d)
|
127
|
-
),
|
160
|
+
job.name,
|
161
|
+
job.executor_keys.replace('systemd:main', 'systemd'),
|
162
|
+
job.label,
|
163
|
+
rich_console.Group(*status_group),
|
128
164
|
)
|
129
165
|
get_console().print(table)
|
130
166
|
|
@@ -133,15 +169,32 @@ def pprint_jobs(
|
|
133
169
|
|
134
170
|
|
135
171
|
def pprint_job(
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
"""Pretty-print a single
|
140
|
-
if daemon.hidden:
|
141
|
-
return
|
172
|
+
job: Job,
|
173
|
+
nopretty: bool = False,
|
174
|
+
):
|
175
|
+
"""Pretty-print a single `Job`."""
|
142
176
|
from meerschaum.utils.warnings import info
|
143
177
|
if not nopretty:
|
144
|
-
info(f"Command for job '{
|
145
|
-
print('\n' +
|
178
|
+
info(f"Command for job '{job.name}':")
|
179
|
+
print('\n' + job.label + '\n')
|
146
180
|
else:
|
147
|
-
print(
|
181
|
+
print(job.name)
|
182
|
+
|
183
|
+
|
184
|
+
def strip_timestamp_from_line(line: str) -> str:
|
185
|
+
"""
|
186
|
+
Remove the leading timestamp from a job's line (if present).
|
187
|
+
"""
|
188
|
+
now = datetime.now(timezone.utc)
|
189
|
+
timestamp_format = get_config('jobs', 'logs', 'timestamps', 'format')
|
190
|
+
now_str = now.strftime(timestamp_format)
|
191
|
+
|
192
|
+
date_prefix_str = line[:len(now_str)]
|
193
|
+
try:
|
194
|
+
line_timestamp = datetime.strptime(date_prefix_str, timestamp_format)
|
195
|
+
except Exception:
|
196
|
+
line_timestamp = None
|
197
|
+
if line_timestamp:
|
198
|
+
line = line[(len(now_str) + 3):]
|
199
|
+
|
200
|
+
return line
|
@@ -47,9 +47,15 @@ def clear_screen(debug: bool = False) -> bool:
|
|
47
47
|
from meerschaum.utils.formatting import ANSI, get_console
|
48
48
|
from meerschaum.utils.debug import dprint
|
49
49
|
from meerschaum.config import get_config
|
50
|
+
from meerschaum.utils.daemon import running_in_daemon
|
50
51
|
global _tried_clear_command
|
52
|
+
|
53
|
+
if running_in_daemon():
|
54
|
+
return True
|
55
|
+
|
51
56
|
if not get_config('shell', 'clear_screen'):
|
52
57
|
return True
|
58
|
+
|
53
59
|
print("", end="", flush=True)
|
54
60
|
if debug:
|
55
61
|
dprint("Skipping screen clear.")
|