richqueue 1.8__tar.gz → 1.11__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.
- richqueue-1.11/PKG-INFO +116 -0
- richqueue-1.11/README.md +99 -0
- {richqueue-1.8 → richqueue-1.11}/pyproject.toml +2 -2
- richqueue-1.11/richqueue/job.py +59 -0
- {richqueue-1.8 → richqueue-1.11}/richqueue/log.py +32 -66
- {richqueue-1.8 → richqueue-1.11}/richqueue/main.py +26 -25
- richqueue-1.11/richqueue/submit.py +54 -0
- richqueue-1.8/PKG-INFO +0 -57
- richqueue-1.8/README.md +0 -40
- richqueue-1.8/richqueue/quotes_encrypted.py +0 -1
- richqueue-1.8/richqueue/wisdom.py +0 -112
- {richqueue-1.8 → richqueue-1.11}/.gitignore +0 -0
- {richqueue-1.8 → richqueue-1.11}/LICENSE +0 -0
- {richqueue-1.8 → richqueue-1.11}/richqueue/__init__.py +0 -0
- {richqueue-1.8 → richqueue-1.11}/richqueue/console.py +0 -0
- {richqueue-1.8 → richqueue-1.11}/richqueue/layout.py +0 -0
- {richqueue-1.8 → richqueue-1.11}/richqueue/slurm.py +0 -0
- {richqueue-1.8 → richqueue-1.11}/richqueue/table.py +0 -0
- {richqueue-1.8 → richqueue-1.11}/richqueue/tools.py +0 -0
richqueue-1.11/PKG-INFO
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: richqueue
|
|
3
|
+
Version: 1.11
|
|
4
|
+
Summary: RichQueue
|
|
5
|
+
Project-URL: Homepage, https://github.com/mwinokan/RichQueue/issues
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/mwinokan/RichQueue/issues
|
|
7
|
+
Author-email: Max Winokan <max@winokan.com>
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Requires-Dist: pandas
|
|
14
|
+
Requires-Dist: rich
|
|
15
|
+
Requires-Dist: typer
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# RichQueue
|
|
19
|
+
|
|
20
|
+
> 💰 RichQueue: A colourful and pythonic SLURM queue viewer
|
|
21
|
+
|
|
22
|
+
Installation from a modern python environment (>3.10) should be as simple as:
|
|
23
|
+
|
|
24
|
+
```shell
|
|
25
|
+
|
|
26
|
+
pip install --upgrade richqueue
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
To see your live-updating SLURM queue:
|
|
30
|
+
|
|
31
|
+
```shell
|
|
32
|
+
rq
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
## Other `rq` options
|
|
38
|
+
|
|
39
|
+
To see more detail:
|
|
40
|
+
|
|
41
|
+
```rq --long```
|
|
42
|
+
|
|
43
|
+
To see someone else's queue:
|
|
44
|
+
|
|
45
|
+
```rq --user USER```
|
|
46
|
+
|
|
47
|
+
To see the last `X` weeks history:
|
|
48
|
+
|
|
49
|
+
```rq --hist X```
|
|
50
|
+
|
|
51
|
+
To see history for a given time period, e.g.:
|
|
52
|
+
|
|
53
|
+
```rq --hist '3 days'```
|
|
54
|
+
|
|
55
|
+
To list available nodes on the cluster:
|
|
56
|
+
|
|
57
|
+
```rq --idle```
|
|
58
|
+
|
|
59
|
+
To show a static view:
|
|
60
|
+
|
|
61
|
+
```rq --no-loop```
|
|
62
|
+
|
|
63
|
+
## Monitoring log files
|
|
64
|
+
|
|
65
|
+
If you keep your SLURM log files in a specific directory exported as the `LOGS` variable, you can use RichQueue to monitor results as they come in with
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
res <JOB_ID>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
<img width="569" height="737" alt="Screenshot 2025-11-20 at 09 05 48" src="https://github.com/user-attachments/assets/1b0f6457-d769-4a1a-b331-bb7f121b5864" />
|
|
72
|
+
|
|
73
|
+
## Changing to the working directory of a job
|
|
74
|
+
|
|
75
|
+
To change to the submission/working directory of a job:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
cd $(res --dir <JOB_ID>)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
You might find it convenient to add a shortcut to this to your login profile, e.g. `.bashrc_user` or `.bash_profile`:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
res --install-jd >> ~/.bashrc_user
|
|
85
|
+
source ~/.bashrc_user
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
This will make the `jd` executable available. Change to the directory of the single active or most recently submitted job:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
jd
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Change to a specific job's directory:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
jd <JOB_ID>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Submitting SLURM jobs
|
|
101
|
+
|
|
102
|
+
RichQueue also provides a convenient wrapper to the `sbatch` command which makes sure your log files end up in the directory specified by the `LOGS` variable, and prints a pretty summary of the submitted job. Just replace `sbatch` with `sb`. E.g.:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
sb --job-name test script.sh --script-arg1
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Would run this under the hood:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
sbatch --output=$LOGS/%j.log --error=$LOGS/%j.log --job-name test script.sh --script-arg1
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
and display the following:
|
|
115
|
+
|
|
116
|
+
<img width="474" height="255" alt="Screenshot 2025-11-20 at 10 37 19" src="https://github.com/user-attachments/assets/4464f813-841a-45bd-864e-00dd3dabf924" />
|
richqueue-1.11/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# RichQueue
|
|
2
|
+
|
|
3
|
+
> 💰 RichQueue: A colourful and pythonic SLURM queue viewer
|
|
4
|
+
|
|
5
|
+
Installation from a modern python environment (>3.10) should be as simple as:
|
|
6
|
+
|
|
7
|
+
```shell
|
|
8
|
+
|
|
9
|
+
pip install --upgrade richqueue
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
To see your live-updating SLURM queue:
|
|
13
|
+
|
|
14
|
+
```shell
|
|
15
|
+
rq
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
## Other `rq` options
|
|
21
|
+
|
|
22
|
+
To see more detail:
|
|
23
|
+
|
|
24
|
+
```rq --long```
|
|
25
|
+
|
|
26
|
+
To see someone else's queue:
|
|
27
|
+
|
|
28
|
+
```rq --user USER```
|
|
29
|
+
|
|
30
|
+
To see the last `X` weeks history:
|
|
31
|
+
|
|
32
|
+
```rq --hist X```
|
|
33
|
+
|
|
34
|
+
To see history for a given time period, e.g.:
|
|
35
|
+
|
|
36
|
+
```rq --hist '3 days'```
|
|
37
|
+
|
|
38
|
+
To list available nodes on the cluster:
|
|
39
|
+
|
|
40
|
+
```rq --idle```
|
|
41
|
+
|
|
42
|
+
To show a static view:
|
|
43
|
+
|
|
44
|
+
```rq --no-loop```
|
|
45
|
+
|
|
46
|
+
## Monitoring log files
|
|
47
|
+
|
|
48
|
+
If you keep your SLURM log files in a specific directory exported as the `LOGS` variable, you can use RichQueue to monitor results as they come in with
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
res <JOB_ID>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
<img width="569" height="737" alt="Screenshot 2025-11-20 at 09 05 48" src="https://github.com/user-attachments/assets/1b0f6457-d769-4a1a-b331-bb7f121b5864" />
|
|
55
|
+
|
|
56
|
+
## Changing to the working directory of a job
|
|
57
|
+
|
|
58
|
+
To change to the submission/working directory of a job:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
cd $(res --dir <JOB_ID>)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
You might find it convenient to add a shortcut to this to your login profile, e.g. `.bashrc_user` or `.bash_profile`:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
res --install-jd >> ~/.bashrc_user
|
|
68
|
+
source ~/.bashrc_user
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
This will make the `jd` executable available. Change to the directory of the single active or most recently submitted job:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
jd
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Change to a specific job's directory:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
jd <JOB_ID>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Submitting SLURM jobs
|
|
84
|
+
|
|
85
|
+
RichQueue also provides a convenient wrapper to the `sbatch` command which makes sure your log files end up in the directory specified by the `LOGS` variable, and prints a pretty summary of the submitted job. Just replace `sbatch` with `sb`. E.g.:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
sb --job-name test script.sh --script-arg1
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Would run this under the hood:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
sbatch --output=$LOGS/%j.log --error=$LOGS/%j.log --job-name test script.sh --script-arg1
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
and display the following:
|
|
98
|
+
|
|
99
|
+
<img width="474" height="255" alt="Screenshot 2025-11-20 at 10 37 19" src="https://github.com/user-attachments/assets/4464f813-841a-45bd-864e-00dd3dabf924" />
|
|
@@ -3,7 +3,7 @@ requires = ["hatchling"]
|
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
[project]
|
|
5
5
|
name = "richqueue"
|
|
6
|
-
version = "1.
|
|
6
|
+
version = "1.11"
|
|
7
7
|
authors = [
|
|
8
8
|
{ name = "Max Winokan", email = "max@winokan.com" },
|
|
9
9
|
]
|
|
@@ -32,4 +32,4 @@ include = [
|
|
|
32
32
|
[project.scripts]
|
|
33
33
|
rq = "richqueue.main:main"
|
|
34
34
|
res = "richqueue.log:main"
|
|
35
|
-
|
|
35
|
+
sb = "richqueue.submit:main"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from .slurm import combined_df
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def guess_current_job(user: str, **kwargs):
|
|
5
|
+
|
|
6
|
+
global JOB_DF
|
|
7
|
+
JOB_DF = combined_df(user=user, **kwargs)
|
|
8
|
+
|
|
9
|
+
assert len(JOB_DF), "No jobs found"
|
|
10
|
+
|
|
11
|
+
pending = JOB_DF[JOB_DF["job_state"] == "PENDING"]
|
|
12
|
+
running = JOB_DF[JOB_DF["job_state"] == "RUNNING"]
|
|
13
|
+
history = JOB_DF[~JOB_DF["job_state"].isin(["PENDING", "RUNNING"])]
|
|
14
|
+
|
|
15
|
+
# pick the single active job
|
|
16
|
+
if len(running) == 1:
|
|
17
|
+
return running.iloc[0]
|
|
18
|
+
|
|
19
|
+
# pick the most recently submitted active job
|
|
20
|
+
elif len(running) > 1:
|
|
21
|
+
return running.sort_values(by="submit_time", ascending=False).iloc[0]
|
|
22
|
+
|
|
23
|
+
# pick the single pending job
|
|
24
|
+
elif len(pending) == 1:
|
|
25
|
+
return pending.iloc[0]
|
|
26
|
+
|
|
27
|
+
# pick the most recently submitted pending job
|
|
28
|
+
elif len(pending) > 1:
|
|
29
|
+
return pending.sort_values(by="submit_time", ascending=False).iloc[0]
|
|
30
|
+
|
|
31
|
+
# pick the most recently submitted pending job
|
|
32
|
+
else:
|
|
33
|
+
return pending.sort_values(by="submit_time", ascending=False).iloc[0]
|
|
34
|
+
|
|
35
|
+
return job
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_specific_job(job: int, user: str, **kwargs):
|
|
39
|
+
|
|
40
|
+
global JOB_DF
|
|
41
|
+
JOB_DF = combined_df(user=user, **kwargs)
|
|
42
|
+
|
|
43
|
+
assert len(JOB_DF), "No jobs found"
|
|
44
|
+
|
|
45
|
+
matches = JOB_DF[JOB_DF["job_id"] == job]
|
|
46
|
+
|
|
47
|
+
if not len(matches):
|
|
48
|
+
console.print("[red bold]Could not find job with ID {job_id=}")
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
elif len(matches) > 1:
|
|
52
|
+
|
|
53
|
+
matches = matches[matches["command"].notna()]
|
|
54
|
+
|
|
55
|
+
if len(matches) > 1:
|
|
56
|
+
console.print(matches.to_dict(orient="records"))
|
|
57
|
+
raise ValueError("Multiple job matches")
|
|
58
|
+
|
|
59
|
+
return matches.iloc[0]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import sys
|
|
1
2
|
import time
|
|
2
3
|
|
|
3
4
|
from rich.live import Live
|
|
@@ -9,9 +10,10 @@ from typer import Typer, Argument, Option
|
|
|
9
10
|
|
|
10
11
|
from .console import console
|
|
11
12
|
from .layout import dual_layout
|
|
12
|
-
from .slurm import
|
|
13
|
+
from .slurm import get_user, PANEL_PADDING
|
|
13
14
|
from .table import job_table, log_table
|
|
14
15
|
from .tools import curry
|
|
16
|
+
from .job import guess_current_job, get_specific_job
|
|
15
17
|
|
|
16
18
|
# access logs
|
|
17
19
|
|
|
@@ -31,12 +33,27 @@ def show_log(
|
|
|
31
33
|
help="Query jobs for another user",
|
|
32
34
|
),
|
|
33
35
|
] = None,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
cwd: Annotated[
|
|
37
|
+
bool,
|
|
38
|
+
Option("-d", "--dir", help="Print job's current working directory and exit"),
|
|
39
|
+
] = False,
|
|
40
|
+
install_jd: Annotated[
|
|
41
|
+
bool, Option("--install-jd", help="Print function to create jd wrapper")
|
|
37
42
|
] = False,
|
|
43
|
+
long: Annotated[bool, Option("-v", "--long", help="More detailed output")] = False,
|
|
38
44
|
):
|
|
39
45
|
|
|
46
|
+
if install_jd:
|
|
47
|
+
print(
|
|
48
|
+
"""
|
|
49
|
+
jd() {
|
|
50
|
+
target="$(res --dir "$@")"
|
|
51
|
+
cd "$target"
|
|
52
|
+
}
|
|
53
|
+
"""
|
|
54
|
+
)
|
|
55
|
+
sys.exit(0)
|
|
56
|
+
|
|
40
57
|
if user is None:
|
|
41
58
|
user = get_user()
|
|
42
59
|
|
|
@@ -55,8 +72,19 @@ def show_log(
|
|
|
55
72
|
job = job_getter()
|
|
56
73
|
|
|
57
74
|
if job is None:
|
|
75
|
+
print("Could not get job")
|
|
58
76
|
return None
|
|
59
77
|
|
|
78
|
+
if cwd:
|
|
79
|
+
cwd = job.get("current_working_directory")
|
|
80
|
+
|
|
81
|
+
if not cwd:
|
|
82
|
+
print("Could not get job's 'current_working_directory'")
|
|
83
|
+
sys.exit(1)
|
|
84
|
+
|
|
85
|
+
print(cwd)
|
|
86
|
+
sys.exit(0)
|
|
87
|
+
|
|
60
88
|
layout_func = curry(dual_layout, log_layout_pair)
|
|
61
89
|
|
|
62
90
|
layout = layout_func(job_getter=job_getter, long=long)
|
|
@@ -76,10 +104,6 @@ def show_log(
|
|
|
76
104
|
time.sleep(1)
|
|
77
105
|
except KeyboardInterrupt:
|
|
78
106
|
live.stop()
|
|
79
|
-
if wisdom:
|
|
80
|
-
from .wisdom import print_random_quote
|
|
81
|
-
|
|
82
|
-
print_random_quote()
|
|
83
107
|
|
|
84
108
|
|
|
85
109
|
def log_layout_pair(job_getter, **kwargs):
|
|
@@ -102,64 +126,6 @@ def log_layout_pair(job_getter, **kwargs):
|
|
|
102
126
|
return upper, lower
|
|
103
127
|
|
|
104
128
|
|
|
105
|
-
def guess_current_job(user: str, **kwargs):
|
|
106
|
-
|
|
107
|
-
global JOB_DF
|
|
108
|
-
JOB_DF = combined_df(user=user, **kwargs)
|
|
109
|
-
|
|
110
|
-
assert len(JOB_DF), "No jobs found"
|
|
111
|
-
|
|
112
|
-
pending = JOB_DF[JOB_DF["job_state"] == "PENDING"]
|
|
113
|
-
running = JOB_DF[JOB_DF["job_state"] == "RUNNING"]
|
|
114
|
-
history = JOB_DF[~JOB_DF["job_state"].isin(["PENDING", "RUNNING"])]
|
|
115
|
-
|
|
116
|
-
# pick the single active job
|
|
117
|
-
if len(running) == 1:
|
|
118
|
-
return running.iloc[0]
|
|
119
|
-
|
|
120
|
-
# pick the most recently submitted active job
|
|
121
|
-
elif len(running) > 1:
|
|
122
|
-
return running.sort_values(by="submit_time", ascending=False).iloc[0]
|
|
123
|
-
|
|
124
|
-
# pick the single pending job
|
|
125
|
-
elif len(pending) == 1:
|
|
126
|
-
return pending.iloc[0]
|
|
127
|
-
|
|
128
|
-
# pick the most recently submitted pending job
|
|
129
|
-
elif len(pending) > 1:
|
|
130
|
-
return pending.sort_values(by="submit_time", ascending=False).iloc[0]
|
|
131
|
-
|
|
132
|
-
# pick the most recently submitted pending job
|
|
133
|
-
else:
|
|
134
|
-
return pending.sort_values(by="submit_time", ascending=False).iloc[0]
|
|
135
|
-
|
|
136
|
-
return job
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def get_specific_job(job: int, user: str, **kwargs):
|
|
140
|
-
|
|
141
|
-
global JOB_DF
|
|
142
|
-
JOB_DF = combined_df(user=user, **kwargs)
|
|
143
|
-
|
|
144
|
-
assert len(JOB_DF), "No jobs found"
|
|
145
|
-
|
|
146
|
-
matches = JOB_DF[JOB_DF["job_id"] == job]
|
|
147
|
-
|
|
148
|
-
if not len(matches):
|
|
149
|
-
console.print("[red bold]Could not find job with ID {job_id=}")
|
|
150
|
-
return None
|
|
151
|
-
|
|
152
|
-
elif len(matches) > 1:
|
|
153
|
-
|
|
154
|
-
matches = matches[matches["command"].notna()]
|
|
155
|
-
|
|
156
|
-
if len(matches) > 1:
|
|
157
|
-
console.print(matches.to_dict(orient="records"))
|
|
158
|
-
raise ValueError("Multiple job matches")
|
|
159
|
-
|
|
160
|
-
return matches.iloc[0]
|
|
161
|
-
|
|
162
|
-
|
|
163
129
|
def main():
|
|
164
130
|
app()
|
|
165
131
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from typer import Typer, Option
|
|
2
|
-
from typing import Optional
|
|
2
|
+
from typing import Optional, List
|
|
3
3
|
from typing_extensions import Annotated
|
|
4
4
|
from .layout import dual_layout
|
|
5
5
|
from rich.live import Live
|
|
@@ -14,6 +14,8 @@ from .slurm import (
|
|
|
14
14
|
NotOnClusterError,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
+
import mrich
|
|
18
|
+
|
|
17
19
|
# set up singletons
|
|
18
20
|
app = Typer()
|
|
19
21
|
|
|
@@ -34,19 +36,15 @@ def show(
|
|
|
34
36
|
no_loop: Annotated[
|
|
35
37
|
bool, Option("-nl", "--no-loop", help="Don't show a live-updating screen")
|
|
36
38
|
] = False,
|
|
37
|
-
hist:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
] = "weeks",
|
|
39
|
+
hist: Optional[List[str]] = Option(
|
|
40
|
+
None,
|
|
41
|
+
"--hist",
|
|
42
|
+
"-h",
|
|
43
|
+
help="Show historical jobs from the preceding specified time period, e.g. '4 weeks'",
|
|
44
|
+
),
|
|
44
45
|
job: Annotated[
|
|
45
46
|
int, Option("-j", "--job", help="Show details for a specific job")
|
|
46
47
|
] = None,
|
|
47
|
-
wisdom: Annotated[
|
|
48
|
-
bool, Option("-w", "--wisdom", help="Show a confucius quote on exit")
|
|
49
|
-
] = False,
|
|
50
48
|
):
|
|
51
49
|
|
|
52
50
|
loop = True
|
|
@@ -59,7 +57,7 @@ def show(
|
|
|
59
57
|
"idle": idle,
|
|
60
58
|
"no_loop": no_loop,
|
|
61
59
|
"hist": hist,
|
|
62
|
-
"hist_unit":
|
|
60
|
+
"hist_unit": "weeks",
|
|
63
61
|
"screen": screen,
|
|
64
62
|
"disappear": disappear,
|
|
65
63
|
"job": job,
|
|
@@ -67,6 +65,12 @@ def show(
|
|
|
67
65
|
|
|
68
66
|
# console.print(kwargs)
|
|
69
67
|
|
|
68
|
+
if hist:
|
|
69
|
+
hist, hist_unit = parse_history_string(hist)
|
|
70
|
+
|
|
71
|
+
kwargs["hist"] = hist
|
|
72
|
+
kwargs["hist_unit"] = hist_unit
|
|
73
|
+
|
|
70
74
|
if no_loop:
|
|
71
75
|
loop = False
|
|
72
76
|
|
|
@@ -106,10 +110,6 @@ def show(
|
|
|
106
110
|
time.sleep(1)
|
|
107
111
|
except KeyboardInterrupt:
|
|
108
112
|
live.stop()
|
|
109
|
-
if wisdom:
|
|
110
|
-
from .wisdom import print_random_quote
|
|
111
|
-
|
|
112
|
-
print_random_quote()
|
|
113
113
|
|
|
114
114
|
# static layout
|
|
115
115
|
else:
|
|
@@ -123,19 +123,20 @@ def show(
|
|
|
123
123
|
console.print(f"[red bold]{e}[reset]")
|
|
124
124
|
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
def parse_history_string(hist: str, unit: str = "week") -> (int, str):
|
|
127
|
+
"""Parse one or two part string into integer value and string unit"""
|
|
128
|
+
|
|
129
|
+
hist = " ".join(hist)
|
|
130
|
+
hist = hist.split()
|
|
129
131
|
|
|
132
|
+
assert len(hist) <= 2, "wrong number of 'hist' values"
|
|
130
133
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
raise NotImplementedError
|
|
134
|
+
if len(hist) == 2:
|
|
135
|
+
unit = hist[1]
|
|
134
136
|
|
|
137
|
+
hist = int(hist[0])
|
|
135
138
|
|
|
136
|
-
|
|
137
|
-
def nickname():
|
|
138
|
-
raise NotImplementedError
|
|
139
|
+
return hist, unit
|
|
139
140
|
|
|
140
141
|
|
|
141
142
|
# start Typer app
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import subprocess
|
|
3
|
+
from os import environ
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .main import show
|
|
7
|
+
from .console import console
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def submit():
|
|
11
|
+
|
|
12
|
+
log_dir = environ.get("LOGS")
|
|
13
|
+
|
|
14
|
+
if not log_dir:
|
|
15
|
+
console.print("[red bold]LOGS variable not set")
|
|
16
|
+
sys.exit(1)
|
|
17
|
+
|
|
18
|
+
log_dir = Path(log_dir)
|
|
19
|
+
|
|
20
|
+
sbatch_args = sys.argv[1:]
|
|
21
|
+
|
|
22
|
+
commands = [
|
|
23
|
+
"sbatch",
|
|
24
|
+
"--output=" f"{log_dir.resolve()}/%j.log",
|
|
25
|
+
"--error=" f"{log_dir.resolve()}/%j.log",
|
|
26
|
+
*sbatch_args,
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
print(" ".join(commands))
|
|
30
|
+
|
|
31
|
+
x = subprocess.run(
|
|
32
|
+
commands, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if x.returncode != 0:
|
|
36
|
+
console.print(f"[red bold]{x.stderr.decode().strip()}")
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
|
|
39
|
+
message = x.stdout.decode().strip()
|
|
40
|
+
|
|
41
|
+
job_id = int(message.split()[-1])
|
|
42
|
+
|
|
43
|
+
console.print(f"[bold green]{message}:")
|
|
44
|
+
|
|
45
|
+
show(job=job_id, no_loop=True, hist=None)
|
|
46
|
+
sys.exit(0)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def main():
|
|
50
|
+
submit()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
main()
|
richqueue-1.8/PKG-INFO
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: richqueue
|
|
3
|
-
Version: 1.8
|
|
4
|
-
Summary: RichQueue
|
|
5
|
-
Project-URL: Homepage, https://github.com/mwinokan/RichQueue/issues
|
|
6
|
-
Project-URL: Bug Tracker, https://github.com/mwinokan/RichQueue/issues
|
|
7
|
-
Author-email: Max Winokan <max@winokan.com>
|
|
8
|
-
License-File: LICENSE
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Operating System :: OS Independent
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Requires-Python: >=3.10
|
|
13
|
-
Requires-Dist: pandas
|
|
14
|
-
Requires-Dist: rich
|
|
15
|
-
Requires-Dist: typer
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
|
|
18
|
-
# RichQueue
|
|
19
|
-
|
|
20
|
-
> 💰 RichQueue: A colourful and pythonic SLURM queue viewer
|
|
21
|
-
|
|
22
|
-
Installation from a modern python environment (>3.10) should be as simple as:
|
|
23
|
-
|
|
24
|
-
```shell
|
|
25
|
-
|
|
26
|
-
pip install --upgrade richqueue
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
To see your live-updating SLURM queue:
|
|
30
|
-
|
|
31
|
-
```shell
|
|
32
|
-
rq
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-

|
|
36
|
-
|
|
37
|
-
## Other options
|
|
38
|
-
|
|
39
|
-
To see more detail:
|
|
40
|
-
|
|
41
|
-
```rq --long```
|
|
42
|
-
|
|
43
|
-
To see someone else's queue:
|
|
44
|
-
|
|
45
|
-
```rq --user USER```
|
|
46
|
-
|
|
47
|
-
To see the last `X` weeks history:
|
|
48
|
-
|
|
49
|
-
```rq --hist X```
|
|
50
|
-
|
|
51
|
-
To list available nodes on the cluster:
|
|
52
|
-
|
|
53
|
-
```rq --idle```
|
|
54
|
-
|
|
55
|
-
To show a static view:
|
|
56
|
-
|
|
57
|
-
```rq --no-loop```
|
richqueue-1.8/README.md
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# RichQueue
|
|
2
|
-
|
|
3
|
-
> 💰 RichQueue: A colourful and pythonic SLURM queue viewer
|
|
4
|
-
|
|
5
|
-
Installation from a modern python environment (>3.10) should be as simple as:
|
|
6
|
-
|
|
7
|
-
```shell
|
|
8
|
-
|
|
9
|
-
pip install --upgrade richqueue
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
To see your live-updating SLURM queue:
|
|
13
|
-
|
|
14
|
-
```shell
|
|
15
|
-
rq
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-

|
|
19
|
-
|
|
20
|
-
## Other options
|
|
21
|
-
|
|
22
|
-
To see more detail:
|
|
23
|
-
|
|
24
|
-
```rq --long```
|
|
25
|
-
|
|
26
|
-
To see someone else's queue:
|
|
27
|
-
|
|
28
|
-
```rq --user USER```
|
|
29
|
-
|
|
30
|
-
To see the last `X` weeks history:
|
|
31
|
-
|
|
32
|
-
```rq --hist X```
|
|
33
|
-
|
|
34
|
-
To list available nodes on the cluster:
|
|
35
|
-
|
|
36
|
-
```rq --idle```
|
|
37
|
-
|
|
38
|
-
To show a static view:
|
|
39
|
-
|
|
40
|
-
```rq --no-loop```
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
gAAAAABnEpbvOgLsVhspN5M4SH7oIIjr2t8BaSv96xKOIeaZfAOABpiQdZiLzMWre3LD14ZH-i0VeydLW6JSaepYtOW6qhnOtZMUh5LvXBubEhPPQkR6w3F7rMOLQQOsBHxHBsYktVf9-733DnWH7KgSMrUT5StcpFmM9mPG--hcJMWfFi5YsOkmlcMBn5elcku9AJRkgyYKzcxdZnX1uWdPJk3C2j-lzysAhecW4sLb7ygvFwvqsz2GFlYebf2JUHjTulYGuU7UuSh-N2NG1X5dZX3mRi9Pn8IDRvidVowdUdQq7IcAxSp8AxdF4OqS1IhIcUJ1xVlRe8qRvu5jtxP3NyBV39zBgfAp1AZInm2CoxLAzGL1_Lb5EeO0ev9PGu6eMSvHfenBLWS_WnB24z0dwfWF9bQPeyOoKbm-0GSBQPSjIJPOaCBfhkFxH0SaRdYVuwgqzCGZXQL_j8OjpDHi_XSWMXCFWuswE4WS2kiu4g46M4mQP-HOR4EuwibdxiPxhvuHwibnEoK0pgxG9wyQeowejaOm-Au7avwd2x_M_S1aY3c4TtI_Immx4BqBuBMFWDOgdwLzHuNgagU5NvYNsXElow0bwlIERjboNvq3Pn3m4kHRF3-RoxmiXuhNkN0Ho11GKGaylbFSTXHrIJUIDKSjzeQnVOPLYOb9JvqL9RI7HrikFIUASJTogH4whfLKNikTaHrSkDOpR5PiZhVTNARkZI9xZ6vEcmDemFTLsQ8RZ22a5w-dG03i7dpnjAnfL9at7fcno56Fz83tev_pb8EIY32SoaEGKjbz_jLIO2nKM238LttWfIefy93Q6Uk_W7Jc3gtmcWj1sQ-KEJ7qmk4QAC--lGNnF8AD1HvD6jyroKzGzRJCbef76kG_6m_zlsZXVSIctA2var1vIpHWMa459jIxpzNiydldvh9NxcP3GiLsFbUDVs3MtzxWz7Onqy2KlnDCFWS76fZIvwuHzdryKEEYEUebfEIxwFLK4dAH9gx6lK0HQY1_Q0NV3MEi5jTcc_I3vpLeiu3xTjmPAt14s_UESjjOoXqxgtG7EQwxX8j2NJdqNNf1NeeHN1Hz4ZKy1XB27hjFqI_bF_f-bW4UgZEddGdbwhjDH8AefKuOTXoi764xlhklvhBLBTfpnqzGK6Qy1tjG-QbbdllEzKlYyMHqQwXvUrYjmy3OnmkkxVN03eYNyeH8tXhx8TSOSouQK7lX31SSK9Oo05H1XQLt6XbmZlXLOo7Yu3zeHBj5MADW7AqLyAk6WDmtGP-UJZviy1anetLqWEV862WuZFEcVSQAylPDgSLF9AZexb0YYQ3soPHeRsiwsjFeFeCLJeq_fN8mZ8CRRIn9bTulhWiYGJurq58XOzvZPiIjdOjY3F9sGRw6vetfFD9STKo_CUlvf_rYrUzUl5Z5S9PEC-yxUrDI4D25d-pCyum_nO9YtQlsv1f4MPwpwzFQvgdqBk43FVKmy8j2N0WHXg_aUVukKYV882ECvq1JT9kIp70yr1fZMKcDEDmgSrExBUoXI7hoN5HkDTKzbe_Mq76-OKH_1xLk4s4Hvn5Zd_v9s0ArshK5W9shQ10SltzHhegT_H9hSWrXQLi3BJx1OE77RRzXoa_eBIFSnnSNQeerbbuK08QsdAtYuk7ikmwg5gKy-8dnX73c9hYu5Au1f_7ZBH5OrlJJe2mU18dX1-uVsyfqYQsUNZSS9YVmeVfcvKFl_dyxVyzfSCQLTizeslpbs8SH9p2iDTL5d2D4ybF3_4AVUiBA_Am7Ael2mM0UZCth4zUyirePiGYXrcxXjQtSHyb_kcnwDYIg2WGhfe99A6EZ51755HTkQHtlLUlCv8zEo57Mz7ljAPH6E0f-fcyhpOJQshe7PV3xsBZL_sHrI5VnNkIQmP7onXQNVY86FphNgKcXCuTkw65I9TEAOEN5_y_YkQGT4Swm0xc83cS0h9jlvVLaBHLOecKXFCbVLCziHghD0Z9bNiuh-Mf7R0iJGDzzRN9AW3oMpcWoPTGuC0QNJWsQN9fXX783LkyxbsxtjFGli4USNhWnes4CNFFMbA5JaLhFPlERxRFMUUB6Du7kSQAvAB9ooxjYNvNSAgRQBc9EaKCVXVc8gDCGvfZcIdI90ysDbeERBJ1gnfKUtO5kf-ar7D1N5X5loOdW0oZ340fd84eHYBEKRJ7kFlr66bHMeEk8nUxjQ_ZUTcHjxDCnhYRrZkCw86zNiWriMKm4Nw7ErZ6kCQqQ3Zr1zZW1IkiMZSmv7qxBviZvAU_LsCeRfm1lqx3rlIIw9J4vfIUwTb5yZ-jtq_BBX6eYxXGCfnuZMm9pcAlWjAjFGOvGQp6JolkBckDNk6RimLaW33WCssEEntVTqiJYFFpXKmD57pfN0hoWEzslnESWS4xTOIxG1U-aJfC5VNzYwgZkMpaZeTuVGQNKUMk2zcHuXWBCtRi0uN3dO7JfgCn2Oeaa6ZPUAWz241VBzg1ka_2pENH5Eki4BKNyw9h928gIiDVVFnaKJydU-GGWuVXtdkhA-xMIuS7H8Mnqtgv2LVpNJyDu1FYKIT_zme1MLScLbuoiIi-ZcTC3Vs-vcroHUMdFPJjscvKZ5XtFQelzLiURcQ9DDzehmf-2Py_pgg5-pWGnNxhaYbV7Nzf68Xfoe5guQzjyGBSjR3Fzk7rIx9p1OfnRLD7zC9HNGy35CqA0K0Gzjym573rUkJ2QaircGt4BNRMqTbKr7dmt6xPIv7dOpK3eBNJvk-HVnz8hKqZTtYt9Kr9qivMuk3eOXjPaeSd6Ih4gUYDnIoU9V3nca-QQhYeaMt8ujfRHl1oE2eod23cZWpecqPitbaJRJQ5Bwmx-PeyjxIlYEby4WO8zr75A7NlVUu4kADDK8XtpQJDkyGMRZzuiqx5rhldyspmkP2zXtMV9tn-KQGZbUM1BW5ldXZZjdVrL3TZabM0vLbpnN_Pf8_3TYWMYOkc5TlNJ9NHbgo7epucW2wGV6O_dytO4TpLEDf4SXR2t7sE8ClJXrYbRvDJlOi3NK_799S7lHzNqw1RU_SnGPujgDRht2wWVUSSXOWijkwFLgdEkHomyKhMBIAwUIDPw_PBxyTZJJW54IWlaGudy9Wxpgi_EszuhuG0IJdC6ynNL-VBUwHZCGkiM8RmQOQdV3UKlcp0MozknS9lVrlenBnGjSK4eJxA1w5dQHssDVW6iK4Dm8SBFDE3B1EwBFd5ck9knoZpnr3e6Wqd2cXyGrPR2axEnjUhwaH0woZi9sXmBS6lbTTG7YfFhFStBqo6v0qvWW3eGyHE9Uzdef1x6WDTtX3TQd1OonX6wrRPv1ne75EhD8gbS3Zat41o264ivzTVbK_6X7htAAHrPbd1_nxoDgT4ezHlOtN1jOzqShCPuwgOaZCowGdbJtQZI2i9GlIAlwYIj-OTpTZGAeYpx35c7jGgIl5h1ALNpAguM-MEF4nnsJDb9j2rqOZkJjacMVORQQuHtUX7iogsdpLR-_dwIjca5H0aM5o0aIrQQ6prwVTZzpb8SoAQq9uFJXfSWO3JpTMo_3jH6oI4d8UpMXApy-QK9hQHTh8dNvM5uZmmirrpFBfTE409BvuYhcojjwXaT7KHKUNlG9hm6-WpPK2WrUD7PeTCHI_s6rW55Tb8bhhvmW0SEiGM2TN9ymrKZ_16GQlVmNiepsl9Ox4AROgYNqcuX95X9JbgwzhKuekfIfdY-c_C4EdY4AtkNl7X3idRNjJWZJwftUzkEXKj9bCvKeaBsZ4rkV-7yETAnNykoWnjAcS2uPpEro_ryr-H47AkXuxlDPGaZgLKAnutyEJz0nDjV2LCmk5M5ApqnjRS5c1jAQpypWIeUa1e6O0NyYVhkY95LLHAcWC_1YsRy63t51ntvl9POZKO7wlwu5UJ8z0QEfWb_4Schs2RX_bSEFgYY0QVWLXP-DoeFudYiaK2m1e661-LfTz07lwROQQ8xiDNuuiuOzdm1bTr4FIQxulQlxcjQJH0H1IwqhcydnkUWCa5-us65flEUhGay5YqI0hPN3XB675xm7_o2Xps3spFy6U0bWv737GUC39KboVDKIcHvwNS72udwy8XW5ggNEyyUkRvXj82LNo88sk38j_A-dYR0nKOKA1j9FktkA_EPJJ8gfkAGOKzDx4B8FA_yQ_yo3IFr4ge52EuKInVsyx7gmRJp4oaZdskQFHIs7eR3qs_TN_eLQohoY-mv4bi7E_Z-OjCi-DEPEjTXt-DgHRG_GOjzvdVYbee1ZJu1BHO2TY8uXAq_vxcno59VvFRVhfcThd9OyFl7Z7ckAG1fbpD5OD_WuGD_1CKYQcqW5PsfKyb2YVrBPKV_GtHLix2fFufH134RrSoBxUSBqcejnP_k7QsG-qrJjG8hDw7-_Us07L8gMbC-s72sd21LBgoQ9wawrrYHyd9Ml46eFF0UDMQXoo1cAp4xMYwhJhQ58FNrH0KuqoZedYlDjc6u9aTGRTZMGy6lvZlyl_sN9N9LDa1FQnKEh3cjNVPOmDflNLshX_Q4ctg0zEhk7TGkrhcvLszfda7-bDt79YdIrjGCEQTn9yjzHjWtL1PbgA8gbIAu0ZL6wjkYk6BH9d6esShFRHsp1anqAd2U9kwvltQnpUkOogPiHBz41__ZrMk2FLiX8F8Ztr_wAFlHmb1Bii3nyGTVj6bvEoXEenxy_UYCZIXtP9-3fR-xSAKQFbKVcZn0C4cY9W2BQOlVP0ZDYL7kDajI5yDXFQeT49WGyq3i_JuNYVb11aUEnP4ghWh2t2WKUsD1L4qS8uw6jQiHPt219V3wnstwoOa1e1y6I9zyT3FpGpg-SPm7LgyQ-JnyBwkwaU7YVkCeEAH8dJX6vgzECqUcNGXSFrpYyYBiPUh70rOs8CYA_FLcj3c5WvC0ChZU7DWbiz3vp4Rx9ApQm2ErI-78s631wINQalqItcEAVU6OLpvmESgrokmyhShciTTu2EM8OHvcQjxsO8A8NhjUw91uCNoU8MjF7gZnItrtuNJc1fKzMdYSeWvf6YrJbfPvR22naeTDlAqex7n-amuNDWDsf-ltV7E4t2-FQc5d8rBYMK9CTQtlVxzBc57OGa-R5cmOaTW8Zq9LzyH2hrEnONYzHWaqlx0m2W9Hiu7hVvQFqxlfsdlvTT4PmKsn0Y-ehiIpMN-3XEUruFzqCoGuU2nbdC08lGLnK5CKGIeYmcWH8InE_rIEf4lvxHpH3w0dn0tUJM094pdhNfniE-dZmPkQJvRjvEVLiQAYfakoacT1PsRfV2D4AxlC38q4Bol-SqpHpJQZ16Rf40cyucZXYzF_6mBFadHSrWqvk8VyhiLPaQVNGmuNoCue1_6qSAU4-d74ywXP_j_e5QWks4fbrXOBlh3ZqFnH_QEZVi-qNMESjgZ52GmjIqtY1IjPH0jZJNk5WXQRmEoMAwsPFT82QltTpopO8EswrsFLL_zuG2kD1tK3TVz_0GYiIGthiZp5ysIekpXgNi6Wd6Ura7nuD4MmwDzLfW8Qyn4ZzOdK94ylv91zEgUlyQu7qRyixvWjQ4Vuf0Jb6FaHb39bm_PL42belMXIerpqly8SOOxG48L9m43s85f51oAC7ZiSJj2Xp5-6oxjwCZ9LGm1yEkVOlg7MBH2WT1bzDDvdZgmXvEt0pbWPWP5SGH9Tmjr6kOQsgBFbj2fse2dvkr8DFTdvauUfT4bDpC5UmmWXNDaDKUmZuLSZPj1dWw6o1lB5_GF2XOJCOeIXZfc48a-jPO0p4PfqKGDoFK7bEDErnD1-0TvzkZu98w-z0Rd21g2qzjePx58e9RoSoX-xUS1OoUvJdrTKlk10YGh5YTRUE09GnLGsah9IWzlv4a6TE7o4QOK881Jks0nb3TVtt2Z8fuROndfIndlXBvHqAwu38va5mYn5UQdxHKTsW1KwsyiHEyygqN--v_6OSVMWAVD-IvZCSW6a69IIJKTtJTWwB-5_w1tDGl_vVlbx-ePlF2h_odqujQRvlAOGvqn_vFuuuUd9kD_MB7aUKegQa5IRs0SvOp43iBLpmYP6sht8qdHzLdluzFWx51VCYMzWp2cZjGhGyHN2UjHhkgFVoRPltwL0LLihC6heXdk4rmBdoezQe8SbzDvMOD0CCI_U0gad8GotaAUgsV95XQeBuUsd6OtpddsEFIgJRtcytH8qlZMt0eGTfe_Gcob6Ss8wzRNvwISINQ29AidZ4jYb2DUYQUF-4f4Eg0KT-Ohi_F4QcQ4OE6VnyDZkYGLrrDT6KaA44cFmmWeQjuglMMKGtZRszs77W0AiGDwelXHlNTUQrmgmJ9x5b9xdw20FcEkKP1JG7jCSomjcQqDbur1ZXf8djuKCFMk17hVXtpcsAdXLpKuly8wZwDeUXmy_vApouIllQoZplBMI1oZZFy8VS98W_vOow6mlXIgyORpYwDcWmTac5bSSI1riek-DXJiEfY4snpzkc-Wy3XLbsDTBr47Q86bjeHMVFgOPpoyYUwj_ueOzxH0fAJiZjww2tPyKIiKJz3-2GXWJG4DCneVE2rECGYTsQDqjxMLST6xZDDFajaB5uolj4khtZd8ufMW7rXlXd0Ec-2sfv51AZXzkSmJCwID4nE_rUpHNKaHPav4GE-P-yAqkpuutslnTm7ZirXm8UTc1GxZmOJ1GGT7gAgNLQGjnp9h6Cq8UT7lah9MZqy9Wy9ID5lHoWcgZYTcxH2fejlOxzEaT7wicbZRDW-bZXx_FN7PUr-b-XqqBjqCyu722Io2iKsvM7_MdQF1O75mujFpTFsnSHEUgYFqwsAfpktuUE_rVZuQ9vkfV0GBvAUvu-w6qPnl6B1QfWapmNzoyQBhlRdTklR5gZtxdo_6lCJtjveuoSa7JCh10mAyOy8mAy57lp74WvZaOXr4abn0xfF9K3bCQeIS8TrAP8U3J6Vmd-W7vaTAtRHc2t-VNxkpHDpgP6U7s4-keNGyuMB4_IXIoqOSfVRKZsFfH9Cdn11Umh2gSJl9V_n3TPoQhqk7w-H0bL7kUwHZ0sScOijLQDxrSH-ZGbDjR2P1qb-isbAgqnUXDo5EdfanA3f5rXYoAqocaOG0cwLCDVbUhRtwoEha35HIViPGa4ZzEh_5Wwr4FveqvsiazYUg-11qKNJYkmPjiD-jbo7AhHWxwGqdTMlSDn7nWveZ2s_35aqW8yHiY9QSzYUA-RwQF32VVTQOHwmAavMOrM1MMFR4isGV2yRZ06xoNn5QlZpgxFLXWY-yHHJ4lYK7gLvS_XN_QmQVZBhEjDRsoJndD8KmXS1OtzMrUpXlUx1QlFcdgfNzdtcPKlNlg1F7xTR9s8_pB2Dd1YH6sk_G2ekoGOmZFDXMOY0B20HPXjAXSbwPvMuCJ4VN7RbwS-nblKKNpLj5WSMYUbmCS6o_sH-YyWxcZ2RlaEWmAM1gwKsofgY0RKlT1h309khqzLSNyqN8h81mXN0VDR0ZlZAdbZyP3BUUrpX5PmEQLxAkjH5CQZbeprFqHWNaUTC4mRKKtSr40LXJljRwF6PIfLat3DE_JrS8gg0XF34CMsi-u_amMl5OyvpsHWbTYYPgoe9S0HEHT41Jz0ENUdkoITCd3TIwJCJ1Jx5-N24sngC9mUcwz_rdsDO136vrPcM4Le63VYkXWA2OacgTrQArPCUAsJyK5gEt-CJF9UZW7HvwBOkZYazbDg3Hw8-ws2T22qjcJzc-wKxGUl5ZRTKAYWotTnk9YZYeaHCrSugepzj4H5fOtV5jdoWrpPHiuBr39QysGd2ahkdxSi9p93mlbjyT5EZL53cI7EX8JXHoRq02SuriPjU6cUE4VPo5JPHmM1LZXc2m4n3LGTlbOyZctk5xaR9xwhe3YzIdgYByEWzvi_pVHwNHNB5PUJvPS2_32SqNI_PHpL1QmfxHoQq5-QDI0uM6XHXr6Fq5JFI04374cPjzRiWNSzDGs7SZ6NpDnMredX7oz7uyuL4D--AkEtc6pUUHnTStetpaHS66l6sNctC2M9ST6NFNMbMHFg8M9PH9bxmC7zy-Mk_iMlMw4W-ACiSFlptwnHkTBcaR0eZdSVtGenjF3qO9VkUuLVk1Y3GbCBxTB5PNHF0c3vBU5rtSG-HzbBA4RUyI7v7ca6QWtzfxOzdtAM8Va5gNCwqmaq8LKYa_gb8rgmcjaKAcJ4feMDGBaugBJ9bzb83q1SFk-4sExA8QEfQDHyc-SQG2AmGaalIJD_pC4QMPfkv03bcIcVmiyeS0zGeaVHzd76udmvxxsQus5k2FVxQEfXddB8T_4O5ZKB8ibL5yO0PL7SC-WuFqydXmLJ_i3szFtH3C5XAMgWW1OSa1EbdNmmvVOquo31DirlLeYXysQ6kIVe94_Es6_sK52hewhogagy0MH610sBzEUpfFTyVe_WNzABjq4Q0k8rEb1vPx3DknceDb-WJqW2-pNtEq0L8-ER_Xqrx8TE2JS7uJkG2GqdviT0KF0mKJt2dz9b8ZjOG6HTSUk3t0jwhA_FrBPwLJoLnwEE5Ynq-mwgsAxWVXUw_WZx--t3byxHefGmtIOQulbSjwJL7tfRXnY9vBHGMEV2EB8fYRDH4syiUNmHuedX91SAsy3q3joAlmNdND4PmsJ63dIRlyrBx_BLONlNuTO3uDk33hjloToQ9ssIDM_v1ylwWj60yvJHnbm28WXZiTuRtqjZMGQlkRLoxXVGFeTU0xXZaaCS_xI1LfOLh5khikjYuu_ZRyxTHwO_LdYx2Yut2nXtjzVPwgR87qJBHYugGpWi3BClAPv3Q__7Mz6onkNUL_HC7tmxF6AdnHz1fopaKIPCWyLht_PMf2JPp_fveBw0G1ybu25aK_yD_ieoJgMjomysdtmYiedFXHpqNPRXHtJ7XYaNQyMiQAvy-JBKdzw5uwSDgMqKwoIgRMFAV_Ybl0qjFLnv_C1E5Bfvf2jLUR_OFk8exU17--jk5LifIpRVd7so7AS-JNmzYf0-RQe8rWU7fEtKcN7zqCI_n6IE2J4COb6IoZk4V2nv9wyMGo4LqL9s_aoBfSbHe1WiZYG8B0OO1Jecjcr-98ZZEgnjOQApfdkLHzz0357Z3NqEOPpZejo17_urndLrvKMWKXOcVL7SDr_7VcYpq4am-jpNb7e4JdiqYsetYd6oWlFDwoCCW0gBUAkQ3NL466XvPcJLnmGMECdJuWwDYQ_N4ODIcwcHew03aetbKdK0TejnZ1ZEbgmOBYbuUyHPl0_USHw_gZH-YsA9gCWtAxgse4MkoiFMdyqWgvThROUHyDziyPG-bHjLpHwqgC6thSX0cLY1LEYQj3Xzab2z4qYo083roHpiB9QJnuuB65z4hRuyjqaQm7Ep81ms3Pqdq6czPhwOIKubovjbv-zCckJXS589v8UpxEJFUi4Q615dcaNUFcuTtkjpMeRYyNhuzHytjL5L7Zw4qsNdxhLtNnPT5KSi25eA-LQWGZOtg50fkWfPf-hyqTzpnUUr4dJsLUDvTDaNGloGP1H1HQHiBhODvKSirBJFZU2gs4v117KiJnF7y75_kVad33OcbAcK3a3svhxtfC9q62BiPAJVfb6s0_85Od88L7TBmlW4-iF0RDc6qeO-0ivmNCFukxxxze48TvsyqEU5DFWaSvwvRbRzOSt-xIqJFQH-fP1slcFooK4yHrNQdBDEF4LNKxJyOytMvELETE4M4Tn91I5C0zUs_JUftf85d6GJDvWWfVc19KsmAu0aLbd2jN6g7Z-TifhNyudDQ3NCiTPaouLNzcggzYoJaZNCtA_aOdlckk7X-wjBWGtw9TEtKNGfET5xktFdhA69_dV3GVfQ5I-7LkW4UaQ2gZ-h4eDAvX0YWZz6CISElmPw50IZPOz1P8-wS3a0AdusbJ2C2PMbyc0rogQdca9y3uPHJq4F57jKGLZeLZroaV5fyTmyfpG3Dxqc8Z8GUB6AX1q0Nme7pbRiLrForjfq5B8ESFsyR-xUMMMQ4kXhHGD2L9Ko-EdfqAUuQ49NsP0zsAiaU6EQ-Jvy1F9bzBsjc3Iv6gXh-l6hAej6CQxR3mefYA6zuUXEx-zz56G2DALVtxNY3UBoXOZ4MVsVBrKCz4ZG7FdbkdgYV9LWobPmQNkjyxgmb_yBj2ezF7WQkuBwFcKWZsR9wzkph0kvBFbaDjqWMg3E4cVTROiKO6RWP0rQI1Le2c-fRAKe4O2VVM9ZqBOHxA-9Lj01xIq-JaB4qjw-zfQTiyFvckKgMS7bY7Yct0DM0xjSNpwgm6OozGxogyOD28TSVg1C6MENnINRruX5utM3gIK5nFoQLE6cBWxuEzeMhJJSpETC73hEYm2EXTIzcfCQwCiO4lFooMCuk4w58Fd-AYwwtUUS9rGKLjC9xlklmegRct0akyEfhO_kHJ7cCp-P035Ld-EkLRBkxriFMN8j1b9os8pGFUI0oTajEdebghpYKP1fq77P7-5aX8pLC7D3sqozhHhOC4Ih8qoLOr_YEuKoepkvzEETO_hY3k4ZCZcGo_5O9czD98RLxf5wibfqKvom2LUIrv_HjPyDI840txzkewxSqaxIB9J9iqRddZwLNYuL2RLDqy8aTMgOpcgYYpQVgijU1qPUG59MoJG9STJVxRqi6XEg68Imh5kP_NApI3Il7p6zRzJWAbML73UrurbQENN1buc2QALcL_upeAXyWQKnD9rHsCxTZbszV1QQwQKq4RojO7OZQwLjCUjpRnoOG1OjDiBvosBmuv2XHlkB6rQHElbmSsKAoTXvvu-MQpUp74TAoqb0G3JqfZgDoYMzizrB_sxZJbJtAWy-L0Yy5Kp5u6o4H1eB1KfH4IhQVEnMlDElY7jgmyjgSnwN9bEndR7NcZBaUklQpW00mbpY1_oQjXn8dfw5PIs-zEwCh2mkCvQCn5AEYYhuKz9hjlN_RcoDpuWkNDoheypO2pk_9ign-wdzd7LjcgEfjUrRqSW3REFI63CEZXEHPi38SrYotxyhRdXkUiIt04Zt12G4hTqAx2NJ4m0Z_0SY9VulyzGlsGb7hPQtw2OXIR40zXXMcWhIfiScq8QPDRTxpw705OwNA_UAWFVPHgli8OJ3IYT15GqEP1MS9UhAfFzhHwr1lCURpra09IKeZwNu8PVv6uTOCxGnTK1EL4AUFBqNUJt0Kg6-uD1LxrHxNDGjU_RwqLwIOrzrr9_QUveV4XTS7-4C5Wl7aazREzopWe78tMQqJ9Z2SQZoAEKQirmnhtVAcsnv8EmHJUc7vOul1xzKC56ysyeM066v9VmY8lE-pMUmxuybfDGJUMCKENCQ1oeEazIA11j0vzX4cAar4A--qpBCJXYt6LkA9ycMVurz55KzsyxJimuCH1-SW5EahcZ-VlEZS6euYrAzUH8kPJRlyRFX22RVqidaLHLEtqEvsq7d4XJxCa72PcXvzFnU3TVwbVNiotjbSAPeoWF75DHGi9CnN6hy-yTMhVek2hXpne-Uxc5mK_zdYv29WiRX0ZQR8bizWeuHIumFsQTDuM0dHcr7Tf6-o1pBqYTv_LepCBErCARVCIRBNIMloalkMirDnfSMhfwOrR27tAzgjeMS81dBdJuuALUTlWrgzcXxcmK_4IL8ts0GaZ8A15jAWyhX1K0zJkB73D_2NiN0OsTY-cvJeotu1cD2mQ7gi45tCsWJ5fzskBxVOHzkaxRa6GUxVgPfvZX6pSjjx0vtbHyYs9_v1EqXLvCM_Kyf942dIsH09Zyg2vbDwDdGYbgq2Mo9wH30szj215fsZevU5vGhHnLqbBkNG9l9A_ufNUdLR2afWTC1WZtsjONua2A06kHN1OhSDr_DTia0dniKd9-JPsDHBwtCmI5ziRSszBCqhE5uY5c_yUmJsxRvPr6MoL1wGe3k8tXjztZFirRSuEnCFwtxWbaajP8ChW4P2uliG1WSNdjiLw-CP6Z2pBPXOkRyZPEi8rl_sg9aCFRLRB0JGT-E1_vYTnZlBUGgpQoQ2nF8_92zAuNPJPEsw4PF1l0cH9oQXUfSenw0kWsmxEW7X0xfM_WEKJuJPcVYfhzvAF0qkNjEdAexy9z0E313boRwZfC3D-iFjkjx1g_A-PKbcMddQSIn2XKc35FFKq_M2X87EpmLoJETajHYmbxV8AKL-ZvfU7fbuTcafEr61UOQB_6Xk6m77xzfDCifbZtpJFtrt_hL20HZjrKgtRbRjPl7StQONMmI0IzcEuFN_WkXq9mfIC8hUzPt1oAVu4BuU4Xuk2uoXz_ePdx_je_HC-nZPpFUz1_M5PVOOy5hFV3yjNquiOQ9RRgqiZ920K8SLJ0Ri-uzqAgJG2bBEUSgHedd4dGkfbhKUZrpDlpIEPKOW4Ltz6PCq4T2yll4A-RVpP1KA3peXhexZIJCwwJSmc1g_NzR9jrCFY61SWpvO1V_tTfKfLXMCgGutwsP4RBgi6fPp2H97OU5rFCA3NWTGy43WmWVysdYue9YWmgwde0wvWfOcgVrJZ2dxYYJZfWBeaiCCuORmrWplKytlcI9cCO_qEHvCyUh7aXnwB4Ffo3Tww2vrJTvZqhKRCnOOVdfTn01wXa5G4JgzxEwDxI0xjdMVedNlTHlU7z3Ix_KVVplvF01Md0Y6U54lbagJSCL3xjbMB4gFL92vDSgDXM816F1NTXJAEsQkJ5WO2bZRt5wNRSkaypMYSgw4_1rtCePLhsUuz4PUz48TmjkxqQs99_DxTeol1-4r42o_3-f74zZQINooNSZBrgaLLIjEsXKYjx7_tPjSByI1uwIWz1lHNP--aNo8c0TH29Mlb2AE5oWMeVTr5WDmE6djkCsna49FOFmXu3y3Vva1sFQEVPlbE1c_zL5ECllnrRbw3PEeIsNwpzbf2R53RQ1DSuegDTEqnENGH1PZ_fM_56oBSFpaV5TGMUgpwyClqIZjsXRx4IqFwYqN2sFS3u2fOAzGE58cu0puL3_IF_UTCWErvW3BM6PTl1icF4bF_DYwDeZMja3Y4eqHK-x5pud3hCVeDpg6rs-2ANrRx354DNSsmweHQgEMz_GUy36bfem7ABcr-YF2okVhT4SLXYtpN0hXYWm108VXia2CXvo6QZp0H2-JusAWjSSPJGo_QnpsYa3Taii_IBgy_1PpFuvzDeZ9QKTE-acckQEzMq4jr2-x09-6-JqFI61AU1XctnomxXgK_KOVbtHXIJQjFS8oEoddqSABi7IhxkwctNHsenm4zdic9UQcxa9UgXF4GtYmktrjKQt7WKdKUbxYn_QZzaC3NY3bubv3pF5UB6FDEOpbhehIjMaQa65zp38rFBJ9q3R12ZhnZHrReBqH0WqmKNLlmL1IU9sXaMXTFmntj2B0VJzt-7BIwRVkoX27ny5OharThw-qCGxM3LWPOhkkTIxhyRRCDwemwb3D4QVUX9XYkmo_FFUWxMUPyZENv89Q6600QvJvOMSg_5XwXbHJWQO59nsuouBS1Le9JLJ3-J3rPAI4b6HEyI3Rh8PkLrePD12VOtKIWTXxvct3qF4ZVf7sRrEqGkymmSTOlxxwD1oQuoXIa-ZlCRnlf86JbzEIef2de3y6fF9EpatQZYZgwtH5eu-b1MP2BfoAxKRdnsxaW2n-7uOlKYalB2r640oka0hyRK4rk1mUFq7x3sdOuXuVqh40fIaieL41CLZOEIi8SLk6ZViDZKPU4hm_67RzWSnKszJH0iGtwutbVf3gRhz_gyunhwHUMXJZJkBtO3CqUBS9OWx-HTFgtwpLpGkIBn7_SC9XmKMSge45mnhMy12YPdrQ_2RX7ei9UR4IU1b9uvfP-krLDbwvrSd3nQb2DDc6_PJ6mfGvUy_vab_SD9o0jWZbNFdmqP4gVAn_MSW-LwefvcrWS5uTNInmkcl22pzfLmhjpvzrUo1uI4uNTyAdjTPVrU7-EOLPYi7QxHBfzIE-NyE4qU_8c3ZEu3LSwHTAuQPwqbqgbWu-sDwzgiRtqOLtPCbJxHpkbLFabZ1kBCgfbDEmijXd2ZjHXr9_PKNAyqijYm_BvS1fUpfkp7DbqZjVsz0I4Aye88DSNE3lrZY_WPNXRr4Bw_DVjav3hKFebib4RkwNWSpwe74NNFVpniVTmmzNsSk2t8N12wIlQbK3jwa6DvrWU03cNvqKdNJYXpNgJf8nAGmkLFUUPekoWlJKLrm8FhwTbGPCoTvXz0KVR6jA3ZaM7iibKYJeJyuGgrG5G8uIyKd8_vxl_0-ixJ_Cqe0swITZOd0QWWkkCpVeuZh8TE5g5QTCqCjRC53xhP5fSRiokmYhlUyGMe7Oj0TCZnep9C4wGAKWyDejTPg_4EF77K4I2XunjKntumh9E6HbryOv3YVcbjmhX8GszPzrkzmRataaCrONmw4noQUjCASI3q56kbiFdx1j0yZL9mfQiiVNF8zo6h2EVLUeliiYnJX5C3S1cY-TVpWGlZxUt0ZxjJfXpOfciRgEfVEPPuTZENGp9cEEEaBdmkTEbBGfwzm_Z1INB9qdJXkKlTnxyDAKi6hCDF8-FaRG2h75aNKWoBqiqTMQCld9AmDLlgx1ajYl78ksnPdAaprQ_fgLbZVXE7H2qFW3FYMsTLOZI6UD07YqeByYBtzdvFZNsxGNw1t9gZ8wnw4qDHWQteWCxK-YcQpm2AXGk64FIbRHeLNblAHCfCsVrPPgf79DP-0tu2dv6Pe2DNifndz3jkaFAzVK0LCS9WMBBG_XwKzQ3Om7HqjW-5MUhqFRvy7j1aD-szAvhUFTG8qqA-VpQVocS75tWltzyOZ9JKaxfDrliTboYGxQ3dwBslkvePWyOSNXm_OzU2aTcbJBAIBocIhHXGRQLQzbjKnYl0rloEJeSSw_fkL-JtIhaCm_7tA5kZYd-1xYAPfly0PjZu6Wwly6jAPRfQt7y8b5L_mG0xovQXwW-0PCtXT_HP3QlvmGf5-INCc2FQNtTpYWPgdK2kIsHK3DPv0TvwCFNE_9fXpkPq-0VvvVdOIJYFhg_cd3zhWzdP11ofDYSVyvGVDWBv-JqIO2esZD5g8H2ixVkNCE1rZl1akOIz7Iu-_rAFzLFGQLVJNwDIrJXV2Si8WRK6zRLKAZFYYOUcB7GFypJo5ByJMZKIBPcfkrU8oK520nn_DgUlMhjx6vEyBVJPgsjlar7eTWfc8d04CvHX_GfQpl-AGnfTvmaWSW_q-g-kvTN7hJrQanbxAlzMU71Eezg4QVZ19VPqGB3Dx3I_oHB2gfRs07FLGW5E4TJ4CiY5rb806EjVnwM2NWeDMbdKMQYGZsBEvVwjoNE9nVWhGoTnx0k5KH-L6yU1zHf2zCv_1h-a6zKPHCJol4mYmtrAl0Kb69uk8G5ax7-TURt_0SWZdyxGTEUcKx0GFGd872ydT9sjh6bpQkdO0SZUUNC7j-z390sVvmM8DPOXPpZXDPN41gdzhdDpvVu3IZ9ZhqIgI4B6YwoO9bt4VBdeR7aXGOGD68dnrfGG-JHcmxM0f3uwAuf9VVvvZg9xRAxR3L1Gbz3h_0UhMZNIGgo_12LXb0itJzQT4RkcCrD5h6pAq23_1d91lPIXeiIr710rGjebQ0fH8Lgyaqb4tKqCQxApx6YKeZktAOQ6Qkhlxsuq7OBVAgGGwnaMZ0mL1NOCpEW9naoVhq2e3kjiIfkz0aNbO5vrUFtVrWZm24mPNdcO-ErDoirFHiOJ_TZ2exmi0Ce1NXPXIFY9_1knOuMGvFPOTI1GVUP3Mu0y9CYlvf68rlYu-ktpLTUvHi0ec0W1T50Rc6-e7A_ZU0U3dwHyFQz5ictEtV4jg0YhCRVvKZMA6tKYpPf8nckUrtnv8HnjcaRK-R6ICLC9CCVD90zt2oMDLZ1tKohgepjwCU7kKCKPl7OJM-o42xBHei1PRaXK4rA7TVtVQhqGflu4cKKQDTrsWy4UkYukdZQon-YGWamAzJHmAVscCrvSOPpa5vb7EOK9lDKsNBsFEHWget1FUkEAe93sHXUcO1EcdfQOYYb1MrLq4-Wg0o0LZPxkIZnRchAW5GZaTPWJ80oLMxOCJQ_lZLHI_sYPiFgJnWb6esrvXKufUrJSgBK-XvmKQbLbkqRVQE07RtQj8aG4oguksskG6De0egc0kM_sl5nO5EoaWjlnfkEkwTjxpvsxp_lMKE3Z2dQjaaW_v15kzTYAS2AOOhWzMlP6s3PEh9W57K8txoYdUwjhwkcXtsA2DkWEDitVTIvFHX2DvKIVAQ==
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
from random import sample
|
|
2
|
-
from .console import console
|
|
3
|
-
from rich.table import Table
|
|
4
|
-
from rich.text import Text
|
|
5
|
-
from rich.panel import Panel
|
|
6
|
-
from rich.layout import Layout
|
|
7
|
-
from rich.padding import Padding
|
|
8
|
-
from typer import Typer
|
|
9
|
-
|
|
10
|
-
import textwrap
|
|
11
|
-
|
|
12
|
-
app = Typer()
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def get_quotes():
|
|
16
|
-
import json
|
|
17
|
-
from pathlib import Path
|
|
18
|
-
|
|
19
|
-
root = Path(__file__).parent
|
|
20
|
-
if not (root / "quotes_decrypted.json").exists():
|
|
21
|
-
from cryptography.fernet import Fernet
|
|
22
|
-
|
|
23
|
-
key = console.input("[bold]Enter passkey:\n")
|
|
24
|
-
|
|
25
|
-
with open(root / "quotes_encrypted.py", "rb") as f:
|
|
26
|
-
encrypted = f.read()
|
|
27
|
-
|
|
28
|
-
fernet = Fernet(key)
|
|
29
|
-
decrypted = fernet.decrypt(encrypted)
|
|
30
|
-
|
|
31
|
-
with open(root / "quotes_decrypted.json", "wt") as f:
|
|
32
|
-
encrypted = f.write(decrypted.decode())
|
|
33
|
-
|
|
34
|
-
payload = json.loads(decrypted.decode())
|
|
35
|
-
return payload
|
|
36
|
-
|
|
37
|
-
else:
|
|
38
|
-
return json.load(open(root / "quotes_decrypted.json", "rt"))
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@app.callback(invoke_without_command=True)
|
|
42
|
-
def print_random_quote():
|
|
43
|
-
|
|
44
|
-
width = console.size.width
|
|
45
|
-
|
|
46
|
-
if width < 12 + PADDING * 2:
|
|
47
|
-
return
|
|
48
|
-
|
|
49
|
-
quote = sample(QUOTES, 1)[0]
|
|
50
|
-
index = QUOTES.index(quote)
|
|
51
|
-
|
|
52
|
-
lines = [""]
|
|
53
|
-
|
|
54
|
-
paragraphs = [""]
|
|
55
|
-
for c in quote:
|
|
56
|
-
if c == "\n":
|
|
57
|
-
paragraphs.append("")
|
|
58
|
-
else:
|
|
59
|
-
paragraphs[-1] += c
|
|
60
|
-
|
|
61
|
-
for i, paragraph in enumerate(paragraphs):
|
|
62
|
-
|
|
63
|
-
if len(paragraph) == 0:
|
|
64
|
-
lines.append("")
|
|
65
|
-
continue
|
|
66
|
-
|
|
67
|
-
if i == 0:
|
|
68
|
-
first_padding = " " * PADDING
|
|
69
|
-
else:
|
|
70
|
-
first_padding = " " * (PADDING + 1)
|
|
71
|
-
|
|
72
|
-
subsequent_padding = " " * (PADDING + 1)
|
|
73
|
-
|
|
74
|
-
if i == 0:
|
|
75
|
-
paragraph = f'"{paragraph}'
|
|
76
|
-
|
|
77
|
-
if i + 1 == len(paragraphs):
|
|
78
|
-
paragraph = f'{paragraph}"'
|
|
79
|
-
|
|
80
|
-
lines += textwrap.wrap(
|
|
81
|
-
paragraph,
|
|
82
|
-
width=width - PADDING,
|
|
83
|
-
replace_whitespace=False,
|
|
84
|
-
initial_indent=first_padding,
|
|
85
|
-
subsequent_indent=subsequent_padding,
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
lines.append("")
|
|
89
|
-
|
|
90
|
-
longest = max(len(l) for l in lines)
|
|
91
|
-
signoff = f"-CONFUCIUS #{index+1}"
|
|
92
|
-
lines.append(" " * (longest - len(signoff)) + f"[italic]{signoff}[reset]")
|
|
93
|
-
|
|
94
|
-
lines.append("")
|
|
95
|
-
|
|
96
|
-
for line in lines:
|
|
97
|
-
console.print(line, highlight=False)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
PADDING = 6
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def main():
|
|
104
|
-
global QUOTES
|
|
105
|
-
QUOTES = get_quotes()
|
|
106
|
-
app()
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if __name__ == "__main__":
|
|
110
|
-
global QUOTES
|
|
111
|
-
QUOTES = get_quotes()
|
|
112
|
-
app()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|