diff --git a/djinit/cli.py b/djinit/cli.py index 0cc0641..318a58d 100644 --- a/djinit/cli.py +++ b/djinit/cli.py @@ -104,7 +104,7 @@ class Cli: self.file_manager.create_readme, self.file_manager.create_env_file, self.file_manager.create_djinit_config, - lambda: self.file_manager.create_pyproject_toml(self.metadata), + lambda: self.file_manager.create_pyproject(self.metadata), ] for step_func in utility_steps: diff --git a/djinit/services/app.py b/djinit/services/app.py index bcc5636..660673c 100644 --- a/djinit/services/app.py +++ b/djinit/services/app.py @@ -196,17 +196,17 @@ class AppManager: create_file_from_template( os.path.join(app_dir, "apps.py"), - "components/apps.j2", + "components/apps.py-tpl", {"app_name": self.app_name}, f"Created apps/{self.app_name}/apps.py", ) subfolders = { - "models": [(f"{self.app_name}.py", "presets/predefined/apps/generic/models.j2")], - "serializers": [(f"{self.app_name}_serializer.py", "presets/predefined/apps/generic/serializers.j2")], - "services": [(f"{self.app_name}_service.py", "presets/predefined/apps/generic/services.j2")], - "views": [(f"{self.app_name}_view.py", "presets/predefined/apps/generic/views.j2")], - "tests": [(f"test_{self.app_name}_api.py", "presets/predefined/apps/generic/tests.j2")], + "models": [(f"{self.app_name}.py", "presets/predefined/apps/generic/models.py-tpl")], + "serializers": [(f"{self.app_name}_serializer.py", "presets/predefined/apps/generic/serializers.py-tpl")], + "services": [(f"{self.app_name}_service.py", "presets/predefined/apps/generic/services.py-tpl")], + "views": [(f"{self.app_name}_view.py", "presets/predefined/apps/generic/views.py-tpl")], + "tests": [(f"test_{self.app_name}_api.py", "presets/predefined/apps/generic/tests.py-tpl")], } for folder, files in subfolders.items(): folder_path = os.path.join(app_dir, folder) @@ -219,8 +219,8 @@ class AppManager: create_file_from_template( os.path.join(app_dir, "urls.py"), - "presets/predefined/apps/generic/urls.j2", - {"app_name": self.app_name, "app_module": app_module}, + "config/urls.py-tpl", + {"url_type": "drf_router", "app_module": app_module, "view_file": f"{self.app_name}_view", "viewset_name": f"{model_class_name}ViewSet", "router_prefix": "", "basename": self.app_name, "has_viewset": True, "has_include": False}, f"Created apps/{self.app_name}/urls.py", ) diff --git a/djinit/services/files.py b/djinit/services/files.py index 34f3a3d..c400af1 100644 --- a/djinit/services/files.py +++ b/djinit/services/files.py @@ -59,7 +59,7 @@ class FileManager: apps_py_path = os.path.join(app_dir, "apps.py") context = {"app_name": app_name} if app_name else {} msg = message or f"Created {os.path.basename(app_dir)}/apps.py" - create_file_from_template(apps_py_path, "components/apps.j2", context, msg, should_format=should_format) + create_file_from_template(apps_py_path, "components/apps.py-tpl", context, msg, should_format=should_format) def _create_subdirectories_with_init(self, base_dir: str, subdirs: list, prefix: str = "") -> None: """Helper to create multiple subdirectories with __init__.py files.""" @@ -81,7 +81,7 @@ class FileManager: ("production.py", base_context), ]: filepath = os.path.join(settings_dir, filename) - template_path = f"config/settings/{filename.replace('.py', '')}.j2" + template_path = f"config/settings/{filename.replace('.py', '')}.py-tpl" self._render_and_create_file( filepath, template_path, @@ -98,14 +98,14 @@ class FileManager: ) -> None: """Helper to create urls.py, wsgi.py, and asgi.py.""" - urls_context = {"api_module": api_module} + urls_context = {"url_type": "project", "api_module": api_module, "include_api": True} if comment_out_api: urls_context["comment_out_api_url"] = True files = [ - ("urls.py", "config/urls/with_api.j2", urls_context), - ("wsgi.py", "config/wsgi.j2", {"project_name": prefix}), - ("asgi.py", "config/asgi.j2", {"project_name": prefix}), + ("urls.py", "config/urls.py-tpl", urls_context), + ("wsgi.py", "config/wsgi.py-tpl", {"project_name": prefix}), + ("asgi.py", "config/asgi.py-tpl", {"project_name": prefix}), ] for filename, template, context in files: @@ -115,7 +115,7 @@ class FileManager: ) def create_gitignore(self) -> None: - self._render_and_create_file(".gitignore", "project/gitignore.j2", {}, "Created .gitignore file") + self._render_and_create_file(".gitignore", "project/gitignore-tpl", {}, "Created .gitignore file") def create_requirements(self) -> None: context = { @@ -124,7 +124,7 @@ class FileManager: } self._render_and_create_file( "requirements.txt", - "project/requirements.j2", + "project/requirements-tpl", context, "Created requirements.txt with Django dependencies", ) @@ -137,7 +137,7 @@ class FileManager: "unified_structure": bool(self.metadata.get("unified_structure")), "module_name": self.module_name, } - self._render_and_create_file("README.md", "project/readme.j2", context, "Created README.md file") + self._render_and_create_file("README.md", "project/readme.md-tpl", context, "Created README.md file") def create_env_file(self) -> None: """Create .env.sample file with environment variables.""" @@ -148,32 +148,39 @@ class FileManager: } self._render_and_create_file( ".env.sample", - "project/env_sample.j2", + "project/env_sample-tpl", context, "Created .env.sample file with generated secret key", ) - def create_pyproject_toml(self, metadata: dict) -> None: + def create_pyproject(self, metadata: dict) -> None: package_name = metadata.get("package_name", "backend") package_name = get_package_name(package_name) - context = {"package_name": package_name, "project_name": self.project_name} + context = { + "package_name": package_name, + "project_name": self.project_name, + "database_type": metadata.get("database_type", "postgresql"), + } self._render_and_create_file( "pyproject.toml", - "project/pyproject_toml.j2", + "project/pyproject.toml-tpl", context, "Created pyproject.toml file for uv", ) def create_project_urls(self) -> None: - """Create project urls.py using project_urls.j2 template (replaces update_project_urls).""" + """Create project urls.py using project_config/urls.py-tpl template (replaces update_project_urls).""" effective_app_modules = calculate_app_module_paths(self.app_names, self.metadata) context = { + "url_type": "project", "project_name": self.project_name, "app_names": effective_app_modules, + "include_apps": True, + "include_api": True, } self._render_and_create_file( "urls.py", - "config/urls/with_apps.j2", + "config/urls.py-tpl", context, "Created project urls.py with comprehensive URL configuration", base_dir=self.project_configs, @@ -184,7 +191,7 @@ class FileManager: context = {"project_name": self.project_name} self._render_and_create_file( "justfile", - "project/justfile.j2", + "project/justfile-tpl", context, "Created justfile with Django development tasks", ) @@ -198,19 +205,19 @@ class FileManager: self._create_apps_py(users_dir, "users") users_subfolders = { - "models": [("user.py", "presets/predefined/apps/users/models.j2")], - "serializers": [("user_serializer.py", "presets/predefined/apps/users/serializers.j2")], - "services": [("user_service.py", "presets/predefined/apps/users/services.j2")], - "views": [("user_view.py", "presets/predefined/apps/users/views.j2")], - "tests": [("test_user_api.py", "presets/predefined/apps/users/tests.j2")], + "models": [("user.py", "presets/predefined/apps/users/models.py-tpl")], + "serializers": [("user_serializer.py", "presets/predefined/apps/users/serializers.py-tpl")], + "services": [("user_service.py", "presets/predefined/apps/users/services.py-tpl")], + "views": [("user_view.py", "presets/predefined/apps/users/views.py-tpl")], + "tests": [("test_user_api.py", "presets/predefined/apps/users/tests.py-tpl")], } self._create_files_from_specs(users_dir, users_subfolders, {"app_module": "apps.users"}) users_urls_path = os.path.join(users_dir, "urls.py") create_file_from_template( users_urls_path, - "presets/predefined/apps/users/urls.j2", - {"app_module": "apps.users"}, + "config/urls.py-tpl", + {"url_type": "drf_router", "app_module": "apps.users", "view_file": "user_view", "viewset_name": "UserViewSet", "router_prefix": "users", "basename": "users", "has_viewset": True, "has_app_name": True, "app_name": "users", "has_include": True}, "Created apps/users/urls.py", ) @@ -218,32 +225,32 @@ class FileManager: create_directory_with_init(core_dir, "Created apps/core/__init__.py") core_subfolders = { - "utils": [("responses.py", "presets/predefined/core/utils/responses.j2")], - "mixins": [("timestamped_model.py", "presets/predefined/core/mixins/timestamped_model.j2")], - "middleware": [("request_logger.py", "presets/predefined/core/middleware/request_logger.j2")], + "utils": [("responses.py", "presets/predefined/core/utils/responses.py-tpl")], + "mixins": [("timestamped_model.py", "presets/predefined/core/mixins/timestamped_model.py-tpl")], + "middleware": [("request_logger.py", "presets/predefined/core/middleware/request_logger.py-tpl")], } self._create_files_from_specs(core_dir, core_subfolders, {}) exceptions_path = os.path.join(core_dir, "exceptions.py") create_file_from_template( - exceptions_path, "presets/predefined/core/exceptions.j2", {}, "Created apps/core/exceptions.py" + exceptions_path, "presets/predefined/core/exceptions.py-tpl", {}, "Created apps/core/exceptions.py" ) api_dir = os.path.join(self.project_root, "api") create_directory_with_init(api_dir, "Created api/__init__.py") api_urls_path = os.path.join(api_dir, "urls.py") - create_file_from_template(api_urls_path, "presets/predefined/api/urls.j2", {}, "Created api/urls.py") + create_file_from_template(api_urls_path, "config/urls.py-tpl", {"url_type": "api_root", "app_name": "api", "version": "v1", "api_module": "api"}, "Created api/urls.py") api_v1_dir = os.path.join(api_dir, "v1") create_directory_with_init(api_v1_dir, "Created api/v1/__init__.py") api_v1_urls_path = os.path.join(api_v1_dir, "urls.py") - create_file_from_template(api_v1_urls_path, "presets/predefined/api/v1/urls.j2", {}, "Created api/v1/urls.py") + create_file_from_template(api_v1_urls_path, "config/urls.py-tpl", {"url_type": "api_version", "app_name": "v1", "app_list": ["users"], "app_module": "apps"}, "Created api/v1/urls.py") project_urls_path = os.path.join(self.project_configs, "urls.py") create_file_from_template( project_urls_path, - "config/urls/with_api.j2", - {"api_module": "api"}, + "config/urls.py-tpl", + {"url_type": "project", "api_module": "api", "include_api": True}, "Updated project urls.py to include API routes", should_format=True, ) @@ -285,8 +292,8 @@ class FileManager: create_file_from_template( os.path.join(api_dir, "urls.py"), - "presets/predefined/api/urls.j2", - {}, + "config/urls.py-tpl", + {"url_type": "api_root", "app_name": "api", "version": "v1", "api_module": "apps.api"}, "Created apps/api/urls.py", should_format=True, ) @@ -296,8 +303,8 @@ class FileManager: create_file_from_template( os.path.join(api_v1_dir, "urls.py"), - "presets/predefined/api/v1/urls.j2", - {}, + "config/urls.py-tpl", + {"url_type": "api_version", "app_name": "v1", "app_list": [], "app_module": "apps"}, "Created apps/api/v1/urls.py", should_format=True, ) @@ -328,28 +335,28 @@ class FileManager: create_file_from_template( os.path.join(project_dir, "admin", "__init__.py"), - "components/admin.j2", + "components/admin.py-tpl", {}, f"Created {self.module_name}/admin/__init__.py", ) create_file_from_template( os.path.join(project_dir, "models", "__init__.py"), - "components/models.j2", + "components/models.py-tpl", {}, f"Created {self.module_name}/models/__init__.py", ) create_file_from_template( os.path.join(project_dir, "api", "README.md"), - "components/api_readme.j2", + "components/api_readme.md-tpl", {"module_name": self.module_name}, f"Created {self.module_name}/api/README.md", ) create_file_from_template( os.path.join(project_dir, "models", "README.md"), - "components/models_readme.j2", + "components/models_readme.md-tpl", {"module_name": self.module_name}, f"Created {self.module_name}/models/README.md", ) @@ -358,7 +365,7 @@ class FileManager: context = {"project_name": self.project_name} self._render_and_create_file( "Procfile", - "project/procfile.j2", + "project/procfile-tpl", context, "Created Procfile with Gunicorn configuration", ) @@ -367,7 +374,7 @@ class FileManager: context = {"python_version": "3.13"} self._render_and_create_file( "runtime.txt", - "project/runtime_txt.j2", + "project/runtime_txt-tpl", context, "Created runtime.txt with Python version specification", ) @@ -379,7 +386,7 @@ class FileManager: workflow_file = os.path.join(github_dir, "ci.yml") self._render_and_create_file( workflow_file, - "project/ci/github_actions.j2", + "project/ci/github_actions-tpl", context, "Created Github Actions workflow (ci.yml)", base_dir=self.project_root, @@ -389,7 +396,7 @@ class FileManager: context = {"project_name": self.project_name, "module_name": self.module_name} self._render_and_create_file( ".gitlab-ci.yml", - "project/ci/gitlab_ci.j2", + "project/ci/gitlab_ci-tpl", context, "Created GitLab CI configuration (.gitlab-ci.yml)", ) diff --git a/djinit/services/templates.py b/djinit/services/templates.py index 4f74608..e7a7f0b 100644 --- a/djinit/services/templates.py +++ b/djinit/services/templates.py @@ -7,7 +7,7 @@ import os from pathlib import Path from typing import Any, Dict -from jinja2 import Environment, FileSystemLoader, Template +from djinit.core.parser import InFileLogicParser class TemplateEngine: @@ -27,22 +27,26 @@ class TemplateEngine: raise FileNotFoundError(f"Template directory not found: {template_dir}") self.template_dir = str(template_dir) - self.env = Environment( - loader=FileSystemLoader(template_dir), trim_blocks=True, lstrip_blocks=True, keep_trailing_newline=True - ) + self.parser = InFileLogicParser() def render_template(self, template_name: str, context: Dict[str, Any]) -> str: """Render a template with the given context. Args: - template_name: Name of the template file (e.g., 'gitignore.j2') + template_name: Name of the template file (e.g., 'gitignore.py-tpl') context: Dictionary of variables to pass to the template Returns: Rendered template content as string """ - template = self.env.get_template(template_name) - return template.render(**context) + template_path = os.path.join(self.template_dir, template_name) + if not os.path.exists(template_path): + raise FileNotFoundError(f"Template not found: {template_path}") + + with open(template_path, "r") as f: + template_text = f.read() + + return self.parser.render(template_text, context) def render_string(self, template_string: str, context: Dict[str, Any]) -> str: """Render a template string with the given context. @@ -54,24 +58,26 @@ class TemplateEngine: Returns: Rendered template content as string """ - template = Template(template_string, trim_blocks=True, lstrip_blocks=True) - return template.render(**context) + return self.parser.render(template_string, context) def get_template_names(self) -> list[str]: """Get list of available template files.""" if not os.path.exists(self.template_dir): return [] - return sorted([file for file in os.listdir(self.template_dir) if file.endswith(".j2")]) + templates = [] + for root, _, files in os.walk(self.template_dir): + for file in files: + if file.endswith("-tpl"): + rel_path = os.path.relpath(os.path.join(root, file), self.template_dir) + templates.append(rel_path) + return sorted(templates) def template_exists(self, template_name: str) -> bool: """Check if a template file exists. Args: template_name: Name of the template file - - Returns: - True if template exists, False otherwise """ template_path = os.path.join(self.template_dir, template_name) return os.path.exists(template_path)