mega_snake 0.1.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.
Files changed (49) hide show
  1. mega_snake/__init__.py +1 -0
  2. mega_snake/__main__.py +107 -0
  3. mega_snake/config.properties +11 -0
  4. mega_snake/config_environment/__init__.py +1 -0
  5. mega_snake/config_environment/create_working_env.py +437 -0
  6. mega_snake/config_environment/gradle_set.py +209 -0
  7. mega_snake/config_environment/graphql_schema.py +79 -0
  8. mega_snake/config_environment/java_set.py +292 -0
  9. mega_snake/config_environment/local_config.py +72 -0
  10. mega_snake/config_environment/maven_set.py +330 -0
  11. mega_snake/config_environment/models/github_queries.py +78 -0
  12. mega_snake/config_environment/models/log_viewer_watcher.py +70 -0
  13. mega_snake/config_environment/models/tools_version.py +204 -0
  14. mega_snake/config_environment/models/vscode_input.py +117 -0
  15. mega_snake/config_environment/models/vscode_launch.py +140 -0
  16. mega_snake/config_environment/models/vscode_task.py +266 -0
  17. mega_snake/config_environment/module.py +33 -0
  18. mega_snake/config_environment/util.py +61 -0
  19. mega_snake/config_setup.ps1 +44 -0
  20. mega_snake/config_setup.sh +48 -0
  21. mega_snake/constants.py +60 -0
  22. mega_snake/diff_tree/__init__.py +1 -0
  23. mega_snake/diff_tree/file_type.py +81 -0
  24. mega_snake/diff_tree/module.py +154 -0
  25. mega_snake/light_weight/__init__.py +1 -0
  26. mega_snake/light_weight/create_release.py +105 -0
  27. mega_snake/light_weight/echo.py +81 -0
  28. mega_snake/light_weight/jks_expired_certs.py +105 -0
  29. mega_snake/light_weight/module.py +30 -0
  30. mega_snake/light_weight/release.py +132 -0
  31. mega_snake/light_weight/release_handler.py +76 -0
  32. mega_snake/light_weight/shell_init.py +76 -0
  33. mega_snake/remote_branches/__init__.py +1 -0
  34. mega_snake/remote_branches/cleanup_remote_branches.py +63 -0
  35. mega_snake/remote_branches/details_remote_branches.py +101 -0
  36. mega_snake/remote_branches/module.py +24 -0
  37. mega_snake/remote_branches/parse_remote_branches.py +73 -0
  38. mega_snake/remote_branches/remote_branch.py +161 -0
  39. mega_snake/resources/java-formatter.xml +337 -0
  40. mega_snake/util/__init__.py +1 -0
  41. mega_snake/util/cli_group.py +71 -0
  42. mega_snake/util/formatting.py +234 -0
  43. mega_snake/util/props.py +384 -0
  44. mega_snake/util/util.py +247 -0
  45. mega_snake-0.1.0.dist-info/METADATA +258 -0
  46. mega_snake-0.1.0.dist-info/RECORD +49 -0
  47. mega_snake-0.1.0.dist-info/WHEEL +4 -0
  48. mega_snake-0.1.0.dist-info/entry_points.txt +2 -0
  49. mega_snake-0.1.0.dist-info/licenses/LICENSE +201 -0
mega_snake/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """ init file for setup configuration package """
mega_snake/__main__.py ADDED
@@ -0,0 +1,107 @@
1
+ """Sets the environment configuration"""
2
+
3
+ import os
4
+ from typing import Optional
5
+ import sys
6
+ import click
7
+ from .diff_tree.module import main as diff_tree
8
+ from .light_weight.module import main as create_release, add_wrapper as create_release_result_callback
9
+ from .remote_branches.module import main as remote_branches, add_wrapper as remote_branches_result_callback
10
+ from .config_environment.module import main as config_environment, add_wrapper as config_env_result_callback
11
+ from .constants import LOGGING_OPT, SHELL_OPT, APP_NAME
12
+ from .util.formatting import get_traceback
13
+ from .util.props import init_app_properties
14
+ from .util.formatting import WorkspaceError, ws_advice
15
+ from .util.cli_group import CliGroup
16
+
17
+
18
+ @click.group(
19
+ help="""A CLI tool focused on simplifying Java development with VSCode by automating workspace configuration.
20
+
21
+ Main features:
22
+ - Automated workspace setup for Java/Gradle projects
23
+ - Java and Gradle version management
24
+ - VSCode extensions and settings configuration
25
+ - Debug configurations and launch settings
26
+ - Local development environment setup
27
+ - Git integration and workspace organization""",
28
+ epilog="""Examples:\n
29
+ # Set up a complete workspace environment\n
30
+ mgsnake working-env\n
31
+ \n
32
+ # Configure Java version\n
33
+ mgsnake set-java\n
34
+ \n
35
+ # Configure Gradle version\n
36
+ mgsnake set-gradle\n
37
+ \n
38
+ # Initialize local configurations\n
39
+ mgsnake init-local-config\n
40
+ \n
41
+ # Run with debug logging\n
42
+ mgsnake --log-level DEBUG <command>\n
43
+ \n
44
+ For more details on specific commands, use: mgsnake <command> --help""",
45
+ context_settings={"help_option_names": ["-h", "--help"]},
46
+ cls=CliGroup,
47
+ no_args_is_help=True,
48
+ )
49
+ @click.option("--log-level", "-l", type=click.Choice(list(LOGGING_OPT), False), default="INFO", help="log level")
50
+ @click.pass_context
51
+ def cli(ctx: click.Context, log_level: str) -> None:
52
+ """cli entry point"""
53
+ ctx.ensure_object(dict) # Ensures ctx.obj is a dictionary
54
+ try:
55
+ light_weight: bool = False
56
+ cmd_name = ctx.invoked_subcommand
57
+ if cmd_name:
58
+ cmd = cli.get_command(ctx, cmd_name)
59
+ if not cmd:
60
+ raise click.ClickException(f"Command '{cmd_name}' not found")
61
+ # check if the command has cli_metadata
62
+ metadata = getattr(cmd.callback, "flags", {})
63
+ flags: Optional[set[str]] = metadata.get("flags")
64
+ if flags and "no_init" in flags:
65
+ return
66
+ ws_advice(f"Invoking subcommand: {cmd_name}")
67
+ if flags and "skip" in flags:
68
+ ws_advice("'skip' flag detected. Running in light-weight mode if local working directory is not found.")
69
+ light_weight = True
70
+ shell = os.environ.get("MEGA_SNAKE_SHELL")
71
+ if not shell:
72
+ raise EnvironmentError("Environment variable 'MEGA_SNAKE_SHELL' is not set")
73
+ if shell not in SHELL_OPT:
74
+ raise ValueError(f"Unsupported shell: {shell}. Supported shells are: {', '.join(SHELL_OPT)}")
75
+ init_app_properties(log_level, shell, light_weight)
76
+ except Exception as e:
77
+ click.echo(f"Error during initialization: {e}", err=True)
78
+ click.echo(get_traceback(e), err=True)
79
+ raise SystemExit(e) from e
80
+
81
+
82
+ @cli.result_callback()
83
+ @click.pass_context
84
+ def post_command(ctx, result, **kwargs) -> None:
85
+ """Post-command execution logic"""
86
+ if ctx.invoked_subcommand:
87
+ ws_advice(
88
+ f"Command '{ctx.invoked_subcommand}' completed successfully with result: {result} and kwargs: {kwargs}"
89
+ )
90
+ exit_code: int = ctx.obj.get("exit_code", 0)
91
+ if exit_code:
92
+ sys.exit(exit_code)
93
+
94
+
95
+ cli.add_command_with_alias(diff_tree, ["dt", "tree"])
96
+ for command in create_release.commands.values():
97
+ cli.add_command(create_release_result_callback(command))
98
+ for command in config_environment.commands.values():
99
+ cli.add_command(config_env_result_callback(command))
100
+ for command in remote_branches.commands.values():
101
+ cli.add_command(remote_branches_result_callback(command))
102
+
103
+ if __name__ == "__main__":
104
+ try:
105
+ cli.main(prog_name=APP_NAME)
106
+ except Exception as e:
107
+ raise WorkspaceError("Error during cli execution", e) from e
@@ -0,0 +1,11 @@
1
+ python_module=mega_snake
2
+ resources_path=resources
3
+ working_path=workspace_temp
4
+ log_file_name=python_snake
5
+ local_config_file_name=local_config
6
+ local_env_file_name=.mgsnake.env
7
+ graphql_schema_file_name=schema
8
+ python_virtual_bash=.venv/bin/activate
9
+ python_virtual_ps1_osx=.venv/bin/Activate.ps1
10
+ python_virtual_ps1_linux=.venv/bin/activate.ps1
11
+ python_virtual_ps1_win=.venv/Scripts/Activate.ps1
@@ -0,0 +1 @@
1
+ """ init file for the config_environment module """
@@ -0,0 +1,437 @@
1
+ """This module contains the functions for setting the workspace for the project."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+ import shutil
6
+ import json
7
+ import re
8
+ from typing import Any
9
+ import click
10
+ import jq
11
+ from mega_snake.constants import APP_NAME, WORKSPACE_EXTENSIONS
12
+ from mega_snake.util.props import get_property
13
+ from mega_snake.util.formatting import ws_success
14
+ from mega_snake.config_environment.util import update_workspace
15
+ from mega_snake.config_environment.models.github_queries import PrQueries, IssuesQueries
16
+ from mega_snake.config_environment.models.log_viewer_watcher import LogWatcher
17
+ from mega_snake.config_environment.models.vscode_task import VscodeTask, TASKS_INPUT_QUERY
18
+ from mega_snake.config_environment.models.vscode_input import VscodeInput, InputType
19
+ from mega_snake.config_environment.models.vscode_launch import (
20
+ VscodeLaunch,
21
+ LAUNCH_INPUT_QUERY,
22
+ SUBSTITUTE_SHELL_TAG,
23
+ SUBSTITUTE_PROJECT_TAG,
24
+ REMOTE_DEBUG_PORT_QUERY,
25
+ )
26
+ from mega_snake.config_environment.java_set import execute as set_java
27
+ from mega_snake.config_environment.gradle_set import execute as set_gradle, set_gradle_version as gradle_command
28
+ from mega_snake.config_environment.maven_set import execute as set_maven, set_maven_version as maven_command
29
+ from mega_snake.config_environment.local_config import execute as initial_load
30
+ from mega_snake.util.formatting import ws_advice, ws_info, ws_warning
31
+ from mega_snake.util.util import (
32
+ get_command_return_code,
33
+ get_validated_input,
34
+ cli_metadata,
35
+ get_remote_url,
36
+ load_json_with_comments,
37
+ get_input_or_default,
38
+ )
39
+
40
+
41
+ @click.command(
42
+ name="working-env",
43
+ short_help="Configures the VS Code workspace environment",
44
+ help="Sets up the VS Code workspace with recommended extensions, default settings, tasks, launch configurations,"
45
+ " and git exclusions. Also configures Java and Gradle when applicable.",
46
+ epilog="""This command will:
47
+ - Create/update VSCode workspace file
48
+ - Configure git exclusions for workspace files
49
+ - Set up Java and Gradle configurations
50
+ - Add recommended extensions
51
+ - Configure default settings and file associations
52
+ - Set up tasks and launch configurations
53
+ - Configure log watchers and GitHub queries
54
+
55
+ usage: mgsnake working-env
56
+ """,
57
+ )
58
+ @cli_metadata(flags={"skip"})
59
+ def create_working_env() -> None: # previously untrackGradleProps
60
+ """
61
+ Sets the workspace for the project.
62
+
63
+ Returns:
64
+ None
65
+ """
66
+ git_repo: bool = True
67
+ if not shutil.which("git"):
68
+ git_repo = False
69
+ if (
70
+ get_validated_input(
71
+ "Git is not installed. Would you like to configure the workspace without git?", ["y", "n"]
72
+ ).lower()
73
+ == "n"
74
+ ):
75
+ ws_warning("Git is required to configure the workspace. Exiting...")
76
+ return
77
+ else:
78
+ if get_command_return_code("git rev-parse --is-inside-work-tree") != 0:
79
+ git_repo = False
80
+ if (
81
+ get_validated_input(
82
+ "Not inside a git repository. Would you like to configure the workspace anyway?", ["y", "n"]
83
+ ).lower()
84
+ == "n"
85
+ ):
86
+ ws_warning("Not inside a git repository. Exiting...")
87
+ return
88
+ _execute(git_repo)
89
+
90
+
91
+ def _execute(git_repo: bool) -> None: # previously untrackGradleProps
92
+ """
93
+ Sets the workspace for the project.
94
+
95
+ Returns:
96
+ None
97
+ """
98
+ workspace_file: str = _get_workspace_file()
99
+ working_path: str = _get_working_path()
100
+ if git_repo:
101
+ _git_exclude(working_path)
102
+ initial_load(False)
103
+ set_java(False, workspace_file)
104
+ # verifying if build.gradle or build.gradle.kts exists
105
+ build_file: str = f"{os.getcwd()}/build.gradle"
106
+ build_kts_file: str = f"{os.getcwd()}/build.gradle.kts"
107
+ if not os.path.exists(build_file) and not os.path.exists(build_kts_file):
108
+ ws_warning(
109
+ f"build.gradle or build.gradle.kts file not found in the current directory. "
110
+ f"Please run '{APP_NAME} {gradle_command.name}' command if you want to set the Gradle version anyway."
111
+ )
112
+ else:
113
+ set_gradle(False, workspace_file)
114
+ # verifying if pom.xml exists
115
+ pom_file: str = f"{os.getcwd()}/pom.xml"
116
+ if not os.path.exists(pom_file):
117
+ ws_warning(
118
+ f"pom.xml file not found in the current directory. "
119
+ f"Please run '{APP_NAME} {maven_command.name}' command if you want to set the Maven version anyway."
120
+ )
121
+ else:
122
+ set_maven(None, workspace_file)
123
+ _add_default_settings(workspace_file, working_path)
124
+
125
+
126
+ FOLDER = os.path.basename(os.getcwd())
127
+ GIT_BLAME_QUERY = '.settings.["git-blame.gitWebUrl"]'
128
+ EXTENSIONS_QUERY = ".extensions.recommendations"
129
+ DEFAULT_PROPS: dict[str, Any] = {
130
+ REMOTE_DEBUG_PORT_QUERY: 5005,
131
+ "mgsnake.java.remoteDebug.profile": "dev",
132
+ "mgsnake.java.remoteDebug.jar": "build/libs/*.jar",
133
+ "terminal.integrated.scrollback": 9000,
134
+ "editor.largeFileOptimizations": False,
135
+ "editor.maxTokenizationLineLength": 2000000,
136
+ "logViewer.followTailMode": "auto",
137
+ "logViewer.chunkSizeKb": 81920,
138
+ "java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90"
139
+ " -Dsun.zip.disableMemoryMapping=true -Xmx4G -Xms100m -Xlog:disable",
140
+ }
141
+ FILE_ASSOCIATIONS: dict[str, str] = {
142
+ "**/.github/workflows/*.yml": "github-actions-workflow",
143
+ "*.yml": "yaml",
144
+ "*.gradle": "gradle",
145
+ }
146
+ FILE_ASSOCIATION_QUERY = '.settings.["files.associations"]'
147
+ NEW_WORKSPACE_CONTENTS: dict[str, Any] = {"folders": [{"name": "main", "path": "."}], "settings": {}}
148
+
149
+
150
+ def _get_workspace_file() -> str:
151
+ """
152
+ Gets the workspace file for the project. If not found, creates a new one.
153
+
154
+ Returns:
155
+ str - The workspace file path
156
+ """
157
+ workspace_file: str = get_property("workspace_file")
158
+ if workspace_file:
159
+ ws_info(f"Vscode workspace file found: {workspace_file}")
160
+ # Check if the workspace file is in the current directory
161
+ if not os.path.exists(workspace_file):
162
+ raise FileNotFoundError(f"No workspace file found at {workspace_file}")
163
+ # Check if the workspace file is not empty
164
+ with open(workspace_file, "r", encoding="utf-8") as file:
165
+ if file.read().strip():
166
+ return workspace_file
167
+ ws_warning("Vscode workspace file is empty")
168
+ else:
169
+ ws_warning("Vscode workspace file not found in current directory")
170
+ if get_validated_input("Would you like to create a new default workspace file?", ["y", "n"]).lower() == "n":
171
+ raise RuntimeError("Vscode workspace file is required to configure the working environment. Exiting...")
172
+ workspace_file = f"{os.getcwd()}/{FOLDER}.code-workspace"
173
+ with open(workspace_file, "w", encoding="utf-8") as file:
174
+ json.dump(NEW_WORKSPACE_CONTENTS, file, indent=4)
175
+ ws_success(f"Vscode workspace file created at {workspace_file}")
176
+ return workspace_file
177
+
178
+
179
+ def _get_working_path() -> str:
180
+ """
181
+ Gets the working path for the project. If not found, creates a new one.
182
+
183
+ Returns:
184
+ str - The working path
185
+ """
186
+ working_path: str = get_property("working_path")
187
+ assert working_path, (
188
+ "Working path is required to configure the working environment, but not found in the properties. This is a bug."
189
+ )
190
+ assert Path(working_path).resolve().is_relative_to(Path.cwd().resolve()), (
191
+ "Working path is not in the current directory. This is a bug."
192
+ )
193
+ if os.path.exists(working_path):
194
+ ws_info(f"Working path found: {working_path}")
195
+ return working_path
196
+ ws_warning("Working path not found in current directory")
197
+ if get_validated_input("Would you like to create a new default working path?", ["y", "n"]).lower() == "n":
198
+ raise RuntimeError("Working path is required to configure the working environment. Exiting...")
199
+ os.makedirs(working_path, exist_ok=True)
200
+ ws_success(f"Working path created at {working_path}")
201
+ return working_path
202
+
203
+
204
+ def _git_exclude(working_path: str) -> None:
205
+ """
206
+ Excludes the .vscode folder and the working path from git.
207
+ """
208
+ working_path = os.path.basename(working_path)
209
+ ex_file: str = ".git/info/exclude"
210
+ # Reading git exclude file
211
+ with open(ex_file, "r", encoding="utf-8") as file:
212
+ exclude: str = file.read()
213
+ if not exclude.endswith("\n"):
214
+ exclude += "\n"
215
+ regex = re.compile(r"^\s*\.vscode/?\s*$", re.MULTILINE)
216
+ if regex.search(exclude):
217
+ ws_advice(f".vscode folder already excluded in {ex_file}")
218
+ else:
219
+ exclude += ".vscode/\n"
220
+ ws_success(f"Excluded .vscode folder in {ex_file}")
221
+ regex = re.compile(rf"^\s*{working_path}/?\s*$", re.MULTILINE)
222
+ if regex.search(exclude):
223
+ ws_advice(f"{working_path} folder already excluded in {ex_file}")
224
+ else:
225
+ exclude += f"{working_path}/\n"
226
+ ws_success(f"Excluded {working_path} folder in {ex_file}")
227
+ regex = re.compile(r"^\s*/\*\.code-workspace\s*$", re.MULTILINE)
228
+ if regex.search(exclude):
229
+ ws_advice(f"root code-workspace file already excluded in {ex_file}")
230
+ else:
231
+ exclude += "/*.code-workspace\n"
232
+ ws_success(f"Excluded root code-workspace file in {ex_file}")
233
+ # Writing git exclude file
234
+ with open(ex_file, "w", encoding="utf-8") as file:
235
+ file.write(exclude)
236
+
237
+
238
+ def _add_default_settings(workspace_file: str, working_path: str) -> None:
239
+ """
240
+ Adds default settings to the workspace file.
241
+
242
+ Args:
243
+ workspace_file (str): The workspace file path
244
+ working_path (str): The working path
245
+ """
246
+ json_data: dict[str, Any] = load_json_with_comments(workspace_file)
247
+ update_file: bool = False
248
+
249
+ json_data, updated = _add_recommended_extensions(json_data)
250
+ update_file = update_file or updated
251
+
252
+ json_data, updated = _update_git_blame(json_data)
253
+ update_file = update_file or updated
254
+
255
+ json_data, updated = _update_github_queries(json_data)
256
+ update_file = update_file or updated
257
+
258
+ json_data, updated = _update_log_watchers(json_data, working_path)
259
+ update_file = update_file or updated
260
+
261
+ json_data, updated = _update_vscode_tasks(json_data, working_path)
262
+ update_file = update_file or updated
263
+
264
+ json_data, updated = _update_vscode_launch(json_data, working_path)
265
+ update_file = update_file or updated
266
+
267
+ json_data, updated = _update_input_props(json_data)
268
+ update_file = update_file or updated
269
+
270
+ json_data, updated = _update_file_associations(json_data)
271
+ update_file = update_file or updated
272
+
273
+ if update_file:
274
+ temp_file = f"{working_path}/blame.json"
275
+ update_workspace(json_data, temp_file, workspace_file)
276
+ ws_success("Workspace settings updated successfully")
277
+ else:
278
+ ws_advice("Workspace settings already up-to-date")
279
+
280
+
281
+ def _add_recommended_extensions(json_data: dict[str, Any]) -> tuple[dict[str, Any], bool]:
282
+ """
283
+ Adds recommended extensions to the json contents.
284
+
285
+ Args:
286
+ json_data (dict): The workspace file contents
287
+ """
288
+ update_file: bool = False
289
+ result = jq.compile(EXTENSIONS_QUERY).input(json_data).all()
290
+ if not result or not result[0]:
291
+ jq_query = f"{EXTENSIONS_QUERY} = {json.dumps(WORKSPACE_EXTENSIONS)}"
292
+ json_data = jq.compile(jq_query).input(json_data).first()
293
+ update_file = True
294
+ else:
295
+ ext_list: list[str] = []
296
+ for ext in WORKSPACE_EXTENSIONS:
297
+ if ext not in result[0]:
298
+ ext_list.append(ext)
299
+ if ext_list:
300
+ jq_query = f"{EXTENSIONS_QUERY} += {json.dumps(ext_list)}"
301
+ json_data = jq.compile(jq_query).input(json_data).first()
302
+ update_file = True
303
+ return json_data, update_file
304
+
305
+
306
+ def _update_git_blame(json_data: dict[str, Any]) -> tuple[dict[str, Any], bool]:
307
+ """Update git blame settings in workspace"""
308
+ result = jq.compile(GIT_BLAME_QUERY).input(json_data).first()
309
+ remote_url = get_remote_url()
310
+ if not result and remote_url:
311
+ remote_url = re.sub(r"^git@", "https://", remote_url)
312
+ match = re.match(r"https\://\w+\.\w+\:", remote_url)
313
+ if match:
314
+ repl = re.sub(r"\:$", "/", match.group())
315
+ remote_url = remote_url.replace(match.group(), repl)
316
+ jq_query = f'{GIT_BLAME_QUERY} = "{remote_url}/tree/$ID"'
317
+ json_data = jq.compile(jq_query).input(json_data).first()
318
+ return json_data, True
319
+ return json_data, False
320
+
321
+
322
+ def _update_github_queries(json_data: dict[str, Any]) -> tuple[dict[str, Any], bool]:
323
+ """Update GitHub PR and Issues queries"""
324
+ updated = False
325
+ for pr_query in PrQueries:
326
+ res = pr_query.add_query(json_data)
327
+ if res:
328
+ updated = True
329
+ json_data = res
330
+
331
+ for issue_query in IssuesQueries:
332
+ res = issue_query.add_query(json_data)
333
+ if res:
334
+ updated = True
335
+ json_data = res
336
+
337
+ return json_data, updated
338
+
339
+
340
+ def _update_log_watchers(json_data: dict[str, Any], working_path: str) -> tuple[dict[str, Any], bool]:
341
+ """Update log watchers configuration"""
342
+ updated = False
343
+ for watcher in LogWatcher:
344
+ res = watcher.add_watcher(json_data, working_path)
345
+ if res:
346
+ updated = True
347
+ json_data = res
348
+ return json_data, updated
349
+
350
+
351
+ def _update_vscode_tasks(json_data: dict[str, Any], working_path: str) -> tuple[dict[str, Any], bool]:
352
+ """Update VSCode tasks configuration"""
353
+ updated = False
354
+
355
+ res = VscodeTask.add_tasks_version(json_data)
356
+ if res:
357
+ updated = True
358
+ json_data = res
359
+
360
+ for input_type in [a for a in VscodeInput if a.enum_type != InputType.LAUNCH]:
361
+ res = input_type.add_tasks_input(json_data, TASKS_INPUT_QUERY)
362
+ if res:
363
+ updated = True
364
+ json_data = res
365
+
366
+ for task in VscodeTask:
367
+ res = task.add_tasks_task(json_data, working_path)
368
+ if res:
369
+ updated = True
370
+ json_data = res
371
+
372
+ return json_data, updated
373
+
374
+
375
+ def _update_vscode_launch(json_data: dict[str, Any], working_path: str) -> tuple[dict[str, Any], bool]:
376
+ """Update VSCode launch configuration"""
377
+ updated = False
378
+
379
+ res = VscodeLaunch.add_launch_version(json_data)
380
+ if res:
381
+ updated = True
382
+ json_data = res
383
+
384
+ for input_type in [a for a in VscodeInput if a.enum_type != InputType.TASK]:
385
+ res = input_type.add_tasks_input(json_data, LAUNCH_INPUT_QUERY)
386
+ if res:
387
+ updated = True
388
+ json_data = res
389
+
390
+ for launch in VscodeLaunch:
391
+ res = launch.add_launch_config(json_data, _launch_substituter, working_path)
392
+ if res:
393
+ updated = True
394
+ json_data = res
395
+
396
+ return json_data, updated
397
+
398
+
399
+ def _update_input_props(json_data: dict[str, Any]) -> tuple[dict[str, Any], bool]:
400
+ """Update VSCode input properties"""
401
+ updated = False
402
+ for key, value in DEFAULT_PROPS.items():
403
+ snake_query: str = f'.settings.["{key}"]'
404
+ result = jq.compile(snake_query).input(json_data).first()
405
+ if result is None:
406
+ prompt: str = f"Enter the value a value for {key}"
407
+ value = get_input_or_default(prompt, value)
408
+ jq_query = f"{snake_query} = {json.dumps(value)}"
409
+ json_data = jq.compile(jq_query).input(json_data).first()
410
+ updated = True
411
+
412
+ return json_data, updated
413
+
414
+
415
+ def _update_file_associations(json_data: dict[str, Any]) -> tuple[dict[str, Any], bool]:
416
+ """Update file associations in workspace"""
417
+ updated = False
418
+ for key, value in FILE_ASSOCIATIONS.items():
419
+ file_query: str = f'{FILE_ASSOCIATION_QUERY}.["{key}"]'
420
+ result = jq.compile(file_query).input(json_data).first()
421
+ if not result:
422
+ jq_query = f"{file_query} = {json.dumps(value)}"
423
+ json_data = jq.compile(jq_query).input(json_data).first()
424
+ updated = True
425
+ return json_data, updated
426
+
427
+
428
+ def _launch_substituter(launch: str) -> str:
429
+ """
430
+ Substitutes launch tags with values
431
+ """
432
+ # verify if the launch contains the tags
433
+ if SUBSTITUTE_SHELL_TAG in launch:
434
+ launch = launch.replace(SUBSTITUTE_SHELL_TAG, get_property("shell"))
435
+ if SUBSTITUTE_PROJECT_TAG in launch:
436
+ launch = launch.replace(SUBSTITUTE_PROJECT_TAG, FOLDER)
437
+ return launch