coregen-cli 3.1.6__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.
- coregen.py +1585 -0
- coregen_cli-3.1.6.dist-info/METADATA +1211 -0
- coregen_cli-3.1.6.dist-info/RECORD +172 -0
- coregen_cli-3.1.6.dist-info/WHEEL +5 -0
- coregen_cli-3.1.6.dist-info/entry_points.txt +2 -0
- coregen_cli-3.1.6.dist-info/top_level.txt +2 -0
- generators/__init__.py +1 -0
- generators/assets/__init__.py +1 -0
- generators/assets/assets.py +30 -0
- generators/assets/main.py +4 -0
- generators/config/__init__.py +1 -0
- generators/config/analisy_options.py +42 -0
- generators/config/main.py +11 -0
- generators/config/pubspec.py +139 -0
- generators/cursor/__init__.py +3 -0
- generators/cursor/setup.py +87 -0
- generators/helpers/__init__.py +56 -0
- generators/helpers/component.py +1030 -0
- generators/helpers/config.py +255 -0
- generators/helpers/data_source.py +180 -0
- generators/helpers/domain.py +469 -0
- generators/helpers/feature.py +654 -0
- generators/helpers/navigation.py +274 -0
- generators/helpers/page.py +63 -0
- generators/helpers/project.py +33 -0
- generators/helpers/utils.py +338 -0
- generators/helpers/validation.py +364 -0
- generators/initializator.py +68 -0
- generators/main.py +81 -0
- generators/static/assets/logo.png +0 -0
- generators/static/assets/mock/auth_user.json +5 -0
- generators/static/assets/svgs/apple.svg +4 -0
- generators/static/assets/svgs/google.svg +2 -0
- generators/static/cursor/AGENTS.md.jinja +37 -0
- generators/static/cursor/agents/doc-writer.md +20 -0
- generators/static/cursor/agents/epic-orchestrator.md +28 -0
- generators/static/cursor/agents/feature-implementer.md +29 -0
- generators/static/cursor/agents/integration-wiring.md +30 -0
- generators/static/cursor/agents/layer-application.md +21 -0
- generators/static/cursor/agents/layer-guardian.md +21 -0
- generators/static/cursor/agents/layer-infrastructure.md +31 -0
- generators/static/cursor/agents/layer-model.md +24 -0
- generators/static/cursor/agents/layer-presentation.md +33 -0
- generators/static/cursor/docs/architecture/APIS_AND_INTEGRATION.md.jinja +42 -0
- generators/static/cursor/docs/architecture/CARAVAGGIO_COMPONENTS.md.jinja +317 -0
- generators/static/cursor/docs/architecture/DDD_LAYERS.md.jinja +32 -0
- generators/static/cursor/docs/architecture/FILE_TEMPLATES.md.jinja +76 -0
- generators/static/cursor/docs/architecture/MOCK_AND_REMOTE_DATA.md.jinja +87 -0
- generators/static/cursor/docs/architecture/REFERENCE_IMPLEMENTATIONS.md.jinja +48 -0
- generators/static/cursor/docs/architecture/WIDGETS_AND_CARAVAGGIO.md.jinja +76 -0
- generators/static/cursor/docs/epics/README.md +17 -0
- generators/static/cursor/lib/domain/AGENTS.md +23 -0
- generators/static/cursor/lib/features/AGENTS.md +23 -0
- generators/static/cursor/lib/widgets/AGENTS.md +21 -0
- generators/static/cursor/rules/apis-layer.mdc.jinja +21 -0
- generators/static/cursor/rules/application-layer.mdc.jinja +12 -0
- generators/static/cursor/rules/architecture-core.mdc.jinja +43 -0
- generators/static/cursor/rules/dart-conventions.mdc.jinja +14 -0
- generators/static/cursor/rules/domain-layer.mdc.jinja +24 -0
- generators/static/cursor/rules/login-context.mdc.jinja +16 -0
- generators/static/cursor/rules/presentation-layer.mdc.jinja +15 -0
- generators/static/cursor/rules/quality-gate.mdc.jinja +14 -0
- generators/static/cursor/rules/ui-caravaggio.mdc.jinja +22 -0
- generators/static/cursor/skills/caravaggio-ui/SKILL.md +138 -0
- generators/static/cursor/skills/epic-delivery/SKILL.md +74 -0
- generators/static/cursor/skills/epic-delivery/templates/epic-status.md +14 -0
- generators/static/cursor/skills/epic-delivery/templates/epic.md +36 -0
- generators/static/templates/apis/common/constants_template.jinja +48 -0
- generators/static/templates/apis/common/data_source_config_template.jinja +47 -0
- generators/static/templates/apis/core/api_injectable_module_template.jinja +14 -0
- generators/static/templates/apis/interceptors/api_logger_template.jinja +135 -0
- generators/static/templates/apis/interceptors/auth_interceptor_template.jinja +207 -0
- generators/static/templates/auth/application/auth_bloc_template.jinja +36 -0
- generators/static/templates/auth/application/auth_event_template.jinja +7 -0
- generators/static/templates/auth/application/auth_state_template.jinja +8 -0
- generators/static/templates/auth/infrastructure/auth_facade_module_template.jinja +16 -0
- generators/static/templates/auth/infrastructure/auth_facade_template.jinja +288 -0
- generators/static/templates/auth/infrastructure/mock_auth_facade_template.jinja +106 -0
- generators/static/templates/auth/model/auth_failure_template.jinja +11 -0
- generators/static/templates/auth/model/i_auth_facade_template.jinja +24 -0
- generators/static/templates/auth/model/user_template.jinja +34 -0
- generators/static/templates/auth/model/value_objects_template.jinja +54 -0
- generators/static/templates/auth/presentation/login_screen_template.jinja +24 -0
- generators/static/templates/auth/sign_in_form/application/sign_in_form_bloc_template.jinja +139 -0
- generators/static/templates/auth/sign_in_form/application/sign_in_form_event_template.jinja +15 -0
- generators/static/templates/auth/sign_in_form/application/sign_in_form_state_template.jinja +26 -0
- generators/static/templates/auth/sign_in_form/presentation/apple_form_template.jinja +16 -0
- generators/static/templates/auth/sign_in_form/presentation/email_password_form_template_INACTIVE.jinja +94 -0
- generators/static/templates/auth/sign_in_form/presentation/google_form_template.jinja +15 -0
- generators/static/templates/auth/sign_in_form/presentation/sign_in_form_template.jinja +381 -0
- generators/static/templates/component/component_bloc_template.jinja +49 -0
- generators/static/templates/component/component_event_template.jinja +7 -0
- generators/static/templates/component/component_form_bloc_template.jinja +56 -0
- generators/static/templates/component/component_form_event_template.jinja +7 -0
- generators/static/templates/component/component_form_state_template.jinja +18 -0
- generators/static/templates/component/component_form_widget_template.jinja +130 -0
- generators/static/templates/component/component_list_widget_template.jinja +95 -0
- generators/static/templates/component/component_state_template.jinja +9 -0
- generators/static/templates/component/component_widget_template.jinja +113 -0
- generators/static/templates/core/bloc/base_form_bloc_template.jinja +32 -0
- generators/static/templates/core/errors/error_localizer_template.jinja +61 -0
- generators/static/templates/core/infrastructure/base_mapper_template.jinja +21 -0
- generators/static/templates/core/infrastructure/base_repository_mixin_template.jinja +78 -0
- generators/static/templates/core/infrastructure/firebase_injectable_module_template.jinja +11 -0
- generators/static/templates/core/infrastructure/firestore_helpers_template.jinja +22 -0
- generators/static/templates/core/infrastructure/repository_error_handler_template.jinja +44 -0
- generators/static/templates/core/model/common_interfaces_template.jinja +3 -0
- generators/static/templates/core/model/entity_template.jinja +5 -0
- generators/static/templates/core/model/errors_template.jinja +15 -0
- generators/static/templates/core/model/failures_template.jinja +97 -0
- generators/static/templates/core/model/value_objects_template.jinja +135 -0
- generators/static/templates/core/model/value_validators_template.jinja +105 -0
- generators/static/templates/core/presentation/app_drawer_template.jinja +52 -0
- generators/static/templates/core/presentation/app_widget_template.jinja +41 -0
- generators/static/templates/core/presentation/bottom_nav_bar_template.jinja +32 -0
- generators/static/templates/domain/domain_mapper_template.jinja +39 -0
- generators/static/templates/domain/domain_remote_service_template.jinja +46 -0
- generators/static/templates/domain/domain_repository_template.jinja +90 -0
- generators/static/templates/domain/domain_service_module_template.jinja +23 -0
- generators/static/templates/domain/enum_template.jinja +3 -0
- generators/static/templates/domain/i_domain_service_template.jinja +18 -0
- generators/static/templates/domain/mock_domain_service_template.jinja +73 -0
- generators/static/templates/feature/feature_bloc_template.jinja +104 -0
- generators/static/templates/feature/feature_dto_template.jinja +26 -0
- generators/static/templates/feature/feature_entity_template.jinja +23 -0
- generators/static/templates/feature/feature_event_template.jinja +20 -0
- generators/static/templates/feature/feature_extensions_template.jinja +25 -0
- generators/static/templates/feature/feature_failure_template.jinja +11 -0
- generators/static/templates/feature/feature_page_template.jinja +113 -0
- generators/static/templates/feature/feature_repository_template.jinja +112 -0
- generators/static/templates/feature/feature_state_template.jinja +20 -0
- generators/static/templates/feature/i_feature_repository_template.jinja +21 -0
- generators/static/templates/feature/value_object_field_template.jinja +0 -0
- generators/static/templates/feature/value_object_template.jinja +0 -0
- generators/static/templates/feature/value_validators_template.jinja +57 -0
- generators/static/templates/home/home_screen_template.jinja +68 -0
- generators/static/templates/home/presentation/home_screen_template.jinja +52 -0
- generators/static/templates/home/screen_template.jinja +12 -0
- generators/static/templates/injection_template.jinja +8 -0
- generators/static/templates/logging/analytics_logging_template.jinja +42 -0
- generators/static/templates/logging/console_template.jinja +50 -0
- generators/static/templates/logging/logger_injectable_module_template.jinja +24 -0
- generators/static/templates/logging/logger_template.jinja +118 -0
- generators/static/templates/main_template.jinja +43 -0
- generators/static/templates/page_template.jinja +29 -0
- generators/static/templates/router_template.jinja +26 -0
- generators/static/templates/splash/presentation/splash_screen_auth_template.jinja +97 -0
- generators/static/templates/splash/presentation/splash_screen_template.jinja +51 -0
- generators/static/templates/storage/storage_repository_template.jinja +80 -0
- generators/static/templates/user_profile/infrastructure/user_profile_dto_template.jinja +27 -0
- generators/static/templates/user_profile/infrastructure/user_profile_mapper_template.jinja +43 -0
- generators/static/templates/user_profile/infrastructure/user_profile_repository_template.jinja +82 -0
- generators/static/templates/user_profile/infrastructure/user_profile_service_template.jinja +27 -0
- generators/static/templates/user_profile/model/i_user_profile_repository_template.jinja +34 -0
- generators/static/templates/user_profile/model/user_profile_failure_template.jinja +11 -0
- generators/static/templates/user_profile/model/user_profile_template.jinja +31 -0
- generators/static/templates/widgets/common/custom_scaffold_template.jinja +52 -0
- generators/static/templates/widgets/common/error_widget_template.jinja +22 -0
- generators/static/templates/widgets/common/loading_widget_template.jinja +14 -0
- generators/static/templates/widgets/common/unknown_state_widget_template.jinja +23 -0
- generators/templates/__init__.py +1 -0
- generators/templates/_core/core_generator.py +121 -0
- generators/templates/apis/apis_generator.py +50 -0
- generators/templates/auth/auth_generator.py +40 -0
- generators/templates/auth/sign_in_form_generator.py +22 -0
- generators/templates/copier.py +51 -0
- generators/templates/home/home_generator.py +26 -0
- generators/templates/lib/lib_generator.py +40 -0
- generators/templates/logging/logging_generator.py +28 -0
- generators/templates/main.py +75 -0
- generators/templates/splash/splash_generator.py +20 -0
- generators/templates/storage/storage_generator.py +5 -0
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
"""Feature generation functions"""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional, List, Dict
|
|
6
|
+
from generators.templates.copier import generate_file
|
|
7
|
+
from .utils import map_field_type, map_field_type_to_dto, to_pascal_case, to_pascal_case_preserve
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def create_feature_layers(feature_dir: Path, feature_name: str, field_list: list[dict], project_name: str, folder: Optional[str], lib_path: Optional[Path] = None, domain_folder: str = "domain") -> None:
|
|
11
|
+
"""Create all layers for a feature"""
|
|
12
|
+
# Build the import path prefix
|
|
13
|
+
if folder:
|
|
14
|
+
import_prefix = f"{folder.replace('/', '/')}/{feature_name}"
|
|
15
|
+
else:
|
|
16
|
+
import_prefix = feature_name
|
|
17
|
+
|
|
18
|
+
# Discover enums for enum-aware generation
|
|
19
|
+
known_enums: set = set()
|
|
20
|
+
enums_info: Dict[str, dict] = {}
|
|
21
|
+
_lib = lib_path or (feature_dir.parent.parent if feature_dir.parent.exists() else None)
|
|
22
|
+
if _lib and (_lib / domain_folder / "enums").exists():
|
|
23
|
+
enums_info = find_enums_with_info(_lib, domain_folder)
|
|
24
|
+
known_enums = set(enums_info.keys())
|
|
25
|
+
|
|
26
|
+
# Model layer
|
|
27
|
+
model_dir = feature_dir / "model"
|
|
28
|
+
model_dir.mkdir(exist_ok=True)
|
|
29
|
+
|
|
30
|
+
# Create value objects and validators
|
|
31
|
+
generate_value_objects_and_validators(import_prefix, field_list, model_dir, project_name, known_enums=known_enums)
|
|
32
|
+
|
|
33
|
+
# Create entity (domain model)
|
|
34
|
+
entity_fields = []
|
|
35
|
+
enum_imports = []
|
|
36
|
+
for field in field_list:
|
|
37
|
+
field_name = field['name']
|
|
38
|
+
field_type = field['type']
|
|
39
|
+
base_ft = field_type[:-1] if field_type.endswith('?') else field_type
|
|
40
|
+
if field_name == 'id':
|
|
41
|
+
entity_fields.append(f" required UniqueId id,")
|
|
42
|
+
elif base_ft in known_enums:
|
|
43
|
+
entity_fields.append(f" required {field_type} {field_name},")
|
|
44
|
+
info = enums_info.get(base_ft)
|
|
45
|
+
if info:
|
|
46
|
+
ei = f"import 'package:{project_name}/{info['folder']}/{info['file_stem']}.dart';"
|
|
47
|
+
if ei not in enum_imports:
|
|
48
|
+
enum_imports.append(ei)
|
|
49
|
+
else:
|
|
50
|
+
entity_fields.append(f" required {to_pascal_case_preserve(field_name)} {field_name},")
|
|
51
|
+
|
|
52
|
+
entity_import = f"import 'package:{project_name}/{import_prefix}/model/value_objects.dart';"
|
|
53
|
+
if enum_imports:
|
|
54
|
+
entity_import = entity_import + "\n" + "\n".join(enum_imports)
|
|
55
|
+
|
|
56
|
+
entity_fields_str = "\n".join(entity_fields)
|
|
57
|
+
generate_file(project_name, model_dir, "feature/feature_entity_template.jinja", f"{feature_name}.dart", {
|
|
58
|
+
"feature_name": feature_name,
|
|
59
|
+
"fields": entity_fields_str,
|
|
60
|
+
"entity_import": entity_import
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
# Create failure
|
|
64
|
+
generate_file(project_name, model_dir, "feature/feature_failure_template.jinja", f"{feature_name}_failure.dart", {"feature_name": feature_name})
|
|
65
|
+
|
|
66
|
+
# Infrastructure layer
|
|
67
|
+
infra_dir = feature_dir / "infrastructure"
|
|
68
|
+
infra_dir.mkdir(exist_ok=True)
|
|
69
|
+
|
|
70
|
+
# Create DTO (enum -> String in DTO)
|
|
71
|
+
dto_fields = ",\n".join([f" required {map_field_type_to_dto(field['type'], known_enums=known_enums) if known_enums else map_field_type(field['type'])} {field['name']}" for field in field_list])
|
|
72
|
+
generate_file(project_name, infra_dir, "feature/feature_dto_template.jinja", f"{feature_name}_dto.dart", {"feature_name": feature_name, "fields": dto_fields})
|
|
73
|
+
|
|
74
|
+
# Create extensions for DTO-Domain conversions
|
|
75
|
+
generate_extensions(feature_name, field_list, infra_dir, project_name, import_prefix, known_enums=known_enums, enums_info=enums_info)
|
|
76
|
+
|
|
77
|
+
i_repo_import = f"""import 'package:{project_name}/{import_prefix}/model/{feature_name}.dart';
|
|
78
|
+
import 'package:{project_name}/{import_prefix}/model/{feature_name}_failure.dart';
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
# Create repository interface
|
|
82
|
+
generate_file(project_name, model_dir, "feature/i_feature_repository_template.jinja", f"i_{feature_name}_repository.dart", {
|
|
83
|
+
"feature_name": feature_name,
|
|
84
|
+
"i_repo_import": i_repo_import
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
repo_import = f"""import 'package:{project_name}/{import_prefix}/infrastructure/{feature_name}_extensions.dart';
|
|
88
|
+
import 'package:{project_name}/{import_prefix}/model/{feature_name}.dart';
|
|
89
|
+
import 'package:{project_name}/{import_prefix}/infrastructure/{feature_name}_dto.dart';
|
|
90
|
+
import 'package:{project_name}/{import_prefix}/model/{feature_name}_failure.dart';
|
|
91
|
+
import 'package:{project_name}/{import_prefix}/model/i_{feature_name}_repository.dart';
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
# Create repository implementation
|
|
95
|
+
generate_file(project_name, infra_dir, "feature/feature_repository_template.jinja", f"{feature_name}_repository.dart", {
|
|
96
|
+
"feature_name": feature_name,
|
|
97
|
+
"repo_import": repo_import
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
# Application layer
|
|
101
|
+
app_dir = feature_dir / "application"
|
|
102
|
+
app_dir.mkdir(exist_ok=True)
|
|
103
|
+
|
|
104
|
+
# Create BLoC files
|
|
105
|
+
generate_file(project_name, app_dir, "feature/feature_event_template.jinja", f"{feature_name}_event.dart", {"feature_name": feature_name})
|
|
106
|
+
generate_file(project_name, app_dir, "feature/feature_state_template.jinja", f"{feature_name}_state.dart", {"feature_name": feature_name})
|
|
107
|
+
generate_file(project_name, app_dir, "feature/feature_bloc_template.jinja", f"{feature_name}_bloc.dart", {
|
|
108
|
+
"feature_name": feature_name,
|
|
109
|
+
"feature_import_prefix": f"{project_name}/{import_prefix}"
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
# Presentation layer
|
|
113
|
+
presentation_dir = feature_dir / "presentation"
|
|
114
|
+
presentation_dir.mkdir(exist_ok=True)
|
|
115
|
+
|
|
116
|
+
# Create page
|
|
117
|
+
generate_file(project_name, presentation_dir, "feature/feature_page_template.jinja", f"{feature_name}_page.dart", {
|
|
118
|
+
"feature_name": feature_name,
|
|
119
|
+
"model_import_prefix": f"{project_name}/{import_prefix}",
|
|
120
|
+
"model_file_stem": feature_name,
|
|
121
|
+
"model_class_pascal": to_pascal_case_preserve(feature_name),
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def generate_value_objects_and_validators(import_prefix: str, field_list: list[dict], model_dir: Path, project_name: str, known_enums: Optional[set] = None, skip_local_validators: bool = False) -> None:
|
|
126
|
+
"""Generate consolidated value objects and optionally validators.
|
|
127
|
+
|
|
128
|
+
When skip_local_validators is True (e.g. for domain entities), value_validators.dart
|
|
129
|
+
is not generated; value_objects use right(input) with a TODO pointing to core validators.
|
|
130
|
+
"""
|
|
131
|
+
value_objects_content = generate_consolidated_value_objects(
|
|
132
|
+
import_prefix, field_list, project_name,
|
|
133
|
+
known_enums=known_enums,
|
|
134
|
+
use_placeholder_validation=skip_local_validators,
|
|
135
|
+
)
|
|
136
|
+
(model_dir / "value_objects.dart").write_text(value_objects_content)
|
|
137
|
+
|
|
138
|
+
if not skip_local_validators:
|
|
139
|
+
generate_value_validators(field_list, model_dir, project_name, known_enums=known_enums)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def generate_consolidated_value_objects(import_prefix: str, field_list: list[dict], project_name: str, known_enums: Optional[set] = None, use_placeholder_validation: bool = False) -> str:
|
|
143
|
+
"""Generate consolidated value objects file.
|
|
144
|
+
|
|
145
|
+
When use_placeholder_validation is True, value_objects do not import local
|
|
146
|
+
value_validators; each factory uses right(input) with a TODO to core validators.
|
|
147
|
+
"""
|
|
148
|
+
imports = f"import 'package:dartz/dartz.dart';\n"
|
|
149
|
+
imports += f"import 'package:{project_name}/core/model/failures.dart';\n"
|
|
150
|
+
imports += f"import 'package:{project_name}/core/model/value_objects.dart';\n"
|
|
151
|
+
if not use_placeholder_validation:
|
|
152
|
+
imports += f"import 'package:{project_name}/{import_prefix}/model/value_validators.dart';\n"
|
|
153
|
+
imports += "\n"
|
|
154
|
+
|
|
155
|
+
_enums = known_enums or set()
|
|
156
|
+
|
|
157
|
+
field_vos = []
|
|
158
|
+
for field in field_list:
|
|
159
|
+
field_name = field['name']
|
|
160
|
+
field_type = field['type']
|
|
161
|
+
|
|
162
|
+
if field_name == 'id':
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
base_ft = field_type[:-1] if field_type.endswith('?') else field_type
|
|
166
|
+
|
|
167
|
+
if base_ft in _enums:
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
if '<' in base_ft or (base_ft and base_ft[0].isupper() and base_ft not in ['String', 'DateTime']):
|
|
171
|
+
continue
|
|
172
|
+
|
|
173
|
+
mapped_field_type = map_field_type(base_ft)
|
|
174
|
+
capitalized_name = to_pascal_case_preserve(field_name)
|
|
175
|
+
|
|
176
|
+
if use_placeholder_validation:
|
|
177
|
+
factory_body = (
|
|
178
|
+
f" // TODO: Validate input here. See core/model/value_validators.dart for examples.\n"
|
|
179
|
+
f" return {capitalized_name}._(right(input));"
|
|
180
|
+
)
|
|
181
|
+
else:
|
|
182
|
+
factory_body = f" return {capitalized_name}._(validate{capitalized_name}(input));"
|
|
183
|
+
|
|
184
|
+
field_vo = f"""
|
|
185
|
+
class {capitalized_name} extends ValueObject<{mapped_field_type}> {{
|
|
186
|
+
@override
|
|
187
|
+
final Either<ValueFailure<{mapped_field_type}>, {mapped_field_type}> value;
|
|
188
|
+
|
|
189
|
+
factory {capitalized_name}({mapped_field_type} input) {{
|
|
190
|
+
{factory_body}
|
|
191
|
+
}}
|
|
192
|
+
|
|
193
|
+
const {capitalized_name}._(this.value);
|
|
194
|
+
}}
|
|
195
|
+
"""
|
|
196
|
+
field_vos.append(field_vo)
|
|
197
|
+
|
|
198
|
+
return imports + "\n".join(field_vos)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def generate_value_validators(field_list: list[dict], model_dir: Path, project_name: str, known_enums: Optional[set] = None) -> None:
|
|
202
|
+
"""Generate value validators file using Jinja template"""
|
|
203
|
+
_enums = known_enums or set()
|
|
204
|
+
# Pre-process field_list: add pascal_name and strip nullable suffix so
|
|
205
|
+
# the template matches base types (e.g., String not String?)
|
|
206
|
+
processed_field_list = []
|
|
207
|
+
for field in field_list:
|
|
208
|
+
base_ft = field['type'][:-1] if field['type'].endswith('?') else field['type']
|
|
209
|
+
# Skip enums — they don't need validators
|
|
210
|
+
if base_ft in _enums:
|
|
211
|
+
continue
|
|
212
|
+
field_dict = dict(field) # Create a copy
|
|
213
|
+
field_dict['pascal_name'] = to_pascal_case_preserve(field['name'])
|
|
214
|
+
if field_dict['type'].endswith('?'):
|
|
215
|
+
field_dict['type'] = field_dict['type'][:-1]
|
|
216
|
+
processed_field_list.append(field_dict)
|
|
217
|
+
|
|
218
|
+
generate_file(project_name, model_dir, "feature/value_validators_template.jinja", "value_validators.dart", {
|
|
219
|
+
"project_name": project_name,
|
|
220
|
+
"field_list": processed_field_list
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def generate_extensions(feature_name: str, field_list: list[dict], infra_dir: Path, project_name: str, import_prefix: str, known_enums: Optional[set] = None, enums_info: Optional[Dict[str, dict]] = None) -> None:
|
|
225
|
+
"""Generate DTO-Domain conversion extensions"""
|
|
226
|
+
_enums = known_enums or set()
|
|
227
|
+
_enums_info = enums_info or {}
|
|
228
|
+
|
|
229
|
+
# Generate toDto() method
|
|
230
|
+
to_dto_fields = []
|
|
231
|
+
for field in field_list:
|
|
232
|
+
field_name = field['name']
|
|
233
|
+
field_type = field['type']
|
|
234
|
+
base_ft = field_type[:-1] if field_type.endswith('?') else field_type
|
|
235
|
+
if field_name == 'id':
|
|
236
|
+
to_dto_fields.append(f" id: id.getOrCrash()")
|
|
237
|
+
elif base_ft in _enums:
|
|
238
|
+
to_dto_fields.append(f" {field_name}: {field_name}.name")
|
|
239
|
+
else:
|
|
240
|
+
to_dto_fields.append(f" {field_name}: {field_name}.getOrCrash()")
|
|
241
|
+
|
|
242
|
+
# Generate fromDto() method
|
|
243
|
+
from_dto_fields = []
|
|
244
|
+
for field in field_list:
|
|
245
|
+
field_name = field['name']
|
|
246
|
+
field_type = field['type']
|
|
247
|
+
base_ft = field_type[:-1] if field_type.endswith('?') else field_type
|
|
248
|
+
if field_name == 'id':
|
|
249
|
+
from_dto_fields.append(f" id: UniqueId.fromUniqueString(id)")
|
|
250
|
+
elif base_ft in _enums:
|
|
251
|
+
from_dto_fields.append(f" {field_name}: {base_ft}.values.byName({field_name})")
|
|
252
|
+
else:
|
|
253
|
+
capitalized_name = to_pascal_case_preserve(field_name)
|
|
254
|
+
from_dto_fields.append(f" {field_name}: {capitalized_name}({field_name})")
|
|
255
|
+
|
|
256
|
+
# Build enum imports for extensions
|
|
257
|
+
enum_ext_imports = []
|
|
258
|
+
for field in field_list:
|
|
259
|
+
ft = field['type']
|
|
260
|
+
base_ft = ft[:-1] if ft.endswith('?') else ft
|
|
261
|
+
if base_ft in _enums:
|
|
262
|
+
info = _enums_info.get(base_ft)
|
|
263
|
+
if info:
|
|
264
|
+
ei = f"import 'package:{project_name}/{info['folder']}/{info['file_stem']}.dart';"
|
|
265
|
+
if ei not in enum_ext_imports:
|
|
266
|
+
enum_ext_imports.append(ei)
|
|
267
|
+
|
|
268
|
+
extra_imports = "\n".join(enum_ext_imports)
|
|
269
|
+
if extra_imports:
|
|
270
|
+
extra_imports = "\n" + extra_imports
|
|
271
|
+
|
|
272
|
+
extension_content = f"""import 'package:{project_name}/core/model/value_objects.dart';
|
|
273
|
+
import 'package:{project_name}/{import_prefix}/model/{feature_name}.dart';
|
|
274
|
+
import 'package:{project_name}/{import_prefix}/model/value_objects.dart';
|
|
275
|
+
import 'package:{project_name}/{import_prefix}/infrastructure/{feature_name}_dto.dart';{extra_imports}
|
|
276
|
+
|
|
277
|
+
extension {feature_name.capitalize()}DtoX on {feature_name.capitalize()}Dto {{
|
|
278
|
+
{feature_name.capitalize()} toDomain() {{
|
|
279
|
+
return {feature_name.capitalize()}(
|
|
280
|
+
{','.join(from_dto_fields)}
|
|
281
|
+
);
|
|
282
|
+
}}
|
|
283
|
+
}}
|
|
284
|
+
|
|
285
|
+
extension {feature_name.capitalize()}DomainX on {feature_name.capitalize()} {{
|
|
286
|
+
{feature_name.capitalize()}Dto toDto() {{
|
|
287
|
+
return {feature_name.capitalize()}Dto(
|
|
288
|
+
{','.join(to_dto_fields)}
|
|
289
|
+
);
|
|
290
|
+
}}
|
|
291
|
+
}}
|
|
292
|
+
|
|
293
|
+
"""
|
|
294
|
+
(infra_dir / f"{feature_name}_extensions.dart").write_text(extension_content)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _is_entity_file(file_path: Path) -> bool:
|
|
298
|
+
"""Check if a .dart file contains a freezed entity class definition.
|
|
299
|
+
|
|
300
|
+
Excludes failure files, interfaces, value objects, validators,
|
|
301
|
+
and generated files (.freezed.dart, .g.dart).
|
|
302
|
+
"""
|
|
303
|
+
name = file_path.name
|
|
304
|
+
if name.endswith('.freezed.dart') or name.endswith('.g.dart'):
|
|
305
|
+
return False
|
|
306
|
+
if name.startswith('i_') or name.endswith('_failure.dart'):
|
|
307
|
+
return False
|
|
308
|
+
if name in ('value_objects.dart', 'value_validators.dart', 'common_interfaces.dart'):
|
|
309
|
+
return False
|
|
310
|
+
|
|
311
|
+
try:
|
|
312
|
+
content = file_path.read_text()
|
|
313
|
+
return bool(re.search(r'abstract class (\w+)\s+with\s+_\$', content))
|
|
314
|
+
except Exception:
|
|
315
|
+
return False
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _get_class_name_from_file(file_path: Path) -> Optional[str]:
|
|
319
|
+
"""Extract the freezed class name from a .dart entity file."""
|
|
320
|
+
try:
|
|
321
|
+
content = file_path.read_text()
|
|
322
|
+
match = re.search(r'abstract class (\w+)\s+with', content)
|
|
323
|
+
if match:
|
|
324
|
+
return match.group(1)
|
|
325
|
+
except Exception:
|
|
326
|
+
pass
|
|
327
|
+
return None
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def find_domain_models(lib_path: Path, domain_folder: str) -> List[str]:
|
|
331
|
+
"""Find all available domain models in the domain folder.
|
|
332
|
+
|
|
333
|
+
Scans lib/{domain_folder}/ subdirectories for model/ folders containing
|
|
334
|
+
entity .dart files. Discovers entities even when the file name differs
|
|
335
|
+
from the containing folder (e.g., user_profile.dart inside auth/).
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
lib_path: Path to lib/ directory
|
|
339
|
+
domain_folder: Name of domain folder (e.g., 'domain')
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
List of model file stems (e.g., ['test', 'user_profile'])
|
|
343
|
+
"""
|
|
344
|
+
models_map = find_domain_models_with_class_names(lib_path, domain_folder)
|
|
345
|
+
return sorted(models_map.keys())
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def get_domain_model_class_name(lib_path: Path, domain_folder: str, folder_name: str) -> Optional[str]:
|
|
349
|
+
"""Get the class name (PascalCase) from a domain model entity file.
|
|
350
|
+
|
|
351
|
+
First checks for an entity file matching folder_name, then scans
|
|
352
|
+
all entity files in the folder's model/ directory.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
lib_path: Path to lib/ directory
|
|
356
|
+
domain_folder: Name of domain folder (e.g., 'domain')
|
|
357
|
+
folder_name: Folder name or file stem of the model (snake_case)
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
Class name in PascalCase (e.g., 'TodoItem') or None if not found
|
|
361
|
+
"""
|
|
362
|
+
domain_path = lib_path / domain_folder
|
|
363
|
+
|
|
364
|
+
# Try exact match first: folder_name is the file stem
|
|
365
|
+
for item in domain_path.iterdir():
|
|
366
|
+
if item.is_dir():
|
|
367
|
+
candidate = item / "model" / f"{folder_name}.dart"
|
|
368
|
+
if candidate.exists():
|
|
369
|
+
return _get_class_name_from_file(candidate)
|
|
370
|
+
|
|
371
|
+
return None
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def find_domain_models_with_class_names(lib_path: Path, domain_folder: str) -> Dict[str, dict]:
|
|
375
|
+
"""Find all available domain models with their class names and folder info.
|
|
376
|
+
|
|
377
|
+
Scans all model/ subdirectories for entity files, including entities
|
|
378
|
+
whose file name differs from the containing folder.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
lib_path: Path to lib/ directory
|
|
382
|
+
domain_folder: Name of domain folder (e.g., 'domain')
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
Dictionary mapping file stems to model info dicts:
|
|
386
|
+
{
|
|
387
|
+
'test': {'class_name': 'Test', 'folder': 'test'},
|
|
388
|
+
'user_profile': {'class_name': 'UserProfile', 'folder': 'auth'},
|
|
389
|
+
}
|
|
390
|
+
"""
|
|
391
|
+
domain_path = lib_path / domain_folder
|
|
392
|
+
|
|
393
|
+
if not domain_path.exists():
|
|
394
|
+
return {}
|
|
395
|
+
|
|
396
|
+
models_map = {}
|
|
397
|
+
for item in domain_path.iterdir():
|
|
398
|
+
if item.is_dir():
|
|
399
|
+
model_dir = item / "model"
|
|
400
|
+
if not (model_dir.exists() and model_dir.is_dir()):
|
|
401
|
+
continue
|
|
402
|
+
for dart_file in model_dir.iterdir():
|
|
403
|
+
if dart_file.suffix == '.dart' and _is_entity_file(dart_file):
|
|
404
|
+
class_name = _get_class_name_from_file(dart_file)
|
|
405
|
+
if class_name:
|
|
406
|
+
file_stem = dart_file.stem
|
|
407
|
+
models_map[file_stem] = {
|
|
408
|
+
'class_name': class_name,
|
|
409
|
+
'folder': item.name,
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return models_map
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def find_enums(lib_path: Path, domain_folder: str) -> List[str]:
|
|
416
|
+
"""Find all available enum names in lib/<domain_folder>/enums/.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
lib_path: Path to lib/ directory
|
|
420
|
+
domain_folder: Name of domain folder (e.g., 'domain')
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
Sorted list of enum names (PascalCase, e.g., ['EventStatus', 'Priority'])
|
|
424
|
+
"""
|
|
425
|
+
enums_info = find_enums_with_info(lib_path, domain_folder)
|
|
426
|
+
return sorted(enums_info.keys())
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def find_enums_with_info(lib_path: Path, domain_folder: str) -> Dict[str, dict]:
|
|
430
|
+
"""Find all enums with their file stems and folder info.
|
|
431
|
+
|
|
432
|
+
Scans lib/<domain_folder>/enums/ for .dart files containing ``enum Name {``.
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
lib_path: Path to lib/ directory
|
|
436
|
+
domain_folder: Name of domain folder (e.g., 'domain')
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
Dictionary mapping enum name to info dict::
|
|
440
|
+
|
|
441
|
+
{
|
|
442
|
+
'EventStatus': {
|
|
443
|
+
'file_stem': 'event_status',
|
|
444
|
+
'folder': 'domain/enums',
|
|
445
|
+
},
|
|
446
|
+
}
|
|
447
|
+
"""
|
|
448
|
+
enums_dir = lib_path / domain_folder / "enums"
|
|
449
|
+
|
|
450
|
+
if not enums_dir.exists():
|
|
451
|
+
return {}
|
|
452
|
+
|
|
453
|
+
enums_map: Dict[str, dict] = {}
|
|
454
|
+
for dart_file in enums_dir.iterdir():
|
|
455
|
+
if dart_file.suffix != '.dart':
|
|
456
|
+
continue
|
|
457
|
+
try:
|
|
458
|
+
content = dart_file.read_text()
|
|
459
|
+
except Exception:
|
|
460
|
+
continue
|
|
461
|
+
for match in re.finditer(r'enum\s+(\w+)\s*\{', content):
|
|
462
|
+
enum_name = match.group(1)
|
|
463
|
+
enums_map[enum_name] = {
|
|
464
|
+
'file_stem': dart_file.stem,
|
|
465
|
+
'folder': f"{domain_folder}/enums",
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return enums_map
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def create_presentation_feature_layers(feature_dir: Path, feature_name: str, domain_model_name: str, domain_folder: str, project_name: str, folder: Optional[str], domain_model_folder: Optional[str] = None) -> None:
|
|
472
|
+
"""Create only application and presentation layers for a feature.
|
|
473
|
+
|
|
474
|
+
This function creates a feature that uses an existing domain model.
|
|
475
|
+
It does NOT create model or infrastructure layers - those should
|
|
476
|
+
already exist in the domain folder.
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
feature_dir: Path to feature directory
|
|
480
|
+
feature_name: Name of the feature
|
|
481
|
+
domain_model_name: Name of the domain model to use (file stem)
|
|
482
|
+
domain_folder: Name of domain folder (e.g., 'domain')
|
|
483
|
+
project_name: Name of the project
|
|
484
|
+
folder: Optional folder path for the feature
|
|
485
|
+
domain_model_folder: Containing folder for the model. Defaults to domain_model_name.
|
|
486
|
+
"""
|
|
487
|
+
if domain_model_folder is None:
|
|
488
|
+
domain_model_folder = domain_model_name
|
|
489
|
+
|
|
490
|
+
# Build import paths
|
|
491
|
+
if folder:
|
|
492
|
+
feature_import_prefix = f"{folder.replace('/', '/')}/{feature_name}"
|
|
493
|
+
else:
|
|
494
|
+
feature_import_prefix = feature_name
|
|
495
|
+
|
|
496
|
+
domain_import_prefix = f"{domain_folder}/{domain_model_folder}"
|
|
497
|
+
|
|
498
|
+
# Application layer
|
|
499
|
+
app_dir = feature_dir / "application"
|
|
500
|
+
app_dir.mkdir(exist_ok=True)
|
|
501
|
+
|
|
502
|
+
# Get PascalCase names
|
|
503
|
+
# Use to_pascal_case_preserve to handle camelCase names like "todoPage" -> "TodoPage"
|
|
504
|
+
feature_pascal = to_pascal_case_preserve(feature_name)
|
|
505
|
+
domain_model_pascal = to_pascal_case_preserve(domain_model_name)
|
|
506
|
+
|
|
507
|
+
# Build Freezed mixin names correctly (e.g., _$TodoPageEvent)
|
|
508
|
+
freezed_mixin_event = "_$" + feature_pascal + "Event"
|
|
509
|
+
freezed_mixin_state = "_$" + feature_pascal + "State"
|
|
510
|
+
|
|
511
|
+
# Create event file that uses domain model for item types
|
|
512
|
+
event_content = f"""/*
|
|
513
|
+
* BLoC events template for feature operations
|
|
514
|
+
* Defines immutable events using Freezed that trigger BLoC actions:
|
|
515
|
+
* - LoadRequested: Fetch all items
|
|
516
|
+
* - CreateRequested: Create new item
|
|
517
|
+
* - UpdateRequested: Update existing item
|
|
518
|
+
* - DeleteRequested: Remove item by ID
|
|
519
|
+
*
|
|
520
|
+
* Events represent user intentions and trigger state changes
|
|
521
|
+
*/
|
|
522
|
+
|
|
523
|
+
part of '{feature_name}_bloc.dart';
|
|
524
|
+
|
|
525
|
+
@freezed
|
|
526
|
+
abstract class {feature_pascal}Event with {freezed_mixin_event} {{
|
|
527
|
+
const factory {feature_pascal}Event.loadRequested() = LoadRequested;
|
|
528
|
+
const factory {feature_pascal}Event.createRequested({domain_model_pascal} item) = CreateRequested;
|
|
529
|
+
const factory {feature_pascal}Event.updateRequested({domain_model_pascal} item) = UpdateRequested;
|
|
530
|
+
const factory {feature_pascal}Event.deleteRequested(String id) = DeleteRequested;
|
|
531
|
+
}}
|
|
532
|
+
"""
|
|
533
|
+
(app_dir / f"{feature_name}_event.dart").write_text(event_content)
|
|
534
|
+
|
|
535
|
+
# Create state file that uses domain model for list types
|
|
536
|
+
state_content = f"""/*
|
|
537
|
+
* BLoC states template for feature UI representation
|
|
538
|
+
* Defines immutable states using Freezed for different UI states:
|
|
539
|
+
* - Initial: App just started, no data loaded
|
|
540
|
+
* - Loading: Data is being fetched/processed
|
|
541
|
+
* - Loaded: Data successfully retrieved with items list
|
|
542
|
+
* - Error: Something went wrong with error message
|
|
543
|
+
*
|
|
544
|
+
* States represent the current condition of the feature
|
|
545
|
+
*/
|
|
546
|
+
|
|
547
|
+
part of '{feature_name}_bloc.dart';
|
|
548
|
+
|
|
549
|
+
@freezed
|
|
550
|
+
abstract class {feature_pascal}State with {freezed_mixin_state} {{
|
|
551
|
+
const factory {feature_pascal}State.initial() = Initial;
|
|
552
|
+
const factory {feature_pascal}State.loading() = Loading;
|
|
553
|
+
const factory {feature_pascal}State.loaded(List<{domain_model_pascal}> items) = Loaded;
|
|
554
|
+
const factory {feature_pascal}State.error(String message) = Error;
|
|
555
|
+
}}
|
|
556
|
+
"""
|
|
557
|
+
(app_dir / f"{feature_name}_state.dart").write_text(state_content)
|
|
558
|
+
|
|
559
|
+
# Create BLoC that uses domain repository
|
|
560
|
+
bloc_content = f"""import 'package:bloc/bloc.dart';
|
|
561
|
+
import 'package:dartz/dartz.dart';
|
|
562
|
+
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
563
|
+
import 'package:injectable/injectable.dart';
|
|
564
|
+
import 'package:{project_name}/{domain_import_prefix}/model/{domain_model_name}.dart';
|
|
565
|
+
import 'package:{project_name}/{domain_import_prefix}/model/{domain_model_name}_failure.dart';
|
|
566
|
+
import 'package:{project_name}/{domain_import_prefix}/model/i_{domain_model_name}_repository.dart';
|
|
567
|
+
|
|
568
|
+
part '{feature_name}_bloc.freezed.dart';
|
|
569
|
+
part '{feature_name}_event.dart';
|
|
570
|
+
part '{feature_name}_state.dart';
|
|
571
|
+
|
|
572
|
+
class {feature_pascal}Bloc extends Bloc<{feature_pascal}Event, {feature_pascal}State> {{
|
|
573
|
+
final I{domain_model_pascal}Repository _repository;
|
|
574
|
+
|
|
575
|
+
{feature_pascal}Bloc(this._repository) : super(const {feature_pascal}State.initial()) {{
|
|
576
|
+
on<LoadRequested>(_onLoadRequested);
|
|
577
|
+
on<CreateRequested>(_onCreateRequested);
|
|
578
|
+
on<UpdateRequested>(_onUpdateRequested);
|
|
579
|
+
on<DeleteRequested>(_onDeleteRequested);
|
|
580
|
+
}}
|
|
581
|
+
|
|
582
|
+
Future<void> _onLoadRequested(LoadRequested event, Emitter<{feature_pascal}State> emit) async {{
|
|
583
|
+
emit(const {feature_pascal}State.loading());
|
|
584
|
+
final Either<{domain_model_pascal}Failure, List<{domain_model_pascal}>> result =
|
|
585
|
+
await _repository.getAll();
|
|
586
|
+
result.fold(
|
|
587
|
+
({domain_model_pascal}Failure failure) => emit({feature_pascal}State.error(failure.toString())),
|
|
588
|
+
(List<{domain_model_pascal}> items) => emit({feature_pascal}State.loaded(items)),
|
|
589
|
+
);
|
|
590
|
+
}}
|
|
591
|
+
|
|
592
|
+
Future<void> _onCreateRequested(CreateRequested event, Emitter<{feature_pascal}State> emit) async {{
|
|
593
|
+
emit(const {feature_pascal}State.loading());
|
|
594
|
+
final Either<{domain_model_pascal}Failure, Unit> result = await _repository.create(event.item);
|
|
595
|
+
result.fold(
|
|
596
|
+
({domain_model_pascal}Failure failure) => emit({feature_pascal}State.error(failure.toString())),
|
|
597
|
+
(Unit _) async {{
|
|
598
|
+
final Either<{domain_model_pascal}Failure, List<{domain_model_pascal}>> itemsResult =
|
|
599
|
+
await _repository.getAll();
|
|
600
|
+
itemsResult.fold(
|
|
601
|
+
({domain_model_pascal}Failure failure) => emit({feature_pascal}State.error(failure.toString())),
|
|
602
|
+
(List<{domain_model_pascal}> items) => emit({feature_pascal}State.loaded(items)),
|
|
603
|
+
);
|
|
604
|
+
}},
|
|
605
|
+
);
|
|
606
|
+
}}
|
|
607
|
+
|
|
608
|
+
Future<void> _onUpdateRequested(UpdateRequested event, Emitter<{feature_pascal}State> emit) async {{
|
|
609
|
+
emit(const {feature_pascal}State.loading());
|
|
610
|
+
final Either<{domain_model_pascal}Failure, Unit> result = await _repository.update(event.item);
|
|
611
|
+
result.fold(
|
|
612
|
+
({domain_model_pascal}Failure failure) => emit({feature_pascal}State.error(failure.toString())),
|
|
613
|
+
(Unit _) async {{
|
|
614
|
+
final Either<{domain_model_pascal}Failure, List<{domain_model_pascal}>> itemsResult =
|
|
615
|
+
await _repository.getAll();
|
|
616
|
+
itemsResult.fold(
|
|
617
|
+
({domain_model_pascal}Failure failure) => emit({feature_pascal}State.error(failure.toString())),
|
|
618
|
+
(List<{domain_model_pascal}> items) => emit({feature_pascal}State.loaded(items)),
|
|
619
|
+
);
|
|
620
|
+
}},
|
|
621
|
+
);
|
|
622
|
+
}}
|
|
623
|
+
|
|
624
|
+
Future<void> _onDeleteRequested(DeleteRequested event, Emitter<{feature_pascal}State> emit) async {{
|
|
625
|
+
emit(const {feature_pascal}State.loading());
|
|
626
|
+
final Either<{domain_model_pascal}Failure, Unit> result = await _repository.delete(event.id);
|
|
627
|
+
result.fold(
|
|
628
|
+
({domain_model_pascal}Failure failure) => emit({feature_pascal}State.error(failure.toString())),
|
|
629
|
+
(Unit _) async {{
|
|
630
|
+
final Either<{domain_model_pascal}Failure, List<{domain_model_pascal}>> itemsResult =
|
|
631
|
+
await _repository.getAll();
|
|
632
|
+
itemsResult.fold(
|
|
633
|
+
({domain_model_pascal}Failure failure) => emit({feature_pascal}State.error(failure.toString())),
|
|
634
|
+
(List<{domain_model_pascal}> items) => emit({feature_pascal}State.loaded(items)),
|
|
635
|
+
);
|
|
636
|
+
}},
|
|
637
|
+
);
|
|
638
|
+
}}
|
|
639
|
+
}}
|
|
640
|
+
"""
|
|
641
|
+
(app_dir / f"{feature_name}_bloc.dart").write_text(bloc_content)
|
|
642
|
+
|
|
643
|
+
# Presentation layer
|
|
644
|
+
presentation_dir = feature_dir / "presentation"
|
|
645
|
+
presentation_dir.mkdir(exist_ok=True)
|
|
646
|
+
|
|
647
|
+
# Create page
|
|
648
|
+
generate_file(project_name, presentation_dir, "feature/feature_page_template.jinja", f"{feature_name}_page.dart", {
|
|
649
|
+
"feature_name": feature_name,
|
|
650
|
+
"model_import_prefix": f"{project_name}/{domain_import_prefix}",
|
|
651
|
+
"model_file_stem": domain_model_name,
|
|
652
|
+
"model_class_pascal": domain_model_pascal,
|
|
653
|
+
})
|
|
654
|
+
|