kod8-cli 0.2.0__tar.gz
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.
- kod8_cli-0.2.0/MANIFEST.in +3 -0
- kod8_cli-0.2.0/PKG-INFO +68 -0
- kod8_cli-0.2.0/README.md +51 -0
- kod8_cli-0.2.0/kod8/__init__.py +0 -0
- kod8_cli-0.2.0/kod8/exercises.json +522 -0
- kod8_cli-0.2.0/kod8/main.py +180 -0
- kod8_cli-0.2.0/kod8_cli.egg-info/PKG-INFO +68 -0
- kod8_cli-0.2.0/kod8_cli.egg-info/SOURCES.txt +13 -0
- kod8_cli-0.2.0/kod8_cli.egg-info/dependency_links.txt +1 -0
- kod8_cli-0.2.0/kod8_cli.egg-info/entry_points.txt +2 -0
- kod8_cli-0.2.0/kod8_cli.egg-info/requires.txt +3 -0
- kod8_cli-0.2.0/kod8_cli.egg-info/top_level.txt +1 -0
- kod8_cli-0.2.0/pyproject.toml +3 -0
- kod8_cli-0.2.0/setup.cfg +4 -0
- kod8_cli-0.2.0/setup.py +24 -0
kod8_cli-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kod8-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: KOD8 Command Line Interface for local testing
|
|
5
|
+
Home-page: https://github.com/Rodrigue-k/kod8
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: requests
|
|
9
|
+
Requires-Dist: rich
|
|
10
|
+
Requires-Dist: keyring
|
|
11
|
+
Dynamic: description
|
|
12
|
+
Dynamic: description-content-type
|
|
13
|
+
Dynamic: home-page
|
|
14
|
+
Dynamic: requires-dist
|
|
15
|
+
Dynamic: requires-python
|
|
16
|
+
Dynamic: summary
|
|
17
|
+
|
|
18
|
+
# KOD8 CLI
|
|
19
|
+
|
|
20
|
+
Tester des exercices à fichier unique en local, puis envoyer leur code au correcteur KOD8. Les tests locaux ne valident pas la progression.
|
|
21
|
+
|
|
22
|
+
## Installation locale
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cd cli
|
|
26
|
+
pip install -e .
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Se connecter sur KOD8, ouvrir `/formations/python/soumissions`, créer un jeton personnel et le fournir dans `KOD8_TOKEN`. Ne jamais le committer. `KOD8_API_URL` vaut `https://kod8.live/api/cli` ; en développement : `http://localhost:5173/api/cli`.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Utilisation quotidienne
|
|
34
|
+
|
|
35
|
+
### 1. Tester son code en local (sans impacter sa note)
|
|
36
|
+
|
|
37
|
+
Créez le fichier imposé par l’énoncé (par exemple `contact.py`). L’installation et la connexion se font une seule fois dans le guide d’installation, avec `kod8 login`. La CLI conserve le jeton dans le coffre système ; Codespaces peut utiliser le secret `KOD8_TOKEN`. Vérifier la version avec `kod8 --version`.
|
|
38
|
+
|
|
39
|
+
Utilisez directement le nom de l’exercice, sans extension :
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
kod8 test contact
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
*Ou si vous êtes directement dans le sous-dossier de l'exercice :*
|
|
46
|
+
```bash
|
|
47
|
+
kod8 test
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
L’outil exécute les tests publics disponibles sur votre ordinateur. Une grille absente ne permet pas de conclure à une réussite.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
### 2. Valider et enregistrer officiellement sa progression
|
|
55
|
+
|
|
56
|
+
Quand tout est vert, vous soumettez votre solution en une seule commande :
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
kod8 submit contact
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
La commande relance les tests publics. Après réussite, elle crée un commit horodaté pour le seul fichier de l’exercice, pousse le commit, puis soumet le code pour votre compte. Seuls des tests serveur réussis valident l’exercice. Un fichier inchangé ne crée pas de commit supplémentaire. `--retry` crée une nouvelle tentative après une erreur technique ; `--no-push` est réservé aux intégrations techniques.
|
|
63
|
+
|
|
64
|
+
Codes de sortie : 0 réussite, 1 échec, 2 revue nécessaire, 3 erreur technique, 4 correction en cours. Les projets et exercices à plusieurs fichiers, à fichiers produits, d’environnement ou d’API se déposent sur leur page avec le dépôt et les preuves ; ils ne sont pas validés par un simple main.py.
|
|
65
|
+
|
|
66
|
+
Pour GitHub Actions, copier `examples/github-actions.yml` dans `.github/workflows/kod8.yml` et `examples/ci_submit.py` dans `.github/scripts/ci_submit.py` du dépôt apprenant. Ajouter le secret `KOD8_TOKEN` et la variable `KOD8_API_URL` si nécessaire. Le helper soumet les scripts nommés reconnus par le catalogue et conserve la compatibilité avec `python/m*/ex*/main.py` ; il n’exécute pas le code apprenant dans la CI. Ne pas appliquer ce workflow aux activités qui demandent un livrable en revue.
|
|
67
|
+
|
|
68
|
+
Le catalogue canonique est `lib/course-content/exercise-contracts.json`. Sa copie embarquée dans la CLI est `kod8/exercises.json`. Régénérer avec `python scripts/build-exercise-contracts.py`, puis produire l’archive téléchargeable avec `python scripts/package-cli.py`. Le paquet n’est pas confirmé publié sur PyPI.
|
kod8_cli-0.2.0/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# KOD8 CLI
|
|
2
|
+
|
|
3
|
+
Tester des exercices à fichier unique en local, puis envoyer leur code au correcteur KOD8. Les tests locaux ne valident pas la progression.
|
|
4
|
+
|
|
5
|
+
## Installation locale
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd cli
|
|
9
|
+
pip install -e .
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Se connecter sur KOD8, ouvrir `/formations/python/soumissions`, créer un jeton personnel et le fournir dans `KOD8_TOKEN`. Ne jamais le committer. `KOD8_API_URL` vaut `https://kod8.live/api/cli` ; en développement : `http://localhost:5173/api/cli`.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Utilisation quotidienne
|
|
17
|
+
|
|
18
|
+
### 1. Tester son code en local (sans impacter sa note)
|
|
19
|
+
|
|
20
|
+
Créez le fichier imposé par l’énoncé (par exemple `contact.py`). L’installation et la connexion se font une seule fois dans le guide d’installation, avec `kod8 login`. La CLI conserve le jeton dans le coffre système ; Codespaces peut utiliser le secret `KOD8_TOKEN`. Vérifier la version avec `kod8 --version`.
|
|
21
|
+
|
|
22
|
+
Utilisez directement le nom de l’exercice, sans extension :
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
kod8 test contact
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
*Ou si vous êtes directement dans le sous-dossier de l'exercice :*
|
|
29
|
+
```bash
|
|
30
|
+
kod8 test
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
L’outil exécute les tests publics disponibles sur votre ordinateur. Une grille absente ne permet pas de conclure à une réussite.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
### 2. Valider et enregistrer officiellement sa progression
|
|
38
|
+
|
|
39
|
+
Quand tout est vert, vous soumettez votre solution en une seule commande :
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
kod8 submit contact
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
La commande relance les tests publics. Après réussite, elle crée un commit horodaté pour le seul fichier de l’exercice, pousse le commit, puis soumet le code pour votre compte. Seuls des tests serveur réussis valident l’exercice. Un fichier inchangé ne crée pas de commit supplémentaire. `--retry` crée une nouvelle tentative après une erreur technique ; `--no-push` est réservé aux intégrations techniques.
|
|
46
|
+
|
|
47
|
+
Codes de sortie : 0 réussite, 1 échec, 2 revue nécessaire, 3 erreur technique, 4 correction en cours. Les projets et exercices à plusieurs fichiers, à fichiers produits, d’environnement ou d’API se déposent sur leur page avec le dépôt et les preuves ; ils ne sont pas validés par un simple main.py.
|
|
48
|
+
|
|
49
|
+
Pour GitHub Actions, copier `examples/github-actions.yml` dans `.github/workflows/kod8.yml` et `examples/ci_submit.py` dans `.github/scripts/ci_submit.py` du dépôt apprenant. Ajouter le secret `KOD8_TOKEN` et la variable `KOD8_API_URL` si nécessaire. Le helper soumet les scripts nommés reconnus par le catalogue et conserve la compatibilité avec `python/m*/ex*/main.py` ; il n’exécute pas le code apprenant dans la CI. Ne pas appliquer ce workflow aux activités qui demandent un livrable en revue.
|
|
50
|
+
|
|
51
|
+
Le catalogue canonique est `lib/course-content/exercise-contracts.json`. Sa copie embarquée dans la CLI est `kod8/exercises.json`. Régénérer avec `python scripts/build-exercise-contracts.py`, puis produire l’archive téléchargeable avec `python scripts/package-cli.py`. Le paquet n’est pas confirmé publié sur PyPI.
|
|
File without changes
|
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "contact",
|
|
4
|
+
"filename": "contact.py",
|
|
5
|
+
"moduleId": "01",
|
|
6
|
+
"stepId": "exercice-01",
|
|
7
|
+
"slug": "python/m1/ex1",
|
|
8
|
+
"mode": "code"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "fiche",
|
|
12
|
+
"filename": "fiche.py",
|
|
13
|
+
"moduleId": "01",
|
|
14
|
+
"stepId": "exercice-02",
|
|
15
|
+
"slug": "python/m1/ex2",
|
|
16
|
+
"mode": "code"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "budget",
|
|
20
|
+
"filename": "budget.py",
|
|
21
|
+
"moduleId": "01",
|
|
22
|
+
"stepId": "exercice-03",
|
|
23
|
+
"slug": "python/m1/ex3",
|
|
24
|
+
"mode": "code"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "facture",
|
|
28
|
+
"filename": "facture.py",
|
|
29
|
+
"moduleId": "01",
|
|
30
|
+
"stepId": "exercice-04",
|
|
31
|
+
"slug": "python/m1/ex4",
|
|
32
|
+
"mode": "code"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "temps",
|
|
36
|
+
"filename": "temps.py",
|
|
37
|
+
"moduleId": "01",
|
|
38
|
+
"stepId": "exercice-05",
|
|
39
|
+
"slug": "python/m1/ex5",
|
|
40
|
+
"mode": "code"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "garde",
|
|
44
|
+
"filename": "garde.py",
|
|
45
|
+
"moduleId": "02",
|
|
46
|
+
"stepId": "exercice-01",
|
|
47
|
+
"slug": "python/m2/ex1",
|
|
48
|
+
"mode": "code"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "parite",
|
|
52
|
+
"filename": "parite.py",
|
|
53
|
+
"moduleId": "02",
|
|
54
|
+
"stepId": "exercice-02",
|
|
55
|
+
"slug": "python/m2/ex2",
|
|
56
|
+
"mode": "code"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "retrait",
|
|
60
|
+
"filename": "retrait.py",
|
|
61
|
+
"moduleId": "02",
|
|
62
|
+
"stepId": "exercice-03",
|
|
63
|
+
"slug": "python/m2/ex3",
|
|
64
|
+
"mode": "code"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "cinema",
|
|
68
|
+
"filename": "cinema.py",
|
|
69
|
+
"moduleId": "02",
|
|
70
|
+
"stepId": "exercice-04",
|
|
71
|
+
"slug": "python/m2/ex4",
|
|
72
|
+
"mode": "code"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "bissextile",
|
|
76
|
+
"filename": "bissextile.py",
|
|
77
|
+
"moduleId": "02",
|
|
78
|
+
"stepId": "exercice-05",
|
|
79
|
+
"slug": "python/m2/ex5",
|
|
80
|
+
"mode": "code"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "repetition",
|
|
84
|
+
"filename": "repetition.py",
|
|
85
|
+
"moduleId": "03",
|
|
86
|
+
"stepId": "exercice-01",
|
|
87
|
+
"slug": "python/m3/ex1",
|
|
88
|
+
"mode": "code"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "mot_de_passe",
|
|
92
|
+
"filename": "mot_de_passe.py",
|
|
93
|
+
"moduleId": "03",
|
|
94
|
+
"stepId": "exercice-02",
|
|
95
|
+
"slug": "python/m3/ex2",
|
|
96
|
+
"mode": "code"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"name": "menu",
|
|
100
|
+
"filename": "menu.py",
|
|
101
|
+
"moduleId": "03",
|
|
102
|
+
"stepId": "exercice-03",
|
|
103
|
+
"slug": "python/m3/ex3",
|
|
104
|
+
"mode": "code"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"name": "moyenne",
|
|
108
|
+
"filename": "moyenne.py",
|
|
109
|
+
"moduleId": "03",
|
|
110
|
+
"stepId": "exercice-04",
|
|
111
|
+
"slug": "python/m3/ex4",
|
|
112
|
+
"mode": "code"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "pyramide",
|
|
116
|
+
"filename": "pyramide.py",
|
|
117
|
+
"moduleId": "03",
|
|
118
|
+
"stepId": "exercice-05",
|
|
119
|
+
"slug": "python/m3/ex5",
|
|
120
|
+
"mode": "code"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "cri",
|
|
124
|
+
"filename": "cri.py",
|
|
125
|
+
"moduleId": "04",
|
|
126
|
+
"stepId": "exercice-01",
|
|
127
|
+
"slug": "python/m4/ex1",
|
|
128
|
+
"mode": "code"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"name": "voyelles",
|
|
132
|
+
"filename": "voyelles.py",
|
|
133
|
+
"moduleId": "04",
|
|
134
|
+
"stepId": "exercice-02",
|
|
135
|
+
"slug": "python/m4/ex2",
|
|
136
|
+
"mode": "code"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"name": "email",
|
|
140
|
+
"filename": "email.py",
|
|
141
|
+
"moduleId": "04",
|
|
142
|
+
"stepId": "exercice-03",
|
|
143
|
+
"slug": "python/m4/ex3",
|
|
144
|
+
"mode": "code"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"name": "censure",
|
|
148
|
+
"filename": "censure.py",
|
|
149
|
+
"moduleId": "04",
|
|
150
|
+
"stepId": "exercice-04",
|
|
151
|
+
"slug": "python/m4/ex4",
|
|
152
|
+
"mode": "code"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"name": "palindrome",
|
|
156
|
+
"filename": "palindrome.py",
|
|
157
|
+
"moduleId": "04",
|
|
158
|
+
"stepId": "exercice-05",
|
|
159
|
+
"slug": "python/m4/ex5",
|
|
160
|
+
"mode": "code"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"name": "courses",
|
|
164
|
+
"filename": "courses.py",
|
|
165
|
+
"moduleId": "05",
|
|
166
|
+
"stepId": "exercice-01",
|
|
167
|
+
"slug": "python/m5/ex1",
|
|
168
|
+
"mode": "code"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"name": "enumeration",
|
|
172
|
+
"filename": "enumeration.py",
|
|
173
|
+
"moduleId": "05",
|
|
174
|
+
"stepId": "exercice-02",
|
|
175
|
+
"slug": "python/m5/ex2",
|
|
176
|
+
"mode": "code"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"name": "statistiques",
|
|
180
|
+
"filename": "statistiques.py",
|
|
181
|
+
"moduleId": "05",
|
|
182
|
+
"stepId": "exercice-03",
|
|
183
|
+
"slug": "python/m5/ex3",
|
|
184
|
+
"mode": "code"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"name": "filtre",
|
|
188
|
+
"filename": "filtre.py",
|
|
189
|
+
"moduleId": "05",
|
|
190
|
+
"stepId": "exercice-04",
|
|
191
|
+
"slug": "python/m5/ex4",
|
|
192
|
+
"mode": "code"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"name": "tri_bulles",
|
|
196
|
+
"filename": "tri_bulles.py",
|
|
197
|
+
"moduleId": "05",
|
|
198
|
+
"stepId": "exercice-05",
|
|
199
|
+
"slug": "python/m5/ex5",
|
|
200
|
+
"mode": "code"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"name": "profil",
|
|
204
|
+
"filename": "profil.py",
|
|
205
|
+
"moduleId": "06",
|
|
206
|
+
"stepId": "exercice-01",
|
|
207
|
+
"slug": "python/m6/ex1",
|
|
208
|
+
"mode": "code"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"name": "mise_a_jour",
|
|
212
|
+
"filename": "mise_a_jour.py",
|
|
213
|
+
"moduleId": "06",
|
|
214
|
+
"stepId": "exercice-02",
|
|
215
|
+
"slug": "python/m6/ex2",
|
|
216
|
+
"mode": "code"
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"name": "annuaire",
|
|
220
|
+
"filename": "annuaire.py",
|
|
221
|
+
"moduleId": "06",
|
|
222
|
+
"stepId": "exercice-03",
|
|
223
|
+
"slug": "python/m6/ex3",
|
|
224
|
+
"mode": "code"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"name": "panier",
|
|
228
|
+
"filename": "panier.py",
|
|
229
|
+
"moduleId": "06",
|
|
230
|
+
"stepId": "exercice-04",
|
|
231
|
+
"slug": "python/m6/ex4",
|
|
232
|
+
"mode": "code"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"name": "frequences",
|
|
236
|
+
"filename": "frequences.py",
|
|
237
|
+
"moduleId": "06",
|
|
238
|
+
"stepId": "exercice-05",
|
|
239
|
+
"slug": "python/m6/ex5",
|
|
240
|
+
"mode": "code"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"name": "politesse",
|
|
244
|
+
"filename": "politesse.py",
|
|
245
|
+
"moduleId": "07",
|
|
246
|
+
"stepId": "exercice-01",
|
|
247
|
+
"slug": "python/m7/ex1",
|
|
248
|
+
"mode": "code"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"name": "tva",
|
|
252
|
+
"filename": "tva.py",
|
|
253
|
+
"moduleId": "07",
|
|
254
|
+
"stepId": "exercice-02",
|
|
255
|
+
"slug": "python/m7/ex2",
|
|
256
|
+
"mode": "code"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"name": "validateur",
|
|
260
|
+
"filename": "validateur.py",
|
|
261
|
+
"moduleId": "07",
|
|
262
|
+
"stepId": "exercice-03",
|
|
263
|
+
"slug": "python/m7/ex3",
|
|
264
|
+
"mode": "code"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"name": "identifiant",
|
|
268
|
+
"filename": "identifiant.py",
|
|
269
|
+
"moduleId": "07",
|
|
270
|
+
"stepId": "exercice-04",
|
|
271
|
+
"slug": "python/m7/ex4",
|
|
272
|
+
"mode": "code"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"name": "multi_retours",
|
|
276
|
+
"filename": "multi_retours.py",
|
|
277
|
+
"moduleId": "07",
|
|
278
|
+
"stepId": "exercice-05",
|
|
279
|
+
"slug": "python/m7/ex5",
|
|
280
|
+
"mode": "code"
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"name": "module_natif",
|
|
284
|
+
"filename": "module_natif.py",
|
|
285
|
+
"moduleId": "08",
|
|
286
|
+
"stepId": "exercice-01",
|
|
287
|
+
"slug": "python/m8/ex1",
|
|
288
|
+
"mode": "code"
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"name": "structure",
|
|
292
|
+
"filename": "structure.py",
|
|
293
|
+
"moduleId": "08",
|
|
294
|
+
"stepId": "exercice-02",
|
|
295
|
+
"slug": "python/m8/ex2",
|
|
296
|
+
"mode": "code"
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"name": "architecte",
|
|
300
|
+
"filename": "architecte.py",
|
|
301
|
+
"moduleId": "08",
|
|
302
|
+
"stepId": "exercice-03",
|
|
303
|
+
"slug": "python/m8/ex3",
|
|
304
|
+
"mode": "artifact"
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
"name": "import_specifique",
|
|
308
|
+
"filename": "import_specifique.py",
|
|
309
|
+
"moduleId": "08",
|
|
310
|
+
"stepId": "exercice-04",
|
|
311
|
+
"slug": "python/m8/ex4",
|
|
312
|
+
"mode": "artifact"
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
"name": "mutabilite",
|
|
316
|
+
"filename": "mutabilite.py",
|
|
317
|
+
"moduleId": "08",
|
|
318
|
+
"stepId": "exercice-05",
|
|
319
|
+
"slug": "python/m8/ex5",
|
|
320
|
+
"mode": "code"
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"name": "bloc_notes",
|
|
324
|
+
"filename": "bloc_notes.py",
|
|
325
|
+
"moduleId": "09",
|
|
326
|
+
"stepId": "exercice-01",
|
|
327
|
+
"slug": "python/m9/ex1",
|
|
328
|
+
"mode": "artifact"
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
"name": "horodatage",
|
|
332
|
+
"filename": "horodatage.py",
|
|
333
|
+
"moduleId": "09",
|
|
334
|
+
"stepId": "exercice-02",
|
|
335
|
+
"slug": "python/m9/ex2",
|
|
336
|
+
"mode": "artifact"
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"name": "export_csv",
|
|
340
|
+
"filename": "export_csv.py",
|
|
341
|
+
"moduleId": "09",
|
|
342
|
+
"stepId": "exercice-03",
|
|
343
|
+
"slug": "python/m9/ex3",
|
|
344
|
+
"mode": "artifact"
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
"name": "lecture_json",
|
|
348
|
+
"filename": "lecture_json.py",
|
|
349
|
+
"moduleId": "09",
|
|
350
|
+
"stepId": "exercice-04",
|
|
351
|
+
"slug": "python/m9/ex4",
|
|
352
|
+
"mode": "artifact"
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
"name": "conversion_csv",
|
|
356
|
+
"filename": "conversion_csv.py",
|
|
357
|
+
"moduleId": "09",
|
|
358
|
+
"stepId": "exercice-05",
|
|
359
|
+
"slug": "python/m9/ex5",
|
|
360
|
+
"mode": "artifact"
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
"name": "division",
|
|
364
|
+
"filename": "division.py",
|
|
365
|
+
"moduleId": "10",
|
|
366
|
+
"stepId": "exercice-01",
|
|
367
|
+
"slug": "python/m10/ex1",
|
|
368
|
+
"mode": "code"
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
"name": "age_valide",
|
|
372
|
+
"filename": "age_valide.py",
|
|
373
|
+
"moduleId": "10",
|
|
374
|
+
"stepId": "exercice-02",
|
|
375
|
+
"slug": "python/m10/ex2",
|
|
376
|
+
"mode": "code"
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
"name": "test_mon_code",
|
|
380
|
+
"filename": "test_mon_code.py",
|
|
381
|
+
"moduleId": "10",
|
|
382
|
+
"stepId": "exercice-03",
|
|
383
|
+
"slug": "python/m10/ex3",
|
|
384
|
+
"mode": "artifact"
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
"name": "lecture_securisee",
|
|
388
|
+
"filename": "lecture_securisee.py",
|
|
389
|
+
"moduleId": "10",
|
|
390
|
+
"stepId": "exercice-04",
|
|
391
|
+
"slug": "python/m10/ex4",
|
|
392
|
+
"mode": "code"
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
"name": "test_prix_ttc",
|
|
396
|
+
"filename": "test_prix_ttc.py",
|
|
397
|
+
"moduleId": "10",
|
|
398
|
+
"stepId": "exercice-05",
|
|
399
|
+
"slug": "python/m10/ex5",
|
|
400
|
+
"mode": "artifact"
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
"name": "creation_venv",
|
|
404
|
+
"filename": "creation_venv.py",
|
|
405
|
+
"moduleId": "11",
|
|
406
|
+
"stepId": "exercice-01",
|
|
407
|
+
"slug": "python/m11/ex1",
|
|
408
|
+
"mode": "artifact"
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
"name": "activation_venv",
|
|
412
|
+
"filename": "activation_venv.py",
|
|
413
|
+
"moduleId": "11",
|
|
414
|
+
"stepId": "exercice-02",
|
|
415
|
+
"slug": "python/m11/ex2",
|
|
416
|
+
"mode": "artifact"
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
"name": "test_couleur",
|
|
420
|
+
"filename": "test_couleur.py",
|
|
421
|
+
"moduleId": "11",
|
|
422
|
+
"stepId": "exercice-03",
|
|
423
|
+
"slug": "python/m11/ex3",
|
|
424
|
+
"mode": "artifact"
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
"name": "requirements",
|
|
428
|
+
"filename": "requirements.py",
|
|
429
|
+
"moduleId": "11",
|
|
430
|
+
"stepId": "exercice-04",
|
|
431
|
+
"slug": "python/m11/ex4",
|
|
432
|
+
"mode": "artifact"
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
"name": "transfert_venv",
|
|
436
|
+
"filename": "transfert_venv.py",
|
|
437
|
+
"moduleId": "11",
|
|
438
|
+
"stepId": "exercice-05",
|
|
439
|
+
"slug": "python/m11/ex5",
|
|
440
|
+
"mode": "artifact"
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
"name": "livre",
|
|
444
|
+
"filename": "livre.py",
|
|
445
|
+
"moduleId": "12",
|
|
446
|
+
"stepId": "exercice-01",
|
|
447
|
+
"slug": "python/m12/ex1",
|
|
448
|
+
"mode": "code"
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
"name": "details_livre",
|
|
452
|
+
"filename": "details_livre.py",
|
|
453
|
+
"moduleId": "12",
|
|
454
|
+
"stepId": "exercice-02",
|
|
455
|
+
"slug": "python/m12/ex2",
|
|
456
|
+
"mode": "code"
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
"name": "panier_objet",
|
|
460
|
+
"filename": "panier_objet.py",
|
|
461
|
+
"moduleId": "12",
|
|
462
|
+
"stepId": "exercice-03",
|
|
463
|
+
"slug": "python/m12/ex3",
|
|
464
|
+
"mode": "code"
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
"name": "transfert",
|
|
468
|
+
"filename": "transfert.py",
|
|
469
|
+
"moduleId": "12",
|
|
470
|
+
"stepId": "exercice-04",
|
|
471
|
+
"slug": "python/m12/ex4",
|
|
472
|
+
"mode": "code"
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
"name": "epargne",
|
|
476
|
+
"filename": "epargne.py",
|
|
477
|
+
"moduleId": "12",
|
|
478
|
+
"stepId": "exercice-05",
|
|
479
|
+
"slug": "python/m12/ex5",
|
|
480
|
+
"mode": "code"
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
"name": "configuration",
|
|
484
|
+
"filename": "configuration.py",
|
|
485
|
+
"moduleId": "13",
|
|
486
|
+
"stepId": "exercice-01",
|
|
487
|
+
"slug": "python/m13/ex1",
|
|
488
|
+
"mode": "artifact"
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
"name": "requete_get",
|
|
492
|
+
"filename": "requete_get.py",
|
|
493
|
+
"moduleId": "13",
|
|
494
|
+
"stepId": "exercice-02",
|
|
495
|
+
"slug": "python/m13/ex2",
|
|
496
|
+
"mode": "artifact"
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
"name": "poids_pokemon",
|
|
500
|
+
"filename": "poids_pokemon.py",
|
|
501
|
+
"moduleId": "13",
|
|
502
|
+
"stepId": "exercice-03",
|
|
503
|
+
"slug": "python/m13/ex3",
|
|
504
|
+
"mode": "artifact"
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
"name": "appel_ia",
|
|
508
|
+
"filename": "appel_ia.py",
|
|
509
|
+
"moduleId": "13",
|
|
510
|
+
"stepId": "exercice-04",
|
|
511
|
+
"slug": "python/m13/ex4",
|
|
512
|
+
"mode": "artifact"
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
"name": "fallback",
|
|
516
|
+
"filename": "fallback.py",
|
|
517
|
+
"moduleId": "13",
|
|
518
|
+
"stepId": "exercice-05",
|
|
519
|
+
"slug": "python/m13/ex5",
|
|
520
|
+
"mode": "artifact"
|
|
521
|
+
}
|
|
522
|
+
]
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
import getpass
|
|
5
|
+
import os
|
|
6
|
+
import pathlib
|
|
7
|
+
import re
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import uuid
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from urllib.parse import urlparse
|
|
13
|
+
import requests
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
from rich.text import Text
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
EXERCISES = json.loads(pathlib.Path(__file__).with_name('exercises.json').read_text(encoding='utf-8'))
|
|
21
|
+
CONFIG = pathlib.Path.home() / '.config' / 'kod8' / 'config.json'
|
|
22
|
+
|
|
23
|
+
def settings():
|
|
24
|
+
return json.loads(CONFIG.read_text(encoding='utf-8')) if CONFIG.is_file() else {}
|
|
25
|
+
|
|
26
|
+
def connection():
|
|
27
|
+
base = os.environ.get('KOD8_API_URL', settings().get('api_url', 'https://kod8.live/api/cli')).rstrip('/')
|
|
28
|
+
token = os.environ.get('KOD8_TOKEN', '')
|
|
29
|
+
if not token:
|
|
30
|
+
import keyring
|
|
31
|
+
try:
|
|
32
|
+
token = keyring.get_password('KOD8', base) or ''
|
|
33
|
+
except keyring.errors.KeyringError:
|
|
34
|
+
raise ValueError('Le coffre de votre système est indisponible. Configurez KOD8_TOKEN dans les secrets de votre environnement.')
|
|
35
|
+
parsed = urlparse(base)
|
|
36
|
+
if parsed.scheme != 'https' and not (parsed.scheme == 'http' and parsed.hostname in ('localhost', '127.0.0.1')):
|
|
37
|
+
raise ValueError('KOD8_API_URL doit utiliser HTTPS (HTTP permis uniquement en développement local).')
|
|
38
|
+
if not re.fullmatch(r'kod8_[a-f0-9]{64}', token):
|
|
39
|
+
raise ValueError('Terminez la configuration initiale : kod8 login.')
|
|
40
|
+
return base, {'Authorization': f'Bearer {token}'}
|
|
41
|
+
|
|
42
|
+
def resolve_target(slug, file):
|
|
43
|
+
contract = next((c for c in EXERCISES if c['name'] == (slug or '').removesuffix('.py')), None)
|
|
44
|
+
if contract:
|
|
45
|
+
if contract['mode'] != 'code':
|
|
46
|
+
raise ValueError('Cette activité demande un dépôt et des preuves : utilisez sa page KOD8.')
|
|
47
|
+
target = pathlib.Path(file or contract['filename'])
|
|
48
|
+
if target.name != contract['filename']:
|
|
49
|
+
raise ValueError(f"Nommez le fichier exactement {contract['filename']}.")
|
|
50
|
+
if not target.is_file():
|
|
51
|
+
raise ValueError(f'Fichier introuvable : {target}')
|
|
52
|
+
return contract['slug'], target
|
|
53
|
+
if not slug:
|
|
54
|
+
match = re.search(r'(python/m\d+/ex[1-5])(?:/|$)', pathlib.Path.cwd().as_posix())
|
|
55
|
+
if not match:
|
|
56
|
+
raise ValueError('Précisez le nom indiqué dans l’exercice : kod8 submit contact')
|
|
57
|
+
slug = match.group(1)
|
|
58
|
+
if not re.fullmatch(r'python/m\d{1,2}/(?:ex[1-5]|activite-\d{2})', slug):
|
|
59
|
+
raise ValueError('Identifiant d’exercice invalide.')
|
|
60
|
+
target = pathlib.Path(file or 'main.py')
|
|
61
|
+
if not target.is_file():
|
|
62
|
+
raise ValueError(f'Fichier introuvable : {target}')
|
|
63
|
+
return slug, target
|
|
64
|
+
|
|
65
|
+
def git_sha():
|
|
66
|
+
result = subprocess.run(['git', 'rev-parse', 'HEAD'], capture_output=True, text=True)
|
|
67
|
+
return result.stdout.strip() if result.returncode == 0 else None
|
|
68
|
+
|
|
69
|
+
def test_command(args):
|
|
70
|
+
base, headers = connection()
|
|
71
|
+
slug, file = resolve_target(args.slug, args.file)
|
|
72
|
+
response = requests.get(f'{base}/tests', params={'slug': slug}, headers=headers, timeout=15)
|
|
73
|
+
if not response.ok:
|
|
74
|
+
raise ValueError(response.json().get('error', 'Tests indisponibles.'))
|
|
75
|
+
tests = response.json().get('tests', [])
|
|
76
|
+
if not tests:
|
|
77
|
+
print('Aucun test public disponible. La soumission restera en revue si la grille serveur manque.')
|
|
78
|
+
return 2
|
|
79
|
+
passed = 0
|
|
80
|
+
for test in tests:
|
|
81
|
+
definition = test['definition']
|
|
82
|
+
try:
|
|
83
|
+
run = subprocess.run([sys.executable, str(file)], input=definition.get('stdin', ''), text=True, capture_output=True, timeout=5)
|
|
84
|
+
success = run.returncode == 0 and all(part in run.stdout for part in definition['stdoutIncludesAll'])
|
|
85
|
+
detail = run.stderr[-1200:] if run.returncode else run.stdout[-1200:]
|
|
86
|
+
except subprocess.TimeoutExpired:
|
|
87
|
+
success = False
|
|
88
|
+
detail = 'Temps maximal dépassé (5 secondes).'
|
|
89
|
+
passed += int(success)
|
|
90
|
+
console.print(Text(f"{test['name']} : {'réussi' if success else 'à reprendre'}", style='#3158d8' if success else '#d75a36'))
|
|
91
|
+
if not success:
|
|
92
|
+
console.print(Panel(Text(detail or 'Aucune sortie.'), title='Résultat à reprendre', border_style='#d75a36'))
|
|
93
|
+
print(f'{passed}/{len(tests)} tests publics réussis. Ce résultat local ne modifie pas la progression.')
|
|
94
|
+
return 0 if passed == len(tests) else 1
|
|
95
|
+
|
|
96
|
+
def submit_command(args):
|
|
97
|
+
base, headers = connection()
|
|
98
|
+
slug, file = resolve_target(args.slug, args.file)
|
|
99
|
+
source = file.read_text(encoding='utf-8')
|
|
100
|
+
if len(source.encode('utf-8')) > 64000:
|
|
101
|
+
raise ValueError('Source trop volumineuse (64 Ko maximum).')
|
|
102
|
+
# A submission first checks the public cases and only versions this file.
|
|
103
|
+
checked = test_command(args)
|
|
104
|
+
if checked != 0:
|
|
105
|
+
print('Soumission interrompue : les tests publics doivent réussir avant le commit et le push.')
|
|
106
|
+
return checked
|
|
107
|
+
if not args.no_push:
|
|
108
|
+
subprocess.run(['git', 'add', '--', str(file)], check=True)
|
|
109
|
+
staged = subprocess.run(['git', 'diff', '--cached', '--quiet', '--', str(file)], check=False)
|
|
110
|
+
if staged.returncode == 1:
|
|
111
|
+
stamp = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')
|
|
112
|
+
subprocess.run(['git', 'commit', '--only', '-m', f'[KOD8] Validation : {file.stem} ({stamp})', '--', str(file)], check=True)
|
|
113
|
+
subprocess.run(['git', 'push'], check=True)
|
|
114
|
+
sha = git_sha()
|
|
115
|
+
key = uuid.uuid4().hex if args.retry else hashlib.sha256(f'{slug}:{sha}:{source}'.encode()).hexdigest()
|
|
116
|
+
response = requests.post(f'{base}/submit', json={'slug': slug, 'source': source, 'commitSha': sha, 'requestId': key}, headers=headers, timeout=40)
|
|
117
|
+
data = response.json()
|
|
118
|
+
if not response.ok:
|
|
119
|
+
raise ValueError(data.get('error', 'Soumission non enregistrée.'))
|
|
120
|
+
status = data.get('status')
|
|
121
|
+
print(f"{data.get('activity', slug)} : {status}")
|
|
122
|
+
if data.get('message'):
|
|
123
|
+
print(data['message'])
|
|
124
|
+
for result in data.get('results', []):
|
|
125
|
+
print(f"{result['name']} : {result['message']}")
|
|
126
|
+
if status == 'passed':
|
|
127
|
+
print('Exercice validé. Progression enregistrée dans votre compte.')
|
|
128
|
+
return 0
|
|
129
|
+
return {'failed': 1, 'needs_review': 2, 'error': 3, 'timeout': 3, 'running': 4}.get(status, 3)
|
|
130
|
+
|
|
131
|
+
def login_command(args):
|
|
132
|
+
base = (args.api_url or os.environ.get('KOD8_API_URL', 'https://kod8.live/api/cli')).rstrip('/')
|
|
133
|
+
parsed = urlparse(base)
|
|
134
|
+
if parsed.scheme != 'https' and not (parsed.scheme == 'http' and parsed.hostname in ('localhost', '127.0.0.1')):
|
|
135
|
+
raise ValueError('Adresse KOD8 invalide : HTTPS requis.')
|
|
136
|
+
print(f"Configuration initiale : {base.removesuffix('/api/cli')}/formations/python/installation#cli")
|
|
137
|
+
token = getpass.getpass('Collez le jeton de configuration (saisie masquée) : ')
|
|
138
|
+
if not re.fullmatch(r'kod8_[a-f0-9]{64}', token):
|
|
139
|
+
raise ValueError('Jeton invalide.')
|
|
140
|
+
r = requests.get(f'{base}/tests', params={'slug': 'contact'}, headers={'Authorization': f'Bearer {token}'}, timeout=15)
|
|
141
|
+
if not r.ok:
|
|
142
|
+
raise ValueError('La connexion KOD8 n’a pas été confirmée. Vérifiez le jeton et l’adresse du site.')
|
|
143
|
+
import keyring
|
|
144
|
+
try:
|
|
145
|
+
keyring.set_password('KOD8', base, token)
|
|
146
|
+
except keyring.errors.KeyringError:
|
|
147
|
+
raise ValueError('Coffre système indisponible. Dans Codespaces, utilisez le secret KOD8_TOKEN et KOD8_API_URL.')
|
|
148
|
+
CONFIG.parent.mkdir(parents=True, exist_ok=True)
|
|
149
|
+
CONFIG.write_text(json.dumps({'api_url': base}), encoding='utf-8')
|
|
150
|
+
print('KOD8 est prêt. Vous pouvez utiliser kod8 test contact et kod8 submit contact.')
|
|
151
|
+
return 0
|
|
152
|
+
|
|
153
|
+
def cli():
|
|
154
|
+
parser = argparse.ArgumentParser(description='KOD8 : tests locaux et soumissions liées à votre compte')
|
|
155
|
+
parser.add_argument('--version', action='version', version='KOD8 0.2.0')
|
|
156
|
+
sub = parser.add_subparsers(dest='command', required=True)
|
|
157
|
+
login = sub.add_parser('login', help='Configurer votre compte une seule fois')
|
|
158
|
+
login.add_argument('--api-url')
|
|
159
|
+
for command in ('test', 'submit'):
|
|
160
|
+
p = sub.add_parser(command)
|
|
161
|
+
p.add_argument('slug', nargs='?')
|
|
162
|
+
p.add_argument('file', nargs='?')
|
|
163
|
+
if command == 'submit':
|
|
164
|
+
p.add_argument('--no-push', action='store_true', help='Soumettre sans commit/push (usage technique)')
|
|
165
|
+
p.add_argument('--push', action='store_true', help=argparse.SUPPRESS)
|
|
166
|
+
p.add_argument('--retry', action='store_true', help='Créer une nouvelle tentative après une erreur technique')
|
|
167
|
+
args = parser.parse_args()
|
|
168
|
+
try:
|
|
169
|
+
status = login_command(args) if args.command == 'login' else test_command(args) if args.command == 'test' else submit_command(args)
|
|
170
|
+
except ValueError as error:
|
|
171
|
+
print(str(error), file=sys.stderr)
|
|
172
|
+
status = 3
|
|
173
|
+
except (OSError, requests.RequestException, subprocess.CalledProcessError):
|
|
174
|
+
print('Action non terminée. Vérifiez le jeton, la connexion, le fichier et les outils Git ; réessayez.', file=sys.stderr)
|
|
175
|
+
status = 3
|
|
176
|
+
sys.exit(status)
|
|
177
|
+
|
|
178
|
+
if __name__ == '__main__':
|
|
179
|
+
cli()
|
|
180
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kod8-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: KOD8 Command Line Interface for local testing
|
|
5
|
+
Home-page: https://github.com/Rodrigue-k/kod8
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: requests
|
|
9
|
+
Requires-Dist: rich
|
|
10
|
+
Requires-Dist: keyring
|
|
11
|
+
Dynamic: description
|
|
12
|
+
Dynamic: description-content-type
|
|
13
|
+
Dynamic: home-page
|
|
14
|
+
Dynamic: requires-dist
|
|
15
|
+
Dynamic: requires-python
|
|
16
|
+
Dynamic: summary
|
|
17
|
+
|
|
18
|
+
# KOD8 CLI
|
|
19
|
+
|
|
20
|
+
Tester des exercices à fichier unique en local, puis envoyer leur code au correcteur KOD8. Les tests locaux ne valident pas la progression.
|
|
21
|
+
|
|
22
|
+
## Installation locale
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cd cli
|
|
26
|
+
pip install -e .
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Se connecter sur KOD8, ouvrir `/formations/python/soumissions`, créer un jeton personnel et le fournir dans `KOD8_TOKEN`. Ne jamais le committer. `KOD8_API_URL` vaut `https://kod8.live/api/cli` ; en développement : `http://localhost:5173/api/cli`.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Utilisation quotidienne
|
|
34
|
+
|
|
35
|
+
### 1. Tester son code en local (sans impacter sa note)
|
|
36
|
+
|
|
37
|
+
Créez le fichier imposé par l’énoncé (par exemple `contact.py`). L’installation et la connexion se font une seule fois dans le guide d’installation, avec `kod8 login`. La CLI conserve le jeton dans le coffre système ; Codespaces peut utiliser le secret `KOD8_TOKEN`. Vérifier la version avec `kod8 --version`.
|
|
38
|
+
|
|
39
|
+
Utilisez directement le nom de l’exercice, sans extension :
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
kod8 test contact
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
*Ou si vous êtes directement dans le sous-dossier de l'exercice :*
|
|
46
|
+
```bash
|
|
47
|
+
kod8 test
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
L’outil exécute les tests publics disponibles sur votre ordinateur. Une grille absente ne permet pas de conclure à une réussite.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
### 2. Valider et enregistrer officiellement sa progression
|
|
55
|
+
|
|
56
|
+
Quand tout est vert, vous soumettez votre solution en une seule commande :
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
kod8 submit contact
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
La commande relance les tests publics. Après réussite, elle crée un commit horodaté pour le seul fichier de l’exercice, pousse le commit, puis soumet le code pour votre compte. Seuls des tests serveur réussis valident l’exercice. Un fichier inchangé ne crée pas de commit supplémentaire. `--retry` crée une nouvelle tentative après une erreur technique ; `--no-push` est réservé aux intégrations techniques.
|
|
63
|
+
|
|
64
|
+
Codes de sortie : 0 réussite, 1 échec, 2 revue nécessaire, 3 erreur technique, 4 correction en cours. Les projets et exercices à plusieurs fichiers, à fichiers produits, d’environnement ou d’API se déposent sur leur page avec le dépôt et les preuves ; ils ne sont pas validés par un simple main.py.
|
|
65
|
+
|
|
66
|
+
Pour GitHub Actions, copier `examples/github-actions.yml` dans `.github/workflows/kod8.yml` et `examples/ci_submit.py` dans `.github/scripts/ci_submit.py` du dépôt apprenant. Ajouter le secret `KOD8_TOKEN` et la variable `KOD8_API_URL` si nécessaire. Le helper soumet les scripts nommés reconnus par le catalogue et conserve la compatibilité avec `python/m*/ex*/main.py` ; il n’exécute pas le code apprenant dans la CI. Ne pas appliquer ce workflow aux activités qui demandent un livrable en revue.
|
|
67
|
+
|
|
68
|
+
Le catalogue canonique est `lib/course-content/exercise-contracts.json`. Sa copie embarquée dans la CLI est `kod8/exercises.json`. Régénérer avec `python scripts/build-exercise-contracts.py`, puis produire l’archive téléchargeable avec `python scripts/package-cli.py`. Le paquet n’est pas confirmé publié sur PyPI.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.py
|
|
5
|
+
kod8/__init__.py
|
|
6
|
+
kod8/exercises.json
|
|
7
|
+
kod8/main.py
|
|
8
|
+
kod8_cli.egg-info/PKG-INFO
|
|
9
|
+
kod8_cli.egg-info/SOURCES.txt
|
|
10
|
+
kod8_cli.egg-info/dependency_links.txt
|
|
11
|
+
kod8_cli.egg-info/entry_points.txt
|
|
12
|
+
kod8_cli.egg-info/requires.txt
|
|
13
|
+
kod8_cli.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
kod8
|
kod8_cli-0.2.0/setup.cfg
ADDED
kod8_cli-0.2.0/setup.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from setuptools import setup, find_packages
|
|
3
|
+
|
|
4
|
+
setup(
|
|
5
|
+
name='kod8-cli',
|
|
6
|
+
version='0.2.0',
|
|
7
|
+
description='KOD8 Command Line Interface for local testing',
|
|
8
|
+
long_description=Path(__file__).with_name('README.md').read_text(encoding='utf-8'),
|
|
9
|
+
long_description_content_type='text/markdown',
|
|
10
|
+
url='https://github.com/Rodrigue-k/kod8',
|
|
11
|
+
python_requires='>=3.10',
|
|
12
|
+
packages=find_packages(include=['kod8', 'kod8.*']),
|
|
13
|
+
package_data={'kod8': ['exercises.json']},
|
|
14
|
+
install_requires=[
|
|
15
|
+
'requests',
|
|
16
|
+
'rich',
|
|
17
|
+
'keyring',
|
|
18
|
+
],
|
|
19
|
+
entry_points={
|
|
20
|
+
'console_scripts': [
|
|
21
|
+
'kod8=kod8.main:cli',
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
)
|