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.
@@ -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}")