forgepy-cli 1.0.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 (74) hide show
  1. builders/__init__.py +0 -0
  2. builders/base_builder.py +17 -0
  3. builders/file_builder.py +31 -0
  4. builders/folder_builder.py +30 -0
  5. builders/python_tools_builder.py +64 -0
  6. cli/__init__.py +3 -0
  7. cli/command.py +52 -0
  8. cli/commands/__init__.py +32 -0
  9. cli/commands/component_command.py +177 -0
  10. cli/commands/config_command.py +174 -0
  11. cli/commands/create_command.py +161 -0
  12. cli/commands/list_command.py +40 -0
  13. cli/commands/version_command.py +29 -0
  14. cli/dispatcher.py +61 -0
  15. cli/parser.py +84 -0
  16. components/__init__.py +1 -0
  17. components/base_component.py +30 -0
  18. components/component_context.py +21 -0
  19. components/component_installer.py +44 -0
  20. components/component_manifest.py +102 -0
  21. components/component_metadata.py +50 -0
  22. components/component_registry.py +74 -0
  23. components/component_state.py +243 -0
  24. components/component_validation.py +88 -0
  25. components/github_actions_component.py +75 -0
  26. components/pytest_component.py +40 -0
  27. components/ruff_component.py +40 -0
  28. config/__init__.py +0 -0
  29. config/default_structure.py +18 -0
  30. config/user_config.py +238 -0
  31. config/version.py +16 -0
  32. core/__init__.py +0 -0
  33. core/environment_builder.py +33 -0
  34. core/git_builder.py +90 -0
  35. core/project_generator.py +125 -0
  36. core/requirements_installer.py +50 -0
  37. core/vscode_builder.py +103 -0
  38. forgepy_cli-1.0.0.dist-info/METADATA +176 -0
  39. forgepy_cli-1.0.0.dist-info/RECORD +74 -0
  40. forgepy_cli-1.0.0.dist-info/WHEEL +5 -0
  41. forgepy_cli-1.0.0.dist-info/entry_points.txt +2 -0
  42. forgepy_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
  43. forgepy_cli-1.0.0.dist-info/top_level.txt +8 -0
  44. main.py +20 -0
  45. models/__init__.py +1 -0
  46. models/project_config.py +87 -0
  47. templates/__init__.py +0 -0
  48. templates/app_template.py +20 -0
  49. templates/basic/basic_files.py +23 -0
  50. templates/basic/basic_template.py +32 -0
  51. templates/changelog_template.py +15 -0
  52. templates/cli/cli_files.py +70 -0
  53. templates/cli/cli_template.py +55 -0
  54. templates/env_template.py +20 -0
  55. templates/gitignore_template.py +27 -0
  56. templates/library/library_files.py +28 -0
  57. templates/library/library_template.py +56 -0
  58. templates/license_template.py +21 -0
  59. templates/pyproject_template.py +15 -0
  60. templates/readme_template.py +15 -0
  61. templates/requirements_template.py +13 -0
  62. templates/template_engine/base_template.py +58 -0
  63. templates/template_engine/file_template.py +76 -0
  64. templates/template_engine/package_name.py +30 -0
  65. templates/template_engine/template_context.py +24 -0
  66. templates/template_engine/template_files.py +10 -0
  67. templates/template_engine/template_metadata.py +59 -0
  68. templates/template_engine/template_registry.py +107 -0
  69. templates/template_manager.py +58 -0
  70. templates/vscode/__init__.py +0 -0
  71. templates/vscode/extensions_template.py +32 -0
  72. templates/vscode/launch_template.py +41 -0
  73. templates/vscode/settings_template.py +37 -0
  74. templates/vscode/tasks_template.py +66 -0
builders/__init__.py ADDED
File without changes
@@ -0,0 +1,17 @@
1
+ """
2
+ ==================================================
3
+ ForgePy
4
+ Module : Base Builder
5
+ ==================================================
6
+ """
7
+
8
+
9
+ class BaseBuilder:
10
+ """
11
+ Parent class seluruh Builder ForgePy.
12
+
13
+ Digunakan sebagai parent agar seluruh Builder
14
+ mempunyai struktur yang konsisten.
15
+ """
16
+
17
+ pass
@@ -0,0 +1,31 @@
1
+ """
2
+ ==================================================
3
+ ForgePy
4
+ Module : File Builder
5
+ ==================================================
6
+ """
7
+
8
+ from pathlib import Path
9
+
10
+ from builders.base_builder import BaseBuilder
11
+
12
+
13
+ class FileBuilder(BaseBuilder):
14
+
15
+ def write(
16
+ self,
17
+ path: Path,
18
+ content: str,
19
+ ) -> None:
20
+
21
+ path.parent.mkdir(
22
+ parents=True,
23
+ exist_ok=True,
24
+ )
25
+
26
+ path.write_text(
27
+ content,
28
+ encoding="utf-8",
29
+ )
30
+
31
+ print(f"[OK] File dibuat : {path}")
@@ -0,0 +1,30 @@
1
+ """
2
+ ==================================================
3
+ ForgePy
4
+ Module : Folder Builder
5
+ ==================================================
6
+ """
7
+
8
+ from pathlib import Path
9
+
10
+ from builders.base_builder import BaseBuilder
11
+
12
+
13
+ class FolderBuilder(BaseBuilder):
14
+
15
+ def create(
16
+ self,
17
+ root: Path,
18
+ folders: list[str],
19
+ ) -> None:
20
+
21
+ for folder in folders:
22
+
23
+ path = root / folder
24
+
25
+ path.mkdir(
26
+ parents=True,
27
+ exist_ok=True,
28
+ )
29
+
30
+ print(f"[OK] Folder dibuat : {path}")
@@ -0,0 +1,64 @@
1
+ """
2
+ ==================================================
3
+ ForgePy
4
+ Module : Python Tools Builder
5
+ ==================================================
6
+ """
7
+
8
+ import subprocess
9
+ from pathlib import Path
10
+
11
+ from builders.base_builder import BaseBuilder
12
+
13
+
14
+ PACKAGE_TOOL_UPDATE_TIMEOUT_SECONDS = 300
15
+
16
+
17
+ class PythonToolsBuilder(BaseBuilder):
18
+ """
19
+ Mengupdate pip, setuptools, dan wheel
20
+ pada Virtual Environment.
21
+ """
22
+
23
+ def update(
24
+ self,
25
+ project_path: Path,
26
+ ) -> None:
27
+
28
+ python = project_path / ".venv" / "Scripts" / "python.exe"
29
+
30
+ if not python.exists():
31
+ print("[WARNING] Virtual Environment belum tersedia.")
32
+ return
33
+
34
+ packages = [
35
+ "pip",
36
+ "setuptools",
37
+ "wheel",
38
+ ]
39
+
40
+ for package in packages:
41
+
42
+ print(f"[INFO] Mengupdate {package}...")
43
+
44
+ try:
45
+ subprocess.run(
46
+ [
47
+ str(python),
48
+ "-m",
49
+ "pip",
50
+ "install",
51
+ "--upgrade",
52
+ package,
53
+ ],
54
+ check=True,
55
+ timeout=PACKAGE_TOOL_UPDATE_TIMEOUT_SECONDS,
56
+ )
57
+ except subprocess.SubprocessError as error:
58
+ print(
59
+ "[ERROR] Packaging-tool update failed for "
60
+ f"'{package}': {error}"
61
+ )
62
+ raise
63
+
64
+ print(f"[OK] {package} berhasil diupdate.")
cli/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """
2
+ ForgePy CLI Package
3
+ """
cli/command.py ADDED
@@ -0,0 +1,52 @@
1
+ """
2
+ ==================================================
3
+ ForgePy
4
+ Module : Base Command
5
+ ==================================================
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+ from argparse import ArgumentParser, Namespace
10
+
11
+
12
+ class Command(ABC):
13
+ """
14
+ Kontrak dasar untuk seluruh command ForgePy.
15
+ """
16
+
17
+ name: str = ""
18
+ summary: str = ""
19
+ description: str = ""
20
+
21
+ def validate_registration(self) -> None:
22
+ """
23
+ Memastikan metadata command lengkap sebelum didaftarkan.
24
+ """
25
+
26
+ for field in (
27
+ "name",
28
+ "summary",
29
+ "description",
30
+ ):
31
+ if not getattr(self, field):
32
+ raise ValueError(
33
+ f"Command '{type(self).__name__}' harus memiliki {field}."
34
+ )
35
+
36
+ def configure_parser(
37
+ self,
38
+ parser: ArgumentParser,
39
+ ) -> None:
40
+ """
41
+ Mendaftarkan argumen khusus command.
42
+
43
+ Command tanpa argumen dapat menggunakan implementasi default.
44
+ """
45
+ del parser
46
+
47
+ @abstractmethod
48
+ def execute(self, args: Namespace) -> int:
49
+ """
50
+ Menjalankan command dan mengembalikan status proses CLI.
51
+ """
52
+ raise NotImplementedError
@@ -0,0 +1,32 @@
1
+ """
2
+ ForgePy Commands
3
+
4
+ Seluruh command bawaan didaftarkan pada module ini.
5
+ """
6
+
7
+ from cli.command import Command
8
+ from cli.commands.component_command import ComponentCommand
9
+ from cli.commands.config_command import ConfigCommand
10
+ from cli.commands.create_command import CreateCommand
11
+ from cli.commands.list_command import ListCommand
12
+ from cli.commands.version_command import VersionCommand
13
+
14
+
15
+ DEFAULT_COMMAND = "create"
16
+
17
+
18
+ def create_commands() -> tuple[Command, ...]:
19
+ """
20
+ Membuat seluruh command bawaan dalam urutan tampilan CLI.
21
+
22
+ Command baru hanya perlu ditambahkan pada catalog ini agar parser
23
+ dan dispatcher menggunakan registrasi yang sama.
24
+ """
25
+
26
+ return (
27
+ CreateCommand(),
28
+ VersionCommand(),
29
+ ListCommand(),
30
+ ConfigCommand(),
31
+ ComponentCommand(),
32
+ )
@@ -0,0 +1,177 @@
1
+ """CLI access to registered ForgePy components."""
2
+
3
+ from argparse import ArgumentParser, Namespace
4
+ from pathlib import Path
5
+
6
+ from cli.command import Command
7
+ from components.component_installer import (
8
+ ComponentAlreadyInstalledError,
9
+ ComponentInstaller,
10
+ )
11
+ from components.component_registry import ComponentRegistry
12
+ from components.component_state import (
13
+ ComponentStateStore,
14
+ ForgePyComponentStateError,
15
+ )
16
+ from components.component_validation import ComponentValidationError
17
+
18
+
19
+ class ComponentCommand(Command):
20
+ """List available or installed components, or install one."""
21
+
22
+ name = "component"
23
+ summary = "List, inspect, or add ForgePy components."
24
+ description = (
25
+ "List registered ForgePy components, inspect project-local installed "
26
+ "state, or add one to an existing project."
27
+ )
28
+
29
+ def __init__(self, registry: ComponentRegistry | None = None) -> None:
30
+ self._registry = registry
31
+
32
+ def configure_parser(self, parser: ArgumentParser) -> None:
33
+ actions = parser.add_subparsers(
34
+ dest="component_action",
35
+ title="component actions",
36
+ description="Available component actions",
37
+ metavar="ACTION",
38
+ required=True,
39
+ )
40
+
41
+ actions.add_parser(
42
+ "list",
43
+ help="List registered built-in components.",
44
+ description=(
45
+ "List registered built-in component names and descriptions."
46
+ ),
47
+ )
48
+
49
+ installed_parser = actions.add_parser(
50
+ "installed",
51
+ help="List components recorded as installed in a project.",
52
+ description=(
53
+ "List component names recorded in an explicitly supplied "
54
+ "existing project directory."
55
+ ),
56
+ )
57
+ installed_parser.add_argument(
58
+ "--project",
59
+ required=True,
60
+ type=Path,
61
+ metavar="PATH",
62
+ help="Path to an existing project directory.",
63
+ )
64
+
65
+ add_parser = actions.add_parser(
66
+ "add",
67
+ help="Add a registered component to an existing project.",
68
+ description=(
69
+ "Add a registered component to an explicitly supplied "
70
+ "existing project directory."
71
+ ),
72
+ )
73
+ add_parser.add_argument(
74
+ "component_name",
75
+ metavar="NAME",
76
+ help="Registered component name.",
77
+ )
78
+ add_parser.add_argument(
79
+ "--project",
80
+ required=True,
81
+ type=Path,
82
+ metavar="PATH",
83
+ help="Path to an existing project directory.",
84
+ )
85
+
86
+ def execute(self, args: Namespace) -> int:
87
+ action = getattr(args, "component_action", None)
88
+
89
+ if action == "list":
90
+ self._list()
91
+ return 0
92
+ elif action == "installed":
93
+ return self._installed(args.project)
94
+ elif action == "add":
95
+ return self._add(args.component_name, args.project)
96
+ else:
97
+ print(f"[ERROR] Unknown component action: '{action}'.")
98
+ return 1
99
+
100
+ def _list(self) -> None:
101
+ print("=" * 40)
102
+ print(" ForgePy Components ")
103
+ print("=" * 40)
104
+
105
+ for component in self._get_registry().list_components():
106
+ metadata = component.metadata
107
+ print(f"- {metadata.name}: {metadata.description}")
108
+
109
+ @staticmethod
110
+ def _installed(project_path: Path) -> int:
111
+ try:
112
+ installed_components = ComponentStateStore(project_path).load()
113
+ except ForgePyComponentStateError as error:
114
+ print(f"[ERROR] {error}")
115
+ return 1
116
+ except (TypeError, ValueError) as error:
117
+ print(f"[ERROR] Invalid project path '{project_path}': {error}")
118
+ return 1
119
+
120
+ if not installed_components:
121
+ print("No installed components.")
122
+ return 0
123
+
124
+ print("Installed components:")
125
+ for component_name in sorted(installed_components):
126
+ print(f"- {component_name}")
127
+
128
+ return 0
129
+
130
+ def _add(self, name: str, project_path: Path) -> int:
131
+ try:
132
+ self._get_registry().get(name)
133
+ except KeyError:
134
+ print(f"[ERROR] Unknown ForgePy component: '{name}'.")
135
+ return 1
136
+
137
+ try:
138
+ ComponentInstaller(
139
+ registry=self._get_registry(),
140
+ ).install(
141
+ name=name,
142
+ project_path=project_path,
143
+ )
144
+ except ComponentAlreadyInstalledError as error:
145
+ print(f"[ERROR] {error}")
146
+ return 1
147
+ except ComponentValidationError as error:
148
+ print(f"[ERROR] {error}")
149
+ return 1
150
+ except ForgePyComponentStateError as error:
151
+ print(f"[ERROR] {error}")
152
+ return 1
153
+ except (TypeError, ValueError) as error:
154
+ print(f"[ERROR] Invalid project path '{project_path}': {error}")
155
+ return 1
156
+ except FileExistsError as error:
157
+ target = error.filename or str(project_path)
158
+ print(
159
+ "[ERROR] Component installation refused because the target "
160
+ f"already exists: '{target}'."
161
+ )
162
+ return 1
163
+ except OSError as error:
164
+ print(f"[ERROR] Component installation failed: {error}")
165
+ return 1
166
+
167
+ print(
168
+ f"[OK] Component '{name}' added to project "
169
+ f"'{project_path}'."
170
+ )
171
+ return 0
172
+
173
+ def _get_registry(self) -> ComponentRegistry:
174
+ if self._registry is None:
175
+ self._registry = ComponentRegistry()
176
+
177
+ return self._registry
@@ -0,0 +1,174 @@
1
+ """
2
+ ==================================================
3
+ ForgePy
4
+ Module : Config Command
5
+ ==================================================
6
+ """
7
+
8
+ import json
9
+ from argparse import ArgumentParser, Namespace
10
+
11
+ from cli.command import Command
12
+ from config.user_config import (
13
+ ConfigStore,
14
+ ForgePyConfigError,
15
+ UnknownConfigSettingError,
16
+ )
17
+
18
+
19
+ class ConfigCommand(Command):
20
+ """
21
+ Manage persistent ForgePy user configuration.
22
+ """
23
+
24
+ name = "config"
25
+ summary = "Show or update ForgePy user configuration."
26
+ description = (
27
+ "Inspect or update the persistent ForgePy configuration stored "
28
+ "under the current user's home directory."
29
+ )
30
+
31
+ def __init__(
32
+ self,
33
+ store: ConfigStore | None = None,
34
+ ) -> None:
35
+ self._store = store
36
+
37
+ def configure_parser(
38
+ self,
39
+ parser: ArgumentParser,
40
+ ) -> None:
41
+ actions = parser.add_subparsers(
42
+ dest="config_action",
43
+ title="config actions",
44
+ description="Available configuration actions",
45
+ metavar="ACTION",
46
+ required=True,
47
+ )
48
+
49
+ actions.add_parser(
50
+ "show",
51
+ help="Show every supported configuration setting.",
52
+ description=(
53
+ "Show the effective ForgePy user configuration. "
54
+ "Missing files use safe defaults without being created."
55
+ ),
56
+ )
57
+
58
+ set_parser = actions.add_parser(
59
+ "set",
60
+ help="Set and persist one supported configuration value.",
61
+ description=(
62
+ "Update one supported ForgePy configuration setting "
63
+ "while preserving all other values."
64
+ ),
65
+ )
66
+ set_parser.add_argument(
67
+ "setting",
68
+ metavar="KEY",
69
+ help=(
70
+ "Setting to update. Supported keys: "
71
+ f"{self._supported_settings_text()}."
72
+ ),
73
+ )
74
+ set_parser.add_argument(
75
+ "value",
76
+ metavar="VALUE",
77
+ help="String value to persist for the selected setting.",
78
+ )
79
+
80
+ actions.add_parser(
81
+ "reset",
82
+ help="Reset and persist all settings to their defaults.",
83
+ description=(
84
+ "Replace the persisted ForgePy user configuration "
85
+ "with the safe defaults."
86
+ ),
87
+ )
88
+
89
+ def execute(self, args: Namespace) -> int:
90
+ action = getattr(
91
+ args,
92
+ "config_action",
93
+ None,
94
+ )
95
+
96
+ try:
97
+ if action == "show":
98
+ self._show()
99
+ elif action == "set":
100
+ self._set(
101
+ setting=args.setting,
102
+ value=args.value,
103
+ )
104
+ elif action == "reset":
105
+ self._reset()
106
+ else:
107
+ print(
108
+ "[ERROR] Unknown ForgePy configuration action: "
109
+ f"'{action}'."
110
+ )
111
+ return 1
112
+ except UnknownConfigSettingError as error:
113
+ print(f"[ERROR] {error}")
114
+ print(
115
+ "Supported settings: "
116
+ f"{self._supported_settings_text()}."
117
+ )
118
+ return 1
119
+ except ForgePyConfigError as error:
120
+ print(f"[ERROR] {error}")
121
+ return 1
122
+
123
+ return 0
124
+
125
+ def _show(self) -> None:
126
+ config = self._get_store().load()
127
+
128
+ print("=" * 40)
129
+ print(" ForgePy Configuration ")
130
+ print("=" * 40)
131
+
132
+ for setting, value in config.items():
133
+ print(
134
+ f"{setting} = "
135
+ f"{self._display_value(value)}"
136
+ )
137
+
138
+ def _set(
139
+ self,
140
+ setting: str,
141
+ value: str,
142
+ ) -> None:
143
+ updated = self._get_store().update(
144
+ setting,
145
+ value,
146
+ )
147
+
148
+ print(
149
+ "[OK] ForgePy configuration updated: "
150
+ f"{setting} = {self._display_value(updated[setting])}"
151
+ )
152
+
153
+ def _reset(self) -> None:
154
+ self._get_store().reset()
155
+ print("[OK] ForgePy configuration reset to defaults.")
156
+
157
+ def _get_store(self) -> ConfigStore:
158
+ if self._store is None:
159
+ self._store = ConfigStore()
160
+
161
+ return self._store
162
+
163
+ @staticmethod
164
+ def _display_value(value: str) -> str:
165
+ return json.dumps(
166
+ value,
167
+ ensure_ascii=False,
168
+ )
169
+
170
+ @staticmethod
171
+ def _supported_settings_text() -> str:
172
+ return ", ".join(
173
+ ConfigStore.defaults()
174
+ )