splent-cli 0.0.1__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.
- splent_cli/__init__.py +6 -0
- splent_cli/__main__.py +11 -0
- splent_cli/cli.py +59 -0
- splent_cli/commands/__init__.py +0 -0
- splent_cli/commands/clear_cache.py +90 -0
- splent_cli/commands/clear_log.py +31 -0
- splent_cli/commands/clear_uploads.py +44 -0
- splent_cli/commands/compose_env.py +50 -0
- splent_cli/commands/coverage.py +46 -0
- splent_cli/commands/db_console.py +27 -0
- splent_cli/commands/db_dump.py +45 -0
- splent_cli/commands/db_migrate.py +37 -0
- splent_cli/commands/db_reset.py +90 -0
- splent_cli/commands/db_seed.py +123 -0
- splent_cli/commands/env.py +18 -0
- splent_cli/commands/info.py +74 -0
- splent_cli/commands/linter.py +146 -0
- splent_cli/commands/locust.py +219 -0
- splent_cli/commands/module_create.py +138 -0
- splent_cli/commands/module_list.py +28 -0
- splent_cli/commands/route_list.py +68 -0
- splent_cli/commands/selenium.py +85 -0
- splent_cli/commands/test.py +45 -0
- splent_cli/commands/update.py +159 -0
- splent_cli/commands/webpack_compile.py +68 -0
- splent_cli/utils/__init__.py +0 -0
- splent_cli/utils/path_utils.py +116 -0
- splent_cli-0.0.1.dist-info/LICENSE +113 -0
- splent_cli-0.0.1.dist-info/METADATA +133 -0
- splent_cli-0.0.1.dist-info/RECORD +33 -0
- splent_cli-0.0.1.dist-info/WHEEL +5 -0
- splent_cli-0.0.1.dist-info/entry_points.txt +2 -0
- splent_cli-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import base64
|
|
3
|
+
import pkg_resources
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_metadata_value(metadata_lines, key):
|
|
7
|
+
default_value = f"{key}: Unknown"
|
|
8
|
+
line = next(
|
|
9
|
+
(line for line in metadata_lines if line.startswith(key)),
|
|
10
|
+
default_value,
|
|
11
|
+
)
|
|
12
|
+
return (
|
|
13
|
+
line.split(":", 1)[1].strip()
|
|
14
|
+
if line != default_value
|
|
15
|
+
else default_value.split(":", 1)[1].strip()
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@click.command()
|
|
20
|
+
def info():
|
|
21
|
+
"""Displays information about the Rosemary CLI."""
|
|
22
|
+
distribution = pkg_resources.get_distribution("splent_cli")
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
metadata = distribution.get_metadata_lines("METADATA")
|
|
26
|
+
author = get_metadata_value(metadata, "Author")
|
|
27
|
+
author_email = get_metadata_value(metadata, "Author-email")
|
|
28
|
+
description = get_metadata_value(metadata, "Summary")
|
|
29
|
+
except FileNotFoundError:
|
|
30
|
+
author, author_email, description = (
|
|
31
|
+
"Unknown",
|
|
32
|
+
"Unknown",
|
|
33
|
+
"Not available",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
name = distribution.project_name
|
|
37
|
+
version = distribution.version
|
|
38
|
+
|
|
39
|
+
click.echo(f"Name: {name}")
|
|
40
|
+
click.echo(f"Version: {version}")
|
|
41
|
+
click.echo(f"Author: {author}")
|
|
42
|
+
click.echo(f"Author-email: {author_email}")
|
|
43
|
+
click.echo(f"Description: {description}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@click.command("love:me", hidden=True)
|
|
47
|
+
@click.option("--again", is_flag=True)
|
|
48
|
+
def info2(again):
|
|
49
|
+
if not again:
|
|
50
|
+
click.echo(click.style("Love me --again?", fg="magenta"))
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
lyrics = "ICAgIA0KICAgIEtub3cgSSd2ZSBkb25lIHdyb25nLA0KICAgIExlZnQgeW91ciBoZWFydCB0b3JuDQogICAgSXMgdGhhdCB3aGF0IGRldmlscyBkbz8NCiAgICBUb29rIHlvdSBzbyBsb3csDQogICAgV2hlcmUgb25seSBmb29scyBnbw0KICAgIEkgc2hvb2sgdGhlIGFuZ2VsIGluIHlvdSENCiAgICANCiAgICBOb3cgSSdtIHJpc2luZyBmcm9tIHRoZSBncm91bmQNCiAgICBSaXNpbmcgdXAgdG8geW91IQ0KICAgIEZpbGxlZCB3aXRoIGFsbCB0aGUgc3RyZW5ndGggSSd2ZSBmb3VuZCwNCiAgICBUaGVyZSdzIG5vdGhpbmcgSSBjYW4ndCBkbyENCiAgICANCiAgICBJIG5lZWQgdG8ga25vdyBub3csIGtub3cgbm93LCBjYW4geW91IGxvdmUgbWUgYWdhaW4/DQogICAgSSBuZWVkIHRvIGtub3cgbm93LCBrbm93IG5vdywgY2FuIHlvdSBsb3ZlIG1lIGFnYWluPw0KICAgIEkgbmVlZCB0byBrbm93IG5vdywga25vdyBub3csIGNhbiB5b3UgbG92ZSBtZSBhZ2Fpbj8NCiAgICBJIG5lZWQgdG8ga25vdyBub3csIGtub3cgbm93LCBjYW4geW91IGxvdmUgbWUgYWdhaW4/DQogICAgDQogICAgSSdsbCBzcGluIHlvdSBhcm91bmQsIHdvbid0IGxldCB5b3UgZmFsbCBkb3duLA0KICAgIFdvdWxkIHlvdSBsZXQgbWUgZG93bj8gTm8hDQogICAgSSdsbCBzcGluIHlvdSBhcm91bmQsIHdvbid0IGxldCB5b3UgZmFsbCBkb3duLA0KICAgIFdvdWxkIHlvdSBsZXQgbWUgZG93bj8gTm8hDQogICAgDQogICAgTm93IEknbSByaXNpbmcgZnJvbSB0aGUgZ3JvdW5kDQogICAgUmlzaW5nIHVwIHRvIHlvdSENCiAgICBGaWxsZWQgd2l0aCBhbGwgdGhlIHN0cmVuZ3RoIEkndmUgZm91bmQsDQogICAgVGhlcmUncyBub3RoaW5nIEkgY2FuJ3QgZG8hDQogICAgDQogICAgSSBuZWVkIHRvIGtub3cgbm93LCBrbm93IG5vdywgY2FuIHlvdSBsb3ZlIG1lIGFnYWluPw0KICAgIEkgbmVlZCB0byBrbm93IG5vdywga25vdyBub3csIGNhbiB5b3UgbG92ZSBtZSBhZ2Fpbj8NCiAgICBJIG5lZWQgdG8ga25vdyBub3csIGtub3cgbm93LCBjYW4geW91IGxvdmUgbWUgYWdhaW4/DQogICAgSSBuZWVkIHRvIGtub3cgbm93LCBrbm93IG5vdywgY2FuIHlvdSBsb3ZlIG1lIGFnYWluPw0KDQogICAgQ29uZ3JhdHVsYXRpb25zLCB5b3UgZm91bmQgdGhlIGVhc3RlciBlZ2ch" # noqa
|
|
54
|
+
|
|
55
|
+
decoded = decode_lyrics(lyrics)
|
|
56
|
+
colored_lyrics = colorize_lyrics(decoded)
|
|
57
|
+
click.echo(colored_lyrics)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def colorize_lyrics(lyrics):
|
|
61
|
+
colored_lyrics = ""
|
|
62
|
+
colors = ["red", "green", "yellow", "blue", "magenta", "cyan", "white"]
|
|
63
|
+
|
|
64
|
+
for i, line in enumerate(lyrics.splitlines()):
|
|
65
|
+
colored_lyrics += click.style(line, fg=colors[i % len(colors)]) + "\n"
|
|
66
|
+
|
|
67
|
+
return colored_lyrics
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def decode_lyrics(encoded_lyrics):
|
|
71
|
+
decoded_lyrics = base64.b64decode(encoded_lyrics.encode("utf-8")).decode(
|
|
72
|
+
"utf-8"
|
|
73
|
+
)
|
|
74
|
+
return decoded_lyrics
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
from splent_cli.utils.path_utils import PathUtils
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.command("linter", help="Runs Ruff linter on the project.")
|
|
8
|
+
def linter():
|
|
9
|
+
"""Ejecuta Ruff para analizar el código en busca de errores."""
|
|
10
|
+
directories = [
|
|
11
|
+
PathUtils.get_app_dir(),
|
|
12
|
+
PathUtils.get_splent_cli_dir(),
|
|
13
|
+
PathUtils.get_core_dir(),
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
click.echo(
|
|
17
|
+
click.style("\n📌 Running Ruff Linter...\n", fg="cyan", bold=True)
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
for directory in directories:
|
|
21
|
+
click.echo(
|
|
22
|
+
click.style(
|
|
23
|
+
f"🔍 Checking {directory}...\n", fg="yellow", bold=True
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
result = subprocess.run(
|
|
27
|
+
["ruff", "check", directory], capture_output=True, text=True
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if result.returncode != 0:
|
|
31
|
+
click.echo(click.style(result.stdout, fg="red"))
|
|
32
|
+
click.echo(
|
|
33
|
+
click.style(
|
|
34
|
+
f"❌ Issues found in {directory}.\n", fg="red", bold=True
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
else:
|
|
38
|
+
click.echo(
|
|
39
|
+
click.style(
|
|
40
|
+
f"✅ No issues found in {directory}. 🎉\n",
|
|
41
|
+
fg="green",
|
|
42
|
+
bold=True,
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
click.echo(
|
|
47
|
+
click.style("✔️ Linter check completed!\n", fg="cyan", bold=True)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@click.command(
|
|
52
|
+
"linter:fix",
|
|
53
|
+
help="Automatically formats and fixes code using Ruff and Black.",
|
|
54
|
+
)
|
|
55
|
+
def linter_fix():
|
|
56
|
+
"""Ejecuta Ruff en modo 'fix' y luego Black para formateo."""
|
|
57
|
+
directories = [
|
|
58
|
+
PathUtils.get_app_dir(),
|
|
59
|
+
PathUtils.get_splent_cli_dir(),
|
|
60
|
+
PathUtils.get_core_dir(),
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
click.echo(
|
|
64
|
+
click.style(
|
|
65
|
+
"\n📌 Running Ruff Fix & Black Formatter...\n",
|
|
66
|
+
fg="cyan",
|
|
67
|
+
bold=True,
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
fixes_applied = 0
|
|
72
|
+
|
|
73
|
+
for directory in directories:
|
|
74
|
+
click.echo(
|
|
75
|
+
click.style(
|
|
76
|
+
f"🔧 Fixing {directory} with Ruff...\n", fg="yellow", bold=True
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
result = subprocess.run(
|
|
80
|
+
["ruff", "check", "--fix", directory],
|
|
81
|
+
capture_output=True,
|
|
82
|
+
text=True,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if "Fixed" in result.stdout:
|
|
86
|
+
click.echo(click.style(result.stdout, fg="blue"))
|
|
87
|
+
fixes_applied += 1
|
|
88
|
+
else:
|
|
89
|
+
click.echo(
|
|
90
|
+
click.style(
|
|
91
|
+
f"✅ No fixes needed in {directory}. 🎉\n",
|
|
92
|
+
fg="green",
|
|
93
|
+
bold=True,
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
click.echo(
|
|
98
|
+
click.style(
|
|
99
|
+
f"🎨 Formatting {directory} with Black...\n",
|
|
100
|
+
fg="yellow",
|
|
101
|
+
bold=True,
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
black_result = subprocess.run(
|
|
105
|
+
["black", "--line-length=79", directory],
|
|
106
|
+
capture_output=True,
|
|
107
|
+
text=True,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
if black_result.returncode == 0:
|
|
111
|
+
click.echo(
|
|
112
|
+
click.style(
|
|
113
|
+
f"✨ Code in {directory} formatted successfully!\n",
|
|
114
|
+
fg="green",
|
|
115
|
+
bold=True,
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
else:
|
|
119
|
+
click.echo(
|
|
120
|
+
click.style(
|
|
121
|
+
f"❌ Failed to format {directory} with Black.\n",
|
|
122
|
+
fg="red",
|
|
123
|
+
bold=True,
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
click.echo(
|
|
128
|
+
click.style("\n✔️ Fix & Formatting Completed!", fg="cyan", bold=True)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if fixes_applied > 0:
|
|
132
|
+
click.echo(
|
|
133
|
+
click.style(
|
|
134
|
+
f"🔧 Ruff applied fixes in {fixes_applied} directories!",
|
|
135
|
+
fg="blue",
|
|
136
|
+
bold=True,
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
click.echo(
|
|
141
|
+
click.style(
|
|
142
|
+
"🚀 No fixes were needed! Your code is already clean! 🎉",
|
|
143
|
+
fg="green",
|
|
144
|
+
bold=True,
|
|
145
|
+
)
|
|
146
|
+
)
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import click
|
|
4
|
+
import docker
|
|
5
|
+
import signal
|
|
6
|
+
import psutil
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.command(
|
|
10
|
+
"locust", help="Launches Locust for load testing based on the environment."
|
|
11
|
+
)
|
|
12
|
+
@click.argument("module", required=False)
|
|
13
|
+
def locust(module):
|
|
14
|
+
|
|
15
|
+
# Absolute paths
|
|
16
|
+
working_dir = os.getenv("WORKING_DIR", "")
|
|
17
|
+
core_dir = os.path.join(working_dir, "core")
|
|
18
|
+
docker_dir = os.path.join(working_dir, "docker/")
|
|
19
|
+
modules_dir = os.path.join(working_dir, "app/modules")
|
|
20
|
+
|
|
21
|
+
def validate_module(module):
|
|
22
|
+
"""Check if the module exists."""
|
|
23
|
+
if module:
|
|
24
|
+
module_path = os.path.join(modules_dir, module)
|
|
25
|
+
if not os.path.exists(module_path):
|
|
26
|
+
raise click.UsageError(f"module '{module}' does not exist.")
|
|
27
|
+
locustfile_path = os.path.join(
|
|
28
|
+
module_path, "tests", "locustfile.py"
|
|
29
|
+
)
|
|
30
|
+
if not os.path.exists(locustfile_path):
|
|
31
|
+
raise click.UsageError(
|
|
32
|
+
f"Locustfile for module '{module}' does not exist at path "
|
|
33
|
+
f"'{locustfile_path}'."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def run_docker_locust(volume_name, module):
|
|
37
|
+
"""Build and run the Locust container with the specified volume."""
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
# Check if the container already exists
|
|
41
|
+
client.containers.get("locust_container")
|
|
42
|
+
click.echo("Locust container is already running.")
|
|
43
|
+
return
|
|
44
|
+
except docker.errors.NotFound:
|
|
45
|
+
pass # Container does not exist, proceed to create it
|
|
46
|
+
|
|
47
|
+
click.echo(
|
|
48
|
+
f"Starting Locust in Docker environment on port 8089 with volume: {volume_name}..."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Build Locust's image
|
|
52
|
+
build_command = [
|
|
53
|
+
"docker",
|
|
54
|
+
"build",
|
|
55
|
+
"-f",
|
|
56
|
+
os.path.join(docker_dir, "images/Dockerfile.locust"),
|
|
57
|
+
"-t",
|
|
58
|
+
"locust-image",
|
|
59
|
+
".",
|
|
60
|
+
]
|
|
61
|
+
click.echo(f"Build command: {' '.join(build_command)}")
|
|
62
|
+
subprocess.run(build_command, check=True)
|
|
63
|
+
|
|
64
|
+
# Define the locustfile path
|
|
65
|
+
locustfile_path = os.path.join(
|
|
66
|
+
core_dir, "bootstraps/locustfile_bootstrap.py"
|
|
67
|
+
)
|
|
68
|
+
if module:
|
|
69
|
+
locustfile_path = f"{modules_dir}/{module}/tests/locustfile.py"
|
|
70
|
+
|
|
71
|
+
# Run the Locust container
|
|
72
|
+
up_command = [
|
|
73
|
+
"docker",
|
|
74
|
+
"run",
|
|
75
|
+
"-d",
|
|
76
|
+
"-p",
|
|
77
|
+
"8089:8089",
|
|
78
|
+
"-v",
|
|
79
|
+
f"{volume_name}:/app",
|
|
80
|
+
"--name",
|
|
81
|
+
"locust_container",
|
|
82
|
+
"--network",
|
|
83
|
+
"docker_flasky_network",
|
|
84
|
+
"locust-image",
|
|
85
|
+
"-f",
|
|
86
|
+
locustfile_path,
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
click.echo(f"Docker Run command: {' '.join(up_command)}")
|
|
90
|
+
subprocess.run(up_command, check=True)
|
|
91
|
+
click.echo(
|
|
92
|
+
click.style(
|
|
93
|
+
"Locust is running at http://localhost:8089", fg="green"
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def is_locust_running():
|
|
98
|
+
"""Check if Locust is already running."""
|
|
99
|
+
for proc in psutil.process_iter(["pid", "name"]):
|
|
100
|
+
if proc.info["name"] == "locust":
|
|
101
|
+
return True
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
def run_in_console(module):
|
|
105
|
+
|
|
106
|
+
if is_locust_running():
|
|
107
|
+
click.echo("Locust is already running.")
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
locustfile_path = os.path.join(
|
|
111
|
+
core_dir, "bootstraps/locustfile_bootstrap.py"
|
|
112
|
+
)
|
|
113
|
+
if module:
|
|
114
|
+
locustfile_path = os.path.join(
|
|
115
|
+
modules_dir, module, "tests", "locustfile.py"
|
|
116
|
+
)
|
|
117
|
+
locust_command = ["locust", "-f", locustfile_path]
|
|
118
|
+
click.echo(f"Locust command: {' '.join(locust_command)}")
|
|
119
|
+
subprocess.Popen(
|
|
120
|
+
locust_command,
|
|
121
|
+
stdin=subprocess.PIPE,
|
|
122
|
+
stdout=subprocess.DEVNULL,
|
|
123
|
+
stderr=subprocess.DEVNULL,
|
|
124
|
+
)
|
|
125
|
+
click.echo(
|
|
126
|
+
click.style(
|
|
127
|
+
"Locust is running at http://localhost:8089", fg="green"
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def run_local_locust(module):
|
|
132
|
+
"""Run Locust in the local environment."""
|
|
133
|
+
click.echo("Starting Locust in local environment on port 8089...")
|
|
134
|
+
run_in_console(module)
|
|
135
|
+
|
|
136
|
+
def run_vagrant_locust(module):
|
|
137
|
+
"""Run Locust in the Vagrant environment."""
|
|
138
|
+
click.echo("Starting Locust in Vagrant environment on port 8089...")
|
|
139
|
+
run_in_console(module)
|
|
140
|
+
|
|
141
|
+
# Validate module if provided
|
|
142
|
+
if module:
|
|
143
|
+
validate_module(module)
|
|
144
|
+
|
|
145
|
+
if working_dir == "/app/":
|
|
146
|
+
client = docker.from_env()
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
web_container = client.containers.get("web_app_container")
|
|
150
|
+
volume_name = next(
|
|
151
|
+
(
|
|
152
|
+
mount.get("Name") or mount.get("Source")
|
|
153
|
+
for mount in web_container.attrs["Mounts"]
|
|
154
|
+
if mount["Destination"] == "/app"
|
|
155
|
+
),
|
|
156
|
+
None,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if not volume_name:
|
|
160
|
+
raise ValueError(
|
|
161
|
+
"No volume or bind mount found mounted on /app"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
run_docker_locust(volume_name, module)
|
|
165
|
+
|
|
166
|
+
except docker.errors.NotFound:
|
|
167
|
+
click.echo(click.style("Web container not found.", fg="red"))
|
|
168
|
+
except Exception as e:
|
|
169
|
+
click.echo(click.style(f"An error occurred: {str(e)}", fg="red"))
|
|
170
|
+
|
|
171
|
+
elif working_dir == "":
|
|
172
|
+
run_local_locust(module)
|
|
173
|
+
|
|
174
|
+
elif working_dir == "/vagrant/":
|
|
175
|
+
run_vagrant_locust(module)
|
|
176
|
+
|
|
177
|
+
else:
|
|
178
|
+
click.echo(
|
|
179
|
+
click.style(f"Unrecognized WORKING_DIR: {working_dir}", fg="red")
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@click.command(
|
|
184
|
+
"locust:stop", help="Stops the Locust container if it is running."
|
|
185
|
+
)
|
|
186
|
+
def stop():
|
|
187
|
+
working_dir = os.getenv("WORKING_DIR", "")
|
|
188
|
+
|
|
189
|
+
def stop_local_locust():
|
|
190
|
+
"""Stop Locust process in the local environment."""
|
|
191
|
+
click.echo("Stopping Locust in local environment...")
|
|
192
|
+
for proc in psutil.process_iter(["pid", "name"]):
|
|
193
|
+
if proc.info["name"] == "locust":
|
|
194
|
+
click.echo(
|
|
195
|
+
f"Stopping Locust process with PID {proc.info['pid']}..."
|
|
196
|
+
)
|
|
197
|
+
os.kill(proc.info["pid"], signal.SIGTERM)
|
|
198
|
+
|
|
199
|
+
def stop_docker_locust():
|
|
200
|
+
click.echo("Stopping Locust container if it is running...")
|
|
201
|
+
stop_command = ["docker", "stop", "locust_container"]
|
|
202
|
+
rm_command = ["docker", "rm", "locust_container"]
|
|
203
|
+
|
|
204
|
+
# Stop the Locust container if it is running
|
|
205
|
+
subprocess.run(stop_command)
|
|
206
|
+
|
|
207
|
+
# Remove the Locust container
|
|
208
|
+
subprocess.run(rm_command)
|
|
209
|
+
|
|
210
|
+
if working_dir == "/app/":
|
|
211
|
+
stop_docker_locust()
|
|
212
|
+
|
|
213
|
+
elif working_dir == "" or working_dir == "/vagrant/":
|
|
214
|
+
stop_local_locust()
|
|
215
|
+
|
|
216
|
+
else:
|
|
217
|
+
click.echo(
|
|
218
|
+
click.style(f"Unrecognized WORKING_DIR: {working_dir}", fg="red")
|
|
219
|
+
)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import stat
|
|
2
|
+
import click
|
|
3
|
+
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
from splent_cli.utils.path_utils import PathUtils
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def pascalcase(s):
|
|
10
|
+
"""Converts string to PascalCase."""
|
|
11
|
+
return "".join(word.capitalize() for word in s.split("_"))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def setup_jinja_env():
|
|
15
|
+
"""Configures and returns a Jinja environment."""
|
|
16
|
+
env = Environment(
|
|
17
|
+
loader=FileSystemLoader(
|
|
18
|
+
searchpath=PathUtils.get_splent_cli_templates_dir()
|
|
19
|
+
),
|
|
20
|
+
autoescape=select_autoescape(["html", "xml", "j2"]),
|
|
21
|
+
)
|
|
22
|
+
env.filters["pascalcase"] = pascalcase
|
|
23
|
+
return env
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def render_and_write_file(env, template_name, filename, context):
|
|
27
|
+
"""Renders a template and writes it to a specified file."""
|
|
28
|
+
template = env.get_template(template_name)
|
|
29
|
+
content = template.render(context) + "\n"
|
|
30
|
+
with open(filename, "w") as f:
|
|
31
|
+
f.write(content)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@click.command("module:create", help="Creates a new module with a given name.")
|
|
35
|
+
@click.argument("name")
|
|
36
|
+
def make_module(name):
|
|
37
|
+
modules_dir = PathUtils.get_modules_dir()
|
|
38
|
+
module_path = f"{modules_dir}/{name}"
|
|
39
|
+
|
|
40
|
+
if os.path.exists(module_path):
|
|
41
|
+
click.echo(
|
|
42
|
+
click.style(f"The module '{name}' already exists.", fg="red")
|
|
43
|
+
)
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
env = setup_jinja_env()
|
|
47
|
+
|
|
48
|
+
# Defines the directories to create.
|
|
49
|
+
directories = {"templates"}
|
|
50
|
+
|
|
51
|
+
files_and_templates = {
|
|
52
|
+
"__init__.py": "module_init.py.j2",
|
|
53
|
+
"routes.py": "module_routes.py.j2",
|
|
54
|
+
"models.py": "module_models.py.j2",
|
|
55
|
+
"repositories.py": "module_repositories.py.j2",
|
|
56
|
+
"services.py": "module_services.py.j2",
|
|
57
|
+
"forms.py": "module_forms.py.j2",
|
|
58
|
+
"seeders.py": "module_seeders.py.j2",
|
|
59
|
+
os.path.join(
|
|
60
|
+
"templates", name, "index.html"
|
|
61
|
+
): "module_templates_index.html.j2",
|
|
62
|
+
"assets/js/scripts.js": "module_scripts.js.j2",
|
|
63
|
+
"assets/js/webpack.config.js": "module_webpack.config.js.j2",
|
|
64
|
+
"tests/test_unit.py": "module_tests_test_unit.py.j2",
|
|
65
|
+
"tests/locustfile.py": "module_tests_locustfile.py.j2",
|
|
66
|
+
"tests/test_selenium.py": "module_tests_test_selenium.py.j2",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Create the necessary directories, explicitly excluding 'tests' from the creation of subfolders.
|
|
70
|
+
for directory in directories:
|
|
71
|
+
os.makedirs(os.path.join(module_path, directory, name), exist_ok=True)
|
|
72
|
+
|
|
73
|
+
# Create 'tests' directory directly under module_path, without additional subfolders.
|
|
74
|
+
os.makedirs(os.path.join(module_path, "tests"), exist_ok=True)
|
|
75
|
+
|
|
76
|
+
# Create 'assets' directory directly under module_path
|
|
77
|
+
os.makedirs(os.path.join(module_path, "assets", "css"), exist_ok=True)
|
|
78
|
+
os.makedirs(os.path.join(module_path, "assets", "js"), exist_ok=True)
|
|
79
|
+
|
|
80
|
+
# Create empty __init__.py file directly in the 'tests' directory.
|
|
81
|
+
open(os.path.join(module_path, "tests", "__init__.py"), "a").close()
|
|
82
|
+
|
|
83
|
+
# Render and write files, including 'test_unit.py' directly in 'tests'.
|
|
84
|
+
for filename, template_name in files_and_templates.items():
|
|
85
|
+
if template_name: # Check if there is a defined template.
|
|
86
|
+
render_and_write_file(
|
|
87
|
+
env,
|
|
88
|
+
template_name,
|
|
89
|
+
os.path.join(module_path, filename),
|
|
90
|
+
{"module_name": name},
|
|
91
|
+
)
|
|
92
|
+
else:
|
|
93
|
+
open(
|
|
94
|
+
os.path.join(module_path, filename), "a"
|
|
95
|
+
).close() # Create empty file if there is no template.
|
|
96
|
+
|
|
97
|
+
click.echo(
|
|
98
|
+
click.style(f"Module '{name}' created successfully.", fg="green")
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Change the owner of the main folder of the module
|
|
102
|
+
os.chown(module_path, 1000, 1000)
|
|
103
|
+
|
|
104
|
+
# Change permissions to drwxrwxr-x (chmod 775)
|
|
105
|
+
os.chmod(
|
|
106
|
+
module_path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH | stat.S_IXOTH
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Change the owner of all created files and directories to UID 1000 and GID 1000
|
|
110
|
+
uid = 1000
|
|
111
|
+
gid = 1000
|
|
112
|
+
|
|
113
|
+
for root, dirs, files in os.walk(module_path):
|
|
114
|
+
for dir_ in dirs:
|
|
115
|
+
dir_path = os.path.join(root, dir_)
|
|
116
|
+
os.chown(dir_path, uid, gid)
|
|
117
|
+
os.chmod(
|
|
118
|
+
dir_path,
|
|
119
|
+
stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH | stat.S_IXOTH,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
for file_ in files:
|
|
123
|
+
file_path = os.path.join(root, file_)
|
|
124
|
+
os.chown(file_path, uid, gid)
|
|
125
|
+
os.chmod(
|
|
126
|
+
file_path,
|
|
127
|
+
stat.S_IRUSR
|
|
128
|
+
| stat.S_IWUSR
|
|
129
|
+
| stat.S_IRGRP
|
|
130
|
+
| stat.S_IWGRP
|
|
131
|
+
| stat.S_IROTH,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
click.echo(
|
|
135
|
+
click.style(
|
|
136
|
+
f"Module '{name}' permissions changed successfully.", fg="green"
|
|
137
|
+
)
|
|
138
|
+
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from flask.cli import with_appcontext
|
|
3
|
+
from splent_app import splent_app
|
|
4
|
+
from splent_framework.core.managers.module_manager import ModuleManager
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.command(
|
|
8
|
+
"module:list", help="Lists all modules and those ignored by .moduleignore."
|
|
9
|
+
)
|
|
10
|
+
@with_appcontext
|
|
11
|
+
def module_list():
|
|
12
|
+
manager = ModuleManager(splent_app)
|
|
13
|
+
|
|
14
|
+
loaded_modules, ignored_modules = manager.get_modules()
|
|
15
|
+
|
|
16
|
+
click.echo(
|
|
17
|
+
click.style(f"Loaded Modules ({len(loaded_modules)}):", fg="green")
|
|
18
|
+
)
|
|
19
|
+
for module in loaded_modules:
|
|
20
|
+
click.echo(f"- {module}")
|
|
21
|
+
|
|
22
|
+
click.echo(
|
|
23
|
+
click.style(
|
|
24
|
+
f"\nIgnored Modules ({len(ignored_modules)}):", fg="bright_yellow"
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
for module in ignored_modules:
|
|
28
|
+
click.echo(click.style(f"- {module}", fg="bright_yellow"))
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import click
|
|
3
|
+
from flask import current_app
|
|
4
|
+
from flask.cli import with_appcontext
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.command("route:list", help="Lists all routes of the Flask application.")
|
|
9
|
+
@click.argument("module_name", required=False)
|
|
10
|
+
@click.option(
|
|
11
|
+
"--group",
|
|
12
|
+
is_flag=True,
|
|
13
|
+
help="Group routes by module when no specific module is provided.",
|
|
14
|
+
)
|
|
15
|
+
@with_appcontext
|
|
16
|
+
def route_list(module_name, group):
|
|
17
|
+
base_path = os.path.join(os.getenv("WORKING_DIR", ""), "app/modules")
|
|
18
|
+
|
|
19
|
+
# Checks if a module was specified and if it exists
|
|
20
|
+
if module_name:
|
|
21
|
+
module_path = os.path.join(base_path, module_name)
|
|
22
|
+
if not os.path.exists(module_path):
|
|
23
|
+
click.echo(
|
|
24
|
+
click.style(
|
|
25
|
+
f"Module '{module_name}' does not exist.", fg="red"
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
return
|
|
29
|
+
click.echo(f"Listing routes for the '{module_name}' module...")
|
|
30
|
+
# Path filtering for a specific module
|
|
31
|
+
filtered_rules = [
|
|
32
|
+
rule
|
|
33
|
+
for rule in current_app.url_map.iter_rules()
|
|
34
|
+
if rule.endpoint.startswith(f"{module_name}.")
|
|
35
|
+
]
|
|
36
|
+
print_route_table(filtered_rules)
|
|
37
|
+
else:
|
|
38
|
+
if group: # Group routes by module
|
|
39
|
+
click.echo("Listing routes for all modules, grouped by module...")
|
|
40
|
+
rules = sorted(
|
|
41
|
+
current_app.url_map.iter_rules(),
|
|
42
|
+
key=lambda rule: rule.endpoint,
|
|
43
|
+
)
|
|
44
|
+
grouped_rules = defaultdict(list)
|
|
45
|
+
for rule in rules:
|
|
46
|
+
module = rule.endpoint.split(".")[0]
|
|
47
|
+
grouped_rules[module].append(rule)
|
|
48
|
+
|
|
49
|
+
for module, rules in sorted(grouped_rules.items()):
|
|
50
|
+
click.echo(click.style(f"\nModule: {module}", fg="yellow"))
|
|
51
|
+
print_route_table(rules)
|
|
52
|
+
else: # Lists all routes without grouping
|
|
53
|
+
click.echo("Listing routes for all modules...")
|
|
54
|
+
rules = sorted(
|
|
55
|
+
current_app.url_map.iter_rules(),
|
|
56
|
+
key=lambda rule: rule.endpoint,
|
|
57
|
+
)
|
|
58
|
+
print_route_table(rules)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def print_route_table(rules):
|
|
62
|
+
click.echo(f"{'Endpoint':<50} {'Methods':<30} {'Route':<100}")
|
|
63
|
+
click.echo("-" * 180)
|
|
64
|
+
for rule in rules:
|
|
65
|
+
methods = ", ".join(
|
|
66
|
+
sorted(rule.methods.difference({"HEAD", "OPTIONS"}))
|
|
67
|
+
)
|
|
68
|
+
click.echo(f"{rule.endpoint:<50} {methods:<30} {rule.rule:<100}")
|