kalo-cli 0.1.0

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 (56) hide show
  1. package/README.md +47 -0
  2. package/bin/kalo.ts +17 -0
  3. package/generators/ai-enhancer/index.ts +281 -0
  4. package/generators/ai-enhancer/keywords.json +1158 -0
  5. package/generators/constants.ts +52 -0
  6. package/generators/django-app/index.ts +67 -0
  7. package/generators/django-app/templates/admin.py.hbs +6 -0
  8. package/generators/django-app/templates/apps.py.hbs +9 -0
  9. package/generators/django-app/templates/init.py.hbs +0 -0
  10. package/generators/django-app/templates/models_init.py.hbs +2 -0
  11. package/generators/django-app/templates/urls.py.hbs +8 -0
  12. package/generators/django-app/templates/views.py.hbs +5 -0
  13. package/generators/django-channel/index.ts +78 -0
  14. package/generators/django-channel/templates/consumer.py.hbs +47 -0
  15. package/generators/django-channel/templates/routing.py.hbs +8 -0
  16. package/generators/django-form/index.ts +62 -0
  17. package/generators/django-form/templates/form.py.hbs +12 -0
  18. package/generators/django-form/templates/forms_file.py.hbs +6 -0
  19. package/generators/django-form/templates/model_form.py.hbs +18 -0
  20. package/generators/django-view/index.ts +95 -0
  21. package/generators/django-view/templates/view_cbv.py.hbs +11 -0
  22. package/generators/django-view/templates/view_fbv.py.hbs +7 -0
  23. package/generators/django-view/templates/view_template.html.hbs +8 -0
  24. package/generators/docs/index.ts +36 -0
  25. package/generators/help/index.ts +84 -0
  26. package/generators/main/index.ts +429 -0
  27. package/generators/utils/ai/common.ts +141 -0
  28. package/generators/utils/ai/index.ts +2 -0
  29. package/generators/utils/analysis.ts +82 -0
  30. package/generators/utils/code-manipulation.ts +119 -0
  31. package/generators/utils/filesystem.ts +64 -0
  32. package/generators/utils/index.ts +47 -0
  33. package/generators/utils/plop-actions.ts +61 -0
  34. package/generators/utils/search.ts +24 -0
  35. package/generators/wagtail-admin/index.ts +122 -0
  36. package/generators/wagtail-admin/templates/admin_view.html.hbs +21 -0
  37. package/generators/wagtail-admin/templates/admin_view.py.hbs +15 -0
  38. package/generators/wagtail-admin/templates/component.html.hbs +6 -0
  39. package/generators/wagtail-admin/templates/component.py.hbs +11 -0
  40. package/generators/wagtail-admin/templates/wagtail_hooks.py.hbs +18 -0
  41. package/generators/wagtail-block/index.ts +55 -0
  42. package/generators/wagtail-block/templates/block_class.py.hbs +13 -0
  43. package/generators/wagtail-block/templates/block_template.html.hbs +5 -0
  44. package/generators/wagtail-page/actions/model.ts +18 -0
  45. package/generators/wagtail-page/actions/orderable.ts +21 -0
  46. package/generators/wagtail-page/actions/page.ts +40 -0
  47. package/generators/wagtail-page/actions/snippet.ts +19 -0
  48. package/generators/wagtail-page/index.ts +63 -0
  49. package/generators/wagtail-page/templates/django_model.py.hbs +18 -0
  50. package/generators/wagtail-page/templates/orderable_model.py.hbs +21 -0
  51. package/generators/wagtail-page/templates/page_pair_model.py.hbs +62 -0
  52. package/generators/wagtail-page/templates/page_template.html.hbs +14 -0
  53. package/generators/wagtail-page/templates/snippet_model.py.hbs +24 -0
  54. package/package.json +47 -0
  55. package/plopfile.ts +26 -0
  56. package/tsconfig.json +29 -0
@@ -0,0 +1,52 @@
1
+ export const WAGTAIL_PAGE_TYPES = [
2
+ { name: 'Paire de Pages Standard (Index + Detail) : Pour une structure hiérarchique (ex: Liste de blog -> Article de blog).', value: 'page_pair' },
3
+ { name: 'Modèle Orderable : Pour gérer des listes ordonnées d\'éléments dans une page (ex: Carrousel, Galerie).', value: 'orderable' },
4
+ { name: 'Snippet Wagtail : Pour du contenu réutilisable hors arborescence (ex: Catégories, Auteurs, Publicités).', value: 'snippet' },
5
+ { name: 'Modèle Django Standard : Une table de base de données classique sans fonctionnalités CMS Wagtail.', value: 'model' },
6
+ ];
7
+
8
+ export const MAIN_COMPONENT_TYPES = [
9
+ { name: 'Modifier une classe existante (AI Enhancer)', value: 'ai_enhancer' },
10
+ ...WAGTAIL_PAGE_TYPES,
11
+ { name: 'Block StreamField', value: 'block' },
12
+ { name: 'Vue Django (FBV/CBV)', value: 'view' },
13
+ { name: 'Formulaire Django', value: 'form' },
14
+ { name: 'Consumer Django Channel', value: 'channel' },
15
+ { name: 'Extension Admin Wagtail', value: 'admin_ext' },
16
+ ];
17
+
18
+ export const VIEW_TYPES = [
19
+ { name: 'Vue basée sur une fonction (FBV) : Idéale pour une logique sur-mesure simple sans l\'abstraction des classes.', value: 'fbv' },
20
+ { name: 'TemplateView (CBV) : Affiche une page HTML statique ou avec des données contextuelles simples.', value: 'TemplateView' },
21
+ { name: 'ListView (CBV) : Génère une page listant plusieurs objets d\'un modèle (ex: catalogue, blog).', value: 'ListView' },
22
+ { name: 'DetailView (CBV) : Affiche les informations détaillées d\'un objet unique (ex: fiche produit, article).', value: 'DetailView' },
23
+ { name: 'CreateView (CBV) : Génère un formulaire pour ajouter une nouvelle entrée dans la base de données.', value: 'CreateView' },
24
+ { name: 'UpdateView (CBV) : Génère un formulaire pré-rempli pour modifier une entrée existante.', value: 'UpdateView' },
25
+ { name: 'DeleteView (CBV) : Affiche une page de confirmation avant la suppression définitive d\'un objet.', value: 'DeleteView' },
26
+ ];
27
+
28
+ export const FORM_TYPES = [
29
+ { name: 'Formulaire Standard (forms.Form) : Pour traiter des données génériques (ex: recherche, contact).', value: 'form' },
30
+ { name: 'Formulaire de Modèle (forms.ModelForm) : Crée automatiquement des champs basés sur un modèle existant.', value: 'model_form' },
31
+ ];
32
+
33
+ export const ADMIN_EXT_TYPES = [
34
+ { name: 'Vue d\'Administration (Page Personnalisée) : Ajoute une page complète dans le menu admin Wagtail.', value: 'view' },
35
+ { name: 'Composant Template (Panneau UI) : Crée un élément d\'interface réutilisable (Panel) pour les formulaires d\'édition.', value: 'component' },
36
+ ];
37
+
38
+ export const CHANNEL_TYPES = [
39
+ { name: 'Consumer Websocket Asynchrone : Pour haute performance (chat, notifications) avec asyncio.', value: 'async' },
40
+ { name: 'Consumer Websocket Synchrone : Pour logique bloquante standard ou code hérité.', value: 'sync' },
41
+ ];
42
+
43
+ export const AI_PROVIDER_TYPES = ['Qwen', 'Gemini'];
44
+
45
+ export const AI_TEMP_TYPES = [
46
+ { name: 'Strict & Précis (0.1)', value: 0.1 },
47
+ { name: 'Équilibré (0.5)', value: 0.5 },
48
+ { name: 'Créatif (0.9)', value: 0.9 }
49
+ ];
50
+
51
+ export const EXIT_VALUE = '__EXIT__';
52
+ export const BACK_VALUE = '__BACK__';
@@ -0,0 +1,67 @@
1
+ import { PlopGeneratorConfig } from 'plop';
2
+
3
+ /**
4
+ * Generator configuration for creating a new Django Application.
5
+ * Sets up the standard directory structure, models, views, urls, and admin files.
6
+ *
7
+ * @type {PlopGeneratorConfig}
8
+ */
9
+ export const djangoAppGenerator: PlopGeneratorConfig = {
10
+ description: 'Créer une nouvelle application Django avec la structure standard',
11
+ prompts: [
12
+ {
13
+ type: 'input',
14
+ name: 'name',
15
+ message: 'Quel est le nom de l\'application Django ? (Cela créera un dossier avec models, views, urls pour une fonctionnalité)',
16
+ validate: (value) => {
17
+ if (/.+/.test(value)) {
18
+ return true;
19
+ }
20
+ return 'Le nom est requis';
21
+ },
22
+ },
23
+ ],
24
+ actions: [
25
+ {
26
+ type: 'add',
27
+ path: 'app/__init__.py',
28
+ templateFile: 'generators/django-app/templates/init.py.hbs',
29
+ skipIfExists: true,
30
+ },
31
+ {
32
+ type: 'add',
33
+ path: 'app/{{name}}/__init__.py',
34
+ templateFile: 'generators/django-app/templates/init.py.hbs',
35
+ },
36
+ {
37
+ type: 'add',
38
+ path: 'app/{{name}}/apps.py',
39
+ templateFile: 'generators/django-app/templates/apps.py.hbs',
40
+ },
41
+ {
42
+ type: 'add',
43
+ path: 'app/{{name}}/models/__init__.py',
44
+ templateFile: 'generators/django-app/templates/models_init.py.hbs',
45
+ },
46
+ {
47
+ type: 'add',
48
+ path: 'app/{{name}}/tests/__init__.py',
49
+ templateFile: 'generators/django-app/templates/init.py.hbs',
50
+ },
51
+ {
52
+ type: 'add',
53
+ path: 'app/{{name}}/views.py',
54
+ templateFile: 'generators/django-app/templates/views.py.hbs',
55
+ },
56
+ {
57
+ type: 'add',
58
+ path: 'app/{{name}}/urls.py',
59
+ templateFile: 'generators/django-app/templates/urls.py.hbs',
60
+ },
61
+ {
62
+ type: 'add',
63
+ path: 'app/{{name}}/admin.py',
64
+ templateFile: 'generators/django-app/templates/admin.py.hbs',
65
+ },
66
+ ],
67
+ };
@@ -0,0 +1,6 @@
1
+ from django.contrib import admin
2
+ from .models import *
3
+
4
+ # Register your models here.
5
+ # <!-- CLASS = {{pascalCase name}}Admin START AI_GENERATED_MODEL -->
6
+ # <!-- CLASS = {{pascalCase name}}Admin END AI_GENERATED_MODEL -->
@@ -0,0 +1,9 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class {{pascalCase name}}Config(AppConfig):
5
+ default_auto_field = 'django.db.models.BigAutoField'
6
+ name = 'app.{{name}}'
7
+
8
+ # <!-- CLASS = {{pascalCase name}}Config START AI_GENERATED_CONFIG -->
9
+ # <!-- CLASS = {{pascalCase name}}Config END AI_GENERATED_CONFIG -->
File without changes
@@ -0,0 +1,2 @@
1
+ from .pages import *
2
+ from .snippets import *
@@ -0,0 +1,8 @@
1
+ from django.urls import path
2
+ from . import views
3
+
4
+ urlpatterns = [
5
+ # <!-- CLASS = {{pascalCase name}}Urls START AI_GENERATED_URL -->
6
+ # <!-- CLASS = {{pascalCase name}}Urls END AI_GENERATED_URL -->
7
+ # path('', views.index, name='index'),
8
+ ]
@@ -0,0 +1,5 @@
1
+ from django.shortcuts import render
2
+
3
+ # Create your views here.
4
+ # <!-- CLASS = {{pascalCase name}}Views START AI_GENERATED_VIEW -->
5
+ # <!-- CLASS = {{pascalCase name}}Views END AI_GENERATED_VIEW -->
@@ -0,0 +1,78 @@
1
+ import { PlopGeneratorConfig } from 'plop';
2
+ import { ensureSuffix, createAppendActions } from '../utils/plop-actions';
3
+ import { CHANNEL_TYPES } from '../constants';
4
+ import { searchChoices } from '../utils/search';
5
+
6
+ /**
7
+ * Generator configuration for creating a Django Channel Consumer and Routing.
8
+ * Supports Async and Sync Websocket Consumers.
9
+ * Automatically configures routing and adds tests.
10
+ *
11
+ * @type {PlopGeneratorConfig}
12
+ */
13
+ export const djangoChannelGenerator: PlopGeneratorConfig = {
14
+ description: 'Créer un Consumer Django Channel et son routing',
15
+ prompts: [
16
+ {
17
+ type: 'autocomplete',
18
+ name: 'type',
19
+ message: 'Quel type de consumer voulez-vous créer ?',
20
+ source: (answers, input) => searchChoices(input, CHANNEL_TYPES),
21
+ },
22
+ {
23
+ type: 'input',
24
+ name: 'name',
25
+ message: 'Nom du Consumer (ex: ChatConsumer) :',
26
+ validate: (value) => {
27
+ return true;
28
+ },
29
+ },
30
+ {
31
+ type: 'input',
32
+ name: 'route',
33
+ message: 'Préfixe de la route URL (ex: chat -> ws/chat/room_name/) :',
34
+ default: 'chat',
35
+ },
36
+ {
37
+ type: 'input',
38
+ name: 'app',
39
+ message: 'Nom de l\'application cible',
40
+ },
41
+ ],
42
+ actions: (data) => {
43
+ const actions = [];
44
+ const appPath = `app/${data.app}`;
45
+ const isAsync = data.type === 'async';
46
+ data.isAsync = isAsync;
47
+
48
+ // Ensure suffix
49
+ data.name = ensureSuffix(data.name, 'Consumer');
50
+
51
+ // Ensure consumers.py exists and Append Consumer
52
+ actions.push(...createAppendActions({
53
+ path: `${appPath}/consumers.py`,
54
+ templateFile: 'generators/django-channel/templates/consumer.py.hbs',
55
+ dumbTemplate: 'import json\nfrom channels.generic.websocket import AsyncWebsocketConsumer, WebsocketConsumer\n\n',
56
+ dumbData: {},
57
+ }));
58
+
59
+ // Ensure routing.py exists
60
+ actions.push({
61
+ type: 'add',
62
+ path: `${appPath}/routing.py`,
63
+ templateFile: 'generators/django-channel/templates/routing.py.hbs',
64
+ skipIfExists: true,
65
+ });
66
+
67
+ // Append to websocket_urlpatterns in routing.py
68
+ // We look for the closing bracket of websocket_urlpatterns list
69
+ actions.push({
70
+ type: 'modify',
71
+ path: `${appPath}/routing.py`,
72
+ pattern: /(websocket_urlpatterns\s*=\s*\[)/,
73
+ template: '$1\n re_path(r\'ws/{{ route }}/(?P<room_name>\\w+)/$\', consumers.{{ name }}.as_asgi()),',
74
+ });
75
+
76
+ return actions;
77
+ },
78
+ };
@@ -0,0 +1,47 @@
1
+ import json
2
+ from channels.generic.websocket import {{#if isAsync}}AsyncWebsocketConsumer{{else}}WebsocketConsumer{{/if}}
3
+
4
+ class {{ name }}({{#if isAsync}}AsyncWebsocketConsumer{{else}}WebsocketConsumer{{/if}}):
5
+ # <!-- CLASS = {{ name }} START AI_GENERATED_CONSUMER -->
6
+ # <!-- CLASS = {{ name }} END AI_GENERATED_CONSUMER -->
7
+ {{#if isAsync}}async {{/if}}def connect(self):
8
+ self.room_name = self.scope['url_route']['kwargs']['room_name']
9
+ self.room_group_name = 'chat_%s' % self.room_name
10
+
11
+ # Join room group
12
+ {{#if isAsync}}await {{/if}}self.channel_layer.group_add(
13
+ self.room_group_name,
14
+ self.channel_name
15
+ )
16
+
17
+ {{#if isAsync}}await {{/if}}self.accept()
18
+
19
+ {{#if isAsync}}async {{/if}}def disconnect(self, close_code):
20
+ # Leave room group
21
+ {{#if isAsync}}await {{/if}}self.channel_layer.group_discard(
22
+ self.room_group_name,
23
+ self.channel_name
24
+ )
25
+
26
+ # Receive message from WebSocket
27
+ {{#if isAsync}}async {{/if}}def receive(self, text_data):
28
+ text_data_json = json.loads(text_data)
29
+ message = text_data_json['message']
30
+
31
+ # Send message to room group
32
+ {{#if isAsync}}await {{/if}}self.channel_layer.group_send(
33
+ self.room_group_name,
34
+ {
35
+ 'type': 'chat_message',
36
+ 'message': message
37
+ }
38
+ )
39
+
40
+ # Receive message from room group
41
+ {{#if isAsync}}async {{/if}}def chat_message(self, event):
42
+ message = event['message']
43
+
44
+ # Send message to WebSocket
45
+ {{#if isAsync}}await {{/if}}self.send(text_data=json.dumps({
46
+ 'message': message
47
+ }))
@@ -0,0 +1,8 @@
1
+ from django.urls import re_path
2
+ from . import consumers
3
+
4
+ websocket_urlpatterns = [
5
+ # <!-- CLASS = {{pascalCase name}}Routing START AI_GENERATED_ROUTING -->
6
+ # <!-- CLASS = {{pascalCase name}}Routing END AI_GENERATED_ROUTING -->
7
+ # re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
8
+ ]
@@ -0,0 +1,62 @@
1
+ import { PlopGeneratorConfig } from 'plop';
2
+ import { ensureSuffix, createAppendActions } from '../utils/plop-actions';
3
+ import { FORM_TYPES } from '../constants';
4
+ import { searchChoices } from '../utils/search';
5
+
6
+ /**
7
+ * Generator configuration for creating a Django Form or ModelForm.
8
+ * Handles standard forms and model-bound forms.
9
+ *
10
+ * @type {PlopGeneratorConfig}
11
+ */
12
+ export const djangoFormGenerator: PlopGeneratorConfig = {
13
+ description: 'Créer un Formulaire Django ou un ModelForm',
14
+ prompts: [
15
+ {
16
+ type: 'autocomplete',
17
+ name: 'type',
18
+ message: 'Quel type de formulaire voulez-vous créer ?',
19
+ source: (answers, input) => searchChoices(input, FORM_TYPES),
20
+ },
21
+ {
22
+ type: 'input',
23
+ name: 'name',
24
+ message: 'Nom du Formulaire (ex: ContactForm, UserProfileForm) :',
25
+ validate: (value) => {
26
+ return true;
27
+ },
28
+ },
29
+ {
30
+ type: 'input',
31
+ name: 'model',
32
+ message: 'Nom du Modèle associé (ex: UserProfile) :',
33
+ when: (answers) => answers.type === 'model_form',
34
+ },
35
+ {
36
+ type: 'input',
37
+ name: 'app',
38
+ message: 'Nom de l\'application cible',
39
+ },
40
+ ],
41
+ actions: (data) => {
42
+ const actions = [];
43
+ const appPath = `app/${data.app}`;
44
+ const isModelForm = data.type === 'model_form';
45
+
46
+ // Ensure suffix
47
+ data.name = ensureSuffix(data.name, 'Form');
48
+
49
+ const templateFile = isModelForm
50
+ ? 'generators/django-form/templates/model_form.py.hbs'
51
+ : 'generators/django-form/templates/form.py.hbs';
52
+
53
+ actions.push(...createAppendActions({
54
+ path: `${appPath}/forms.py`,
55
+ templateFile,
56
+ dumbTemplateFile: 'generators/django-form/templates/forms_file.py.hbs',
57
+ dumbData: data,
58
+ }));
59
+
60
+ return actions;
61
+ },
62
+ };
@@ -0,0 +1,12 @@
1
+
2
+ class {{ name }}(forms.Form):
3
+ # TODO: Define form fields here
4
+ # name = forms.CharField(max_length=100)
5
+ # message = forms.CharField(widget=forms.Textarea)
6
+
7
+ # <!-- CLASS = {{ name }} START AI_GENERATED_FORM -->
8
+ # <!-- CLASS = {{ name }} END AI_GENERATED_FORM -->
9
+ def clean(self):
10
+ cleaned_data = super().clean()
11
+ # TODO: Add custom validation logic
12
+ return cleaned_data
@@ -0,0 +1,6 @@
1
+ from django import forms
2
+
3
+ # Create your forms here.
4
+ # <!-- CLASS = {{pascalCase name}}Forms START AI_GENERATED_FORMS -->
5
+
6
+ # <!-- CLASS = {{pascalCase name}}Forms END AI_GENERATED_FORMS -->
@@ -0,0 +1,18 @@
1
+
2
+ {{#if model}}from .models import {{ model }}{{/if}}
3
+
4
+ class {{ name }}(forms.ModelForm):
5
+ class Meta:
6
+
7
+ {{#if model}} model = {{ model }}{{/if}}
8
+ {{#unless model}} # model = YourModel{{/unless}}
9
+ fields = '__all__' # TODO: Specify explicit fields ['field1', 'field2']
10
+ # widgets = {
11
+ # 'field_name': forms.TextInput(attrs={'class': 'form-control'}),
12
+ # }
13
+ # <!-- CLASS = {{ name }} START AI_GENERATED_MODEL_FORM -->
14
+ # <!-- CLASS = {{ name }} END AI_GENERATED_MODEL_FORM -->
15
+ def clean(self):
16
+ cleaned_data = super().clean()
17
+ # TODO: Add custom validation logic
18
+ return cleaned_data
@@ -0,0 +1,95 @@
1
+ import { PlopGeneratorConfig } from 'plop';
2
+ import { createAppendActions, ensureSuffix } from '../utils/plop-actions';
3
+ import { VIEW_TYPES } from '../constants';
4
+ import { searchChoices } from '../utils/search';
5
+
6
+ /**
7
+ * Generator configuration for creating Django Views.
8
+ * Supports Function-Based Views (FBV) and Class-Based Views (CBV) including:
9
+ * - TemplateView, ListView, DetailView, CreateView, UpdateView, DeleteView.
10
+ * Handles automatic suffixing logic for naming conventions.
11
+ *
12
+ * @type {PlopGeneratorConfig}
13
+ */
14
+ export const djangoViewGenerator: PlopGeneratorConfig = {
15
+ description: 'Créer une Vue Django (FBV ou CBV)',
16
+ prompts: [
17
+ {
18
+ type: 'autocomplete',
19
+ name: 'type',
20
+ message: 'Quel type de vue voulez-vous créer ?',
21
+ source: (answers, input) => searchChoices(input, VIEW_TYPES),
22
+ },
23
+ {
24
+ type: 'input',
25
+ name: 'name',
26
+ message: 'Nom de la Vue (ex: UserProfile, ProductList) :',
27
+ validate: (value, answers) => {
28
+ return true;
29
+ }
30
+ },
31
+ {
32
+ type: 'input',
33
+ name: 'app',
34
+ message: 'Nom de l\'application cible',
35
+ },
36
+ ],
37
+ actions: (data) => {
38
+ const actions = [];
39
+ const appPath = `app/${data.app}`;
40
+ const isCBV = data.type !== 'fbv';
41
+
42
+ // Helper to ensure correct suffix for CBV
43
+ if (isCBV) {
44
+ let suffix = data.type; // e.g. ListView, DetailView
45
+ if (data.type === 'TemplateView') {
46
+ suffix = 'View';
47
+ }
48
+
49
+ if (!data.name.endsWith(suffix)) {
50
+ // Check if it ends with the "Type" part (e.g. "List" for "ListView")
51
+ const typePrefix = suffix.replace(/View$/, ''); // "List"
52
+
53
+ if (typePrefix && data.name.endsWith(typePrefix)) {
54
+ // e.g. ProductList + ListView -> ProductListView
55
+ data.name = `${data.name}View`;
56
+ } else if (data.name.endsWith('View')) {
57
+ // e.g. ProductView + ListView -> ProductListView
58
+ data.name = data.name.replace(/View$/, suffix);
59
+ } else {
60
+ // e.g. Product + ListView -> ProductListView
61
+ data.name = `${data.name}${suffix}`;
62
+ }
63
+ }
64
+ } else {
65
+ // FBV
66
+ data.name = ensureSuffix(data.name, '_view');
67
+ }
68
+
69
+ // Helper for template name
70
+ data.templateName = data.name.replace(/View$/, '');
71
+
72
+ // Append to views.py
73
+ const templateFile = isCBV
74
+ ? 'generators/django-view/templates/view_cbv.py.hbs'
75
+ : 'generators/django-view/templates/view_fbv.py.hbs';
76
+
77
+ actions.push(...createAppendActions({
78
+ path: `${appPath}/views.py`,
79
+ templateFile,
80
+ dumbTemplate: 'from django.shortcuts import render\n\n',
81
+ dumbData: {},
82
+ appendData: isCBV ? { viewType: data.type } : undefined
83
+ }));
84
+
85
+ // Create Template
86
+ actions.push({
87
+ type: 'add',
88
+ path: `${appPath}/templates/${data.app}/{{snakeCase templateName}}.html`, // Clean filename
89
+ templateFile: 'generators/django-view/templates/view_template.html.hbs',
90
+ skipIfExists: true,
91
+ });
92
+
93
+ return actions;
94
+ },
95
+ };
@@ -0,0 +1,11 @@
1
+
2
+ class {{pascalCase name}}({{viewType}}):
3
+ """
4
+ Class-based view for {{name}}.
5
+ """
6
+ template_name = '{{app}}/{{snakeCase name}}.html'
7
+
8
+ # <!-- CLASS = {{pascalCase name}} START AI_GENERATED_VIEW -->
9
+ # <!-- CLASS = {{pascalCase name}} END AI_GENERATED_VIEW -->
10
+ # context_object_name = '{{snakeCase name}}'
11
+ # model = MyModel
@@ -0,0 +1,7 @@
1
+
2
+ def {{snakeCase name}}(request):
3
+ """
4
+ Function-based view for {{name}}.
5
+ """
6
+ context = {}
7
+ return render(request, '{{app}}/{{snakeCase name}}.html', context)
@@ -0,0 +1,8 @@
1
+ {% extends "base.html" %}
2
+
3
+ <!-- CLASS = {{pascalCase name}} START AI_GENERATED_VIEW_TEMPLATE -->
4
+ {% block content %}
5
+ <h1>{{titleCase name}}</h1>
6
+ <p>Welcome to the {{name}} view.</p>
7
+ {% endblock %}
8
+ <!-- CLASS = {{pascalCase name}} END AI_GENERATED_VIEW_TEMPLATE -->
@@ -0,0 +1,36 @@
1
+ import { PlopGeneratorConfig } from 'plop';
2
+ import { spawn } from 'child_process';
3
+
4
+ export const docsGenerator: PlopGeneratorConfig = {
5
+ description: 'Ouvrir le serveur de documentation',
6
+ prompts: [],
7
+ actions: [
8
+ function runDocs() {
9
+ console.log('🚀 Démarrage du serveur de documentation...');
10
+ console.log('🌐 Accédez à http://localhost:3333');
11
+
12
+ const isWindows = process.platform === 'win32';
13
+ const cmd = 'bun';
14
+ const args = ['run', '--port', '3333', 'docs-site/index.html'];
15
+
16
+ const child = spawn(cmd, args, {
17
+ stdio: 'inherit',
18
+ shell: isWindows
19
+ });
20
+
21
+ // Return a promise that resolves when the child process exits
22
+ return new Promise((resolve, reject) => {
23
+ child.on('close', (code) => {
24
+ if (code === 0) {
25
+ resolve('Serveur arrêté');
26
+ } else {
27
+ reject(`Serveur arrêté avec le code ${code}`);
28
+ }
29
+ });
30
+ child.on('error', (err) => {
31
+ reject(err);
32
+ });
33
+ });
34
+ }
35
+ ]
36
+ };
@@ -0,0 +1,84 @@
1
+
2
+ import { PlopGeneratorConfig } from 'plop';
3
+ import keywordConfig from '../ai-enhancer/keywords.json';
4
+ import { EXIT_VALUE } from '../constants';
5
+
6
+ const getHelpItems = (input: string | undefined): any[] => {
7
+ const items: any[] = [];
8
+
9
+ if (input) {
10
+ const inputLower = input.toLowerCase();
11
+ let lastMatchIndex = -1;
12
+ let matchedInstruction = null;
13
+
14
+ const keywords = Array.isArray(keywordConfig) ? keywordConfig : (keywordConfig as any).keywords || [];
15
+
16
+ for (const entry of keywords) {
17
+ if (entry.terms && Array.isArray(entry.terms)) {
18
+ for (const term of entry.terms) {
19
+ const index = inputLower.lastIndexOf(term.toLowerCase());
20
+ if (index !== -1 && index >= lastMatchIndex) {
21
+ lastMatchIndex = index;
22
+ matchedInstruction = entry.instruction;
23
+ }
24
+ }
25
+ }
26
+ }
27
+
28
+ if (matchedInstruction) {
29
+ // Split instruction into lines of max 80 chars for better visibility
30
+ const words = matchedInstruction.split(' ');
31
+ let currentLine = '💡 ';
32
+
33
+ for (const word of words) {
34
+ if ((currentLine + word).length > 80) {
35
+ items.push({ name: currentLine, value: 'INFO', disabled: true });
36
+ currentLine = ' ' + word + ' ';
37
+ } else {
38
+ currentLine += word + ' ';
39
+ }
40
+ }
41
+ if (currentLine.trim()) {
42
+ items.push({ name: currentLine, value: 'INFO', disabled: true });
43
+ }
44
+ items.push({ name: '────────────────────────────────────────', value: 'SEP', disabled: true });
45
+ }
46
+ }
47
+ return items;
48
+ };
49
+
50
+ export const helpGenerator: PlopGeneratorConfig = {
51
+ description: 'Search for AI instructions and keywords',
52
+ prompts: [
53
+ {
54
+ type: 'autocomplete',
55
+ name: 'query',
56
+ message: 'Type a keyword to see associated instructions (e.g. "model", "field"):',
57
+ suggestOnly: true,
58
+ source: async (answers, input) => {
59
+ const choices: any[] = [];
60
+
61
+ const helpItems = getHelpItems(input);
62
+ if (helpItems.length > 0) {
63
+ choices.push(...helpItems);
64
+ }
65
+
66
+ if (!input) {
67
+ choices.push({ name: 'Type to search...', value: 'INFO', disabled: true });
68
+ choices.push({ name: 'Exit', value: EXIT_VALUE });
69
+ return choices;
70
+ }
71
+
72
+ choices.push({ name: 'Exit', value: EXIT_VALUE });
73
+ return choices;
74
+ },
75
+ validate: (value) => {
76
+ if (value === EXIT_VALUE || value?.toLowerCase() === 'exit') {
77
+ process.exit(0);
78
+ }
79
+ return true;
80
+ }
81
+ }
82
+ ],
83
+ actions: []
84
+ };