examshell-python 0.1.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.
- examshell_python-0.1.0/PKG-INFO +10 -0
- examshell_python-0.1.0/README.md +0 -0
- examshell_python-0.1.0/pyproject.toml +27 -0
- examshell_python-0.1.0/setup.cfg +4 -0
- examshell_python-0.1.0/src/examshell/__init__.py +3 -0
- examshell_python-0.1.0/src/examshell/__main__.py +6 -0
- examshell_python-0.1.0/src/examshell/config.py +0 -0
- examshell_python-0.1.0/src/examshell/data/data.json +588 -0
- examshell_python-0.1.0/src/examshell/main.py +326 -0
- examshell_python-0.1.0/src/examshell/models.py +25 -0
- examshell_python-0.1.0/src/examshell/task_generator.py +213 -0
- examshell_python-0.1.0/src/examshell_python.egg-info/PKG-INFO +10 -0
- examshell_python-0.1.0/src/examshell_python.egg-info/SOURCES.txt +15 -0
- examshell_python-0.1.0/src/examshell_python.egg-info/dependency_links.txt +1 -0
- examshell_python-0.1.0/src/examshell_python.egg-info/entry_points.txt +2 -0
- examshell_python-0.1.0/src/examshell_python.egg-info/requires.txt +2 -0
- examshell_python-0.1.0/src/examshell_python.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: examshell-python
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Interactive 42-style exam practice shell for Python exercises
|
|
5
|
+
Author: Denis
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: pydantic>=2.0
|
|
10
|
+
Requires-Dist: rich>=13.0
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "examshell-python"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Interactive 42-style exam practice shell for Python exercises"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Denis" }
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"pydantic>=2.0",
|
|
17
|
+
"rich>=13.0",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
examshell = "examshell.main:cli"
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["src"]
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.package-data]
|
|
27
|
+
examshell = ["data/*.json"]
|
|
File without changes
|
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": 1,
|
|
4
|
+
"level": 1,
|
|
5
|
+
"difficulty": "easy",
|
|
6
|
+
"name": "py_bracket_validator",
|
|
7
|
+
"file": "py_bracket_validator.py",
|
|
8
|
+
"description": {
|
|
9
|
+
"en": "Write a function that checks if the brackets in a string are valid.\nA string is valid if every opening bracket has a matching closing bracket\nin the correct order.\nAllowed brackets: (), [], {}",
|
|
10
|
+
"de": "Schreibe eine Funktion, die prГјft, ob Klammern in einem String korrekt gepaart und geschachtelt sind.\nUnterstГјtze drei Arten: (), [], {}.\n\nDie Funktion soll:\n- True zurГјckgeben, wenn alle Klammern korrekt gepaart sind\n- True fГјr Strings ohne Klammern zurГјckgeben\n- False bei nicht passenden oder falsch geschachtelten Klammern zurГјckgeben\n- Zeichen auГџerhalb von Klammern ignorieren",
|
|
11
|
+
"fr": "Écris une fonction qui vérifie si les parenthèses d’une chaîne sont correctement appariées et imbriquées.\nPrends en charge trois types : (), [], {}.\n\nLa fonction doit :\n- Retourner True si toutes les parenthèses sont correctement appariées\n- Retourner True pour les chaînes sans aucune parenthèse\n- Retourner False si elles ne correspondent pas ou sont mal imbriquées\n- Ignorer les caractères qui ne sont pas des parenthèses",
|
|
12
|
+
"es": "Escribe una funciГіn que compruebe si los parГ©ntesis de una cadena estГЎn bien emparejados y anidados.\nAdmite tres tipos: (), [], {}.\n\nLa funciГіn debe:\n- Devolver True si todos los parГ©ntesis estГЎn bien emparejados\n- Devolver True si la cadena no tiene parГ©ntesis\n- Devolver False si faltan pares o el anidamiento es incorrecto\n- Ignorar caracteres que no sean parГ©ntesis",
|
|
13
|
+
"it": "Scrivi una funzione che verifichi se le parentesi in una stringa sono accoppiate e annidate correttamente.\nSupporta tre tipi: (), [], {}.\n\nLa funzione deve:\n- Restituire True se tutte le parentesi sono corrette\n- Restituire True per stringhe senza parentesi\n- Restituire False se mancano coppie o l’annidamento è errato\n- Ignorare i caratteri che non sono parentesi"
|
|
14
|
+
},
|
|
15
|
+
"signature": "def bracket_validator(s: str) -> bool:",
|
|
16
|
+
"examples": [
|
|
17
|
+
{
|
|
18
|
+
"input": "bracket_validator(\"()\")",
|
|
19
|
+
"output": "True"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"input": "bracket_validator(\"()[]{}\")",
|
|
23
|
+
"output": "True"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"input": "bracket_validator(\"(]\")",
|
|
27
|
+
"output": "False"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"input": "bracket_validator(\"([)]\")",
|
|
31
|
+
"output": "False"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"input": "bracket_validator(\"{[]}\")",
|
|
35
|
+
"output": "True"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"input": "bracket_validator(\"hello(world)\")",
|
|
39
|
+
"output": "True"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"input": "bracket_validator(\"((())\")",
|
|
43
|
+
"output": "False"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"input": "bracket_validator(\"\")",
|
|
47
|
+
"output": "True"
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": 2,
|
|
53
|
+
"level": 1,
|
|
54
|
+
"difficulty": "easy",
|
|
55
|
+
"name": "py_cryptic_sorter",
|
|
56
|
+
"file": "py_cryptic_sorter.py",
|
|
57
|
+
"description": {
|
|
58
|
+
"en": "Write a function that sorts a list of strings according to multiple criteria:\n1. Primary sort: By string length (shortest first)\n2. Secondary sort: ASCII order, except letters are compared case-insensitively\n (for strings of same length)\n3. Tertiary sort: By number of vowels (ascending, for same length and lexically equal)\n4. Equal strings will appear in the same order as in the input list.",
|
|
59
|
+
"de": "Schreibe eine Funktion, die eine Liste von Strings mit dreistufiger Priorität sortiert:\n1. Primär: nach Länge (aufsteigend)\n2. Sekundär: lexikographisch (alphabetisch, ohne Groß-/Kleinschreibung, aufsteigend)\n3. Tertiär: nach Anzahl der Vokale (aufsteigend bei gleicher Länge und lexikalischer Gleichheit)\n\nDie Funktion muss:\n- Leere Strings und leere Listen verarbeiten\n- Gemischte Groß-/Kleinschreibung (für Sortierung wie Kleinbuchstaben behandeln)\n- Sonderzeichen (für Vokalzählung ignorieren)",
|
|
60
|
+
"fr": "Г‰cris une fonction qui trie une liste de chaГ®nes avec une prioritГ© Г trois niveaux :\n1. Tri principal : par longueur (croissant)\n2. Tri secondaire : lexicographique (alphabГ©tique, insensible Г la casse, croissant)\n3. Tri tertiaire : par nombre de voyelles (croissant, si longueur et ordre lexicographique Г©gaux)\n\nLa fonction doit gГ©rer :\n- ChaГ®nes vides et listes vides\n- Casses mixtes (traiter comme des minuscules pour le tri)\n- CaractГЁres spГ©ciaux (les ignorer pour compter les voyelles)",
|
|
61
|
+
"es": "Escribe una funciГіn que ordene una lista de cadenas con prioridad en tres niveles:\n1. Primario: por longitud (ascendente)\n2. Secundario: lexicogrГЎfico (alfabГ©tico, sin distinguir mayГєsculas, ascendente)\n3. Terciario: por nГєmero de vocales (ascendente si la longitud y el orden lГ©xico coinciden)\n\nLa funciГіn debe manejar:\n- Cadenas vacГas y listas vacГas\n- MayГєsculas y minГєsculas mezcladas (tratar como minГєsculas al ordenar)\n- Caracteres especiales (ignorarlos al contar vocales)",
|
|
62
|
+
"it": "Scrivi una funzione che ordini una lista di stringhe con priorità a tre livelli:\n1. Primario: per lunghezza (crescente)\n2. Secondario: lessicografico (alfabetico, case-insensitive, crescente)\n3. Terziario: per numero di vocali (crescente a parità di lunghezza e ordine lessicale)\n\nLa funzione deve gestire:\n- Stringhe vuote e liste vuote\n- Maiuscole e minuscole miste (per l’ordinamento trattare come minuscole)\n- Caratteri speciali (ignorarli nel conteggio delle vocali)"
|
|
63
|
+
},
|
|
64
|
+
"signature": "def cryptic_sorter(strings: list[str]) -> list[str]:",
|
|
65
|
+
"examples": [
|
|
66
|
+
{
|
|
67
|
+
"input": "cryptic_sorter([\"apple\",\"cat\",\"banana\",\"dog\",\"elephant\"])",
|
|
68
|
+
"output": "[\"cat\",\"dog\",\"apple\",\"banana\",\"elephant\"]"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"input": "cryptic_sorter([\"aaa\",\"bbb\",\"AAA\",\"BBB\"])",
|
|
72
|
+
"output": "[\"AAA\", \"aaa\", \"BBB\", \"bbb\"]"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"input": "cryptic_sorter([\"hello\",\"world\",\"hi\",\"test\"])",
|
|
76
|
+
"output": "[\"hi\",\"test\",\"hello\",\"world\"]"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"input": "cryptic_sorter([])",
|
|
80
|
+
"output": "[]"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"input": "cryptic_sorter([\"\"])",
|
|
84
|
+
"output": "[\"\"]"
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"id": 3,
|
|
90
|
+
"level": 2,
|
|
91
|
+
"difficulty": "easy",
|
|
92
|
+
"name": "py_echo_validator",
|
|
93
|
+
"file": "py_echo_validator.py",
|
|
94
|
+
"description": {
|
|
95
|
+
"en": "Write a function that checks if a string is a palindrome,\nignoring spaces and case, only consider alphabetic characters\nfor the comparison.",
|
|
96
|
+
"de": "Schreibe eine Funktion, die prГјft, ob ein String ein Palindrom ist - Leerzeichen und GroГџ-/Kleinschreibung ignorieren; nur alphabetische Zeichen fГјr den Vergleich berГјcksichtigen.",
|
|
97
|
+
"fr": "Г‰cris une fonction qui vГ©rifie si une chaГ®ne est un palindrome, en ignorant espaces et casse, et en ne considГ©rant que les caractГЁres alphabГ©tiques pour la comparaison.",
|
|
98
|
+
"es": "Escribe una funciГіn que compruebe si una cadena es un palГndromo, ignorando espacios y mayГєsculas/minГєsculas, y considerando solo caracteres alfabГ©ticos en la comparaciГіn.",
|
|
99
|
+
"it": "Scrivi una funzione che verifichi se una stringa ГЁ palindroma, ignorando spazi e maiuscole/minuscole e considerando solo i caratteri alfabetici nel confronto."
|
|
100
|
+
},
|
|
101
|
+
"signature": "def echo_validator(text: str) -> bool:",
|
|
102
|
+
"examples": [
|
|
103
|
+
{
|
|
104
|
+
"input": "echo_validator(\"racecar\")",
|
|
105
|
+
"output": "True"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"input": "echo_validator(\"A man a plan a canal Panama\")",
|
|
109
|
+
"output": "True"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"input": "echo_validator(\"race a car\")",
|
|
113
|
+
"output": "False"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"input": "echo_validator(\"Was it a car or a cat I saw\")",
|
|
117
|
+
"output": "True"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"input": "echo_validator(\"hello\")",
|
|
121
|
+
"output": "False"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"input": "echo_validator(\"Madam Im Adam\")",
|
|
125
|
+
"output": "True"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"input": "echo_validator(\"\")",
|
|
129
|
+
"output": "False"
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"id": 4,
|
|
135
|
+
"level": 2,
|
|
136
|
+
"difficulty": "easy",
|
|
137
|
+
"name": "py_mirror_matrix",
|
|
138
|
+
"file": "py_mirror_matrix.py",
|
|
139
|
+
"description": {
|
|
140
|
+
"en": "Given a 2D matrix (list of lists), return a new matrix where each row\nis reversed.",
|
|
141
|
+
"de": "Schreibe eine Funktion, die eine 2D-Matrix horizontal spiegelt, indem jede Zeile umgekehrt wird.\n\nDie Funktion soll:\n- Eine 2D-Liste (Matrix) aus ganzen Zahlen als Eingabe nehmen\n- Eine neue 2D-Liste zurückgeben, in der jede Zeile horizontal umgekehrt ist\n- Matrizen beliebiger Größe (quadratisch oder rechteckig) verarbeiten\n- Die ursprüngliche Zeilenreihenfolge beibehalten\n- Die ursprüngliche Matrix nicht verändern",
|
|
142
|
+
"fr": "Écris une fonction qui miroite une matrice 2D horizontalement en inversant chaque ligne.\n\nLa fonction doit :\n- Prendre une liste 2D (matrice) d’entiers en entrée\n- Retourner une nouvelle liste 2D où chaque ligne est inversée horizontalement\n- Gérer des matrices de toute taille (carrées ou rectangulaires)\n- Conserver l’ordre d’origine des lignes\n- Ne pas modifier la matrice d’origine",
|
|
143
|
+
"es": "Escribe una funciГіn que espeje una matriz 2D horizontalmente invirtiendo cada fila.\n\nLa funciГіn debe:\n- Recibir una lista 2D (matriz) de enteros\n- Devolver una nueva lista 2D donde cada fila estГ© invertida horizontalmente\n- Manejar matrices de cualquier tamaГ±o (cuadradas o rectangulares)\n- Conservar el orden original de las filas\n- No modificar la matriz original",
|
|
144
|
+
"it": "Scrivi una funzione che specchia una matrice 2D in orizzontale invertendo ogni riga.\n\nLa funzione deve:\n- Prendere in input una lista 2D (matrice) di interi\n- Restituire una nuova lista 2D in cui ogni riga è invertita orizzontalmente\n- Gestire matrici di qualsiasi dimensione (quadrate o rettangolari)\n- Preservare l’ordine originale delle righe\n- Non modificare la matrice originale"
|
|
145
|
+
},
|
|
146
|
+
"signature": "def mirror_matrix(matrix: list[list[int]]) -> list[list[int]]:",
|
|
147
|
+
"examples": [
|
|
148
|
+
{
|
|
149
|
+
"input": "mirror_matrix([[1,2,3],[4,5,6]])",
|
|
150
|
+
"output": "[[3,2,1],[6,5,4]]"
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"input": "mirror_matrix([[1,2],[3,4],[5,6]])",
|
|
154
|
+
"output": "[[2,1],[4,3],[6,5]]"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"input": "mirror_matrix([[7]])",
|
|
158
|
+
"output": "[[7]]"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"input": "mirror_matrix([[1,2,3,4]])",
|
|
162
|
+
"output": "[[4,3,2,1]]"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"input": "mirror_matrix([[-1,-2],[-3,-4]])",
|
|
166
|
+
"output": "[[-2,-1],[-4,-3]]"
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"id": 5,
|
|
172
|
+
"level": 3,
|
|
173
|
+
"difficulty": "medium",
|
|
174
|
+
"name": "py_hidenp",
|
|
175
|
+
"file": "py_hidenp.py",
|
|
176
|
+
"description": {
|
|
177
|
+
"en": "Write a function that checks if the string 'small' is a subsequence\nof 'big'. A subsequence means all characters of 'small' appear in 'big'\nin the same order, but not necessarily consecutively.\nFunction is case-sensitive.",
|
|
178
|
+
"de": "Write a function that checks if the string 'small' is a subsequence\nof 'big'. A subsequence means all characters of 'small' appear in 'big'\nin the same order, but not necessarily consecutively.\nFunction is case-sensitive.",
|
|
179
|
+
"fr": "Write a function that checks if the string 'small' is a subsequence\nof 'big'. A subsequence means all characters of 'small' appear in 'big'\nin the same order, but not necessarily consecutively.\nFunction is case-sensitive.",
|
|
180
|
+
"es": "Write a function that checks if the string 'small' is a subsequence\nof 'big'. A subsequence means all characters of 'small' appear in 'big'\nin the same order, but not necessarily consecutively.\nFunction is case-sensitive.",
|
|
181
|
+
"it": "Write a function that checks if the string 'small' is a subsequence\nof 'big'. A subsequence means all characters of 'small' appear in 'big'\nin the same order, but not necessarily consecutively.\nFunction is case-sensitive."
|
|
182
|
+
},
|
|
183
|
+
"signature": "def hidenp(small: str, big: str) -> bool:",
|
|
184
|
+
"examples": [
|
|
185
|
+
{
|
|
186
|
+
"input": "hidenp(\"abc\", \"a1b2c3\")",
|
|
187
|
+
"output": "True"
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"input": "hidenp(\"ace\", \"abcde\")",
|
|
191
|
+
"output": "True"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"input": "hidenp(\"aec\", \"abcde\")",
|
|
195
|
+
"output": "False"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"input": "hidenp(\"\", \"abc\")",
|
|
199
|
+
"output": "True"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"input": "hidenp(\"abc\", \"ab\")",
|
|
203
|
+
"output": "False"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"input": "hidenp(\"aaaa\", \"aaa\")",
|
|
207
|
+
"output": "False"
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"input": "hidenp(\"sing\",\"subsequence testing\")",
|
|
211
|
+
"output": "True"
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"id": 6,
|
|
217
|
+
"level": 3,
|
|
218
|
+
"difficulty": "medium",
|
|
219
|
+
"name": "py_inter",
|
|
220
|
+
"file": "py_inter.py",
|
|
221
|
+
"description": {
|
|
222
|
+
"en": "Write a function that returns a string with the characters that appear\nin both strings, without repetitions. Characters are added in the order\nthey appear in the first string.",
|
|
223
|
+
"de": "Write a function that returns a string with the characters that appear\nin both strings, without repetitions. Characters are added in the order\nthey appear in the first string.",
|
|
224
|
+
"fr": "Write a function that returns a string with the characters that appear\nin both strings, without repetitions. Characters are added in the order\nthey appear in the first string.",
|
|
225
|
+
"es": "Write a function that returns a string with the characters that appear\nin both strings, without repetitions. Characters are added in the order\nthey appear in the first string.",
|
|
226
|
+
"it": "Write a function that returns a string with the characters that appear\nin both strings, without repetitions. Characters are added in the order\nthey appear in the first string."
|
|
227
|
+
},
|
|
228
|
+
"signature": "def inter(s1: str, s2: str) -> str:",
|
|
229
|
+
"examples": [
|
|
230
|
+
{
|
|
231
|
+
"input": "inter(\"hello\", \"world\")",
|
|
232
|
+
"output": "\"lo\""
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"input": "inter(\"banana\", \"band\")",
|
|
236
|
+
"output": "\"ban\""
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"input": "inter(\"abcabc\", \"bc\")",
|
|
240
|
+
"output": "\"bc\""
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"input": "inter(\"abc\", \"xyz\")",
|
|
244
|
+
"output": "\"\""
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
"input": "inter(\"\", \"abc\")",
|
|
248
|
+
"output": "\"\""
|
|
249
|
+
}
|
|
250
|
+
]
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"id": 7,
|
|
254
|
+
"level": 3,
|
|
255
|
+
"difficulty": "medium",
|
|
256
|
+
"name": "py_number_base_converter",
|
|
257
|
+
"file": "py_number_base_converter.py",
|
|
258
|
+
"description": {
|
|
259
|
+
"en": "Write a function that converts a number from one base to another.\nSupport bases from 2 to 36 inclusive.\nUse digits 0-9 and letters A-Z for values 10-35.\nReturn \"ERROR\" for invalid inputs.",
|
|
260
|
+
"de": "Schreibe eine Funktion, die eine Zahl von einer Basis in eine andere konvertiert.\nUnterstГјtze Basen von 2 bis 36 einschlieГџlich, mit Ziffern 0-9 und Buchstaben A-Z fГјr die Werte 10-35. Gib bei ungГјltigen Eingaben (Basis, Ziffern) \"ERROR\" zurГјck.",
|
|
261
|
+
"fr": "Г‰cris une fonction qui convertit un nombre d'une base vers une autre.\nPrends en charge les bases de 2 Г 36 inclus, avec les chiffres 0-9 et les lettres A-Z pour les valeurs 10-35. Renvoie \"ERROR\" pour les entrГ©es invalides (base, chiffres).",
|
|
262
|
+
"es": "Escribe una funciГіn que convierta un nГєmero de una base a otra.\nAdmite bases de 2 a 36 inclusive, usando dГgitos 0-9 y letras A-Z para los valores 10-35. Devuelve \"ERROR\" si la entrada no es vГЎlida (base, dГgitos).",
|
|
263
|
+
"it": "Scrivi una funzione che converta un numero da una base a un'altra.\nSupporta basi da 2 a 36 inclusive, usando cifre 0-9 e lettere A-Z per i valori 10-35. Restituisci \"ERROR\" per input non validi (base, cifre)."
|
|
264
|
+
},
|
|
265
|
+
"signature": "def number_base_converter(number: str, from_base: int, to_base: int) -> str:",
|
|
266
|
+
"examples": [
|
|
267
|
+
{
|
|
268
|
+
"input": "number_base_converter(\"1010\", 2, 10)",
|
|
269
|
+
"output": "\"10\""
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"input": "number_base_converter(\"FF\", 16, 10)",
|
|
273
|
+
"output": "\"255\""
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"input": "number_base_converter(\"255\", 10, 16)",
|
|
277
|
+
"output": "\"FF\""
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"input": "number_base_converter(\"123\", 10, 2)",
|
|
281
|
+
"output": "\"1111011\""
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"input": "number_base_converter(\"Z\", 36, 10)",
|
|
285
|
+
"output": "\"35\""
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"input": "number_base_converter(\"35\", 10, 36)",
|
|
289
|
+
"output": "\"Z\""
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"input": "number_base_converter(\"123\", 1, 10)",
|
|
293
|
+
"output": "\"ERROR\""
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
"input": "number_base_converter(\"G\", 16, 10)",
|
|
297
|
+
"output": "\"ERROR\""
|
|
298
|
+
}
|
|
299
|
+
]
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
"id": 8,
|
|
303
|
+
"level": 3,
|
|
304
|
+
"difficulty": "medium",
|
|
305
|
+
"name": "py_pattern_tracker",
|
|
306
|
+
"file": "py_pattern_tracker.py",
|
|
307
|
+
"description": {
|
|
308
|
+
"en": "Write a function that counts the number of valid consecutive digit pairs\nin a string. A valid pair consists of two adjacent digits where the second\ndigit is exactly one greater than the first.\nA 9 followed by a 0 is NOT a valid pair.",
|
|
309
|
+
"de": "Schreibe eine Funktion, die die Anzahl gültiger aufeinanderfolgender Ziffernpaare in einem String zählt. Ein gültiges Paar besteht aus zwei benachbarten Ziffern, bei denen die zweite genau um eins größer ist als die erste. Eine 9 gefolgt von 0 ist KEIN gültiges Paar.",
|
|
310
|
+
"fr": "Écris une fonction qui compte le nombre de paires de chiffres consécutifs valides dans une chaîne. Une paire valide est formée de deux chiffres adjacents dont le second vaut exactement un de plus que le premier. Un 9 suivi d’un 0 n’est PAS une paire valide.",
|
|
311
|
+
"es": "Escribe una funciГіn que cuente cuГЎntos pares consecutivos de dГgitos vГЎlidos hay en una cadena. Un par vГЎlido son dos dГgitos adyacentes donde el segundo es exactamente uno mayor que el primero. Un 9 seguido de 0 NO es un par vГЎlido.",
|
|
312
|
+
"it": "Scrivi una funzione che conti quante coppie consecutive di cifre valide ci sono in una stringa. Una coppia valida ГЁ formata da due cifre adiacenti in cui la seconda ГЁ esattamente uno piГ№ della prima. Un 9 seguito da 0 NON ГЁ una coppia valida."
|
|
313
|
+
},
|
|
314
|
+
"signature": "def pattern_tracker(text: str) -> int:",
|
|
315
|
+
"examples": [
|
|
316
|
+
{
|
|
317
|
+
"input": "pattern_tracker(\"123\")",
|
|
318
|
+
"output": "2"
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
"input": "pattern_tracker(\"12a34\")",
|
|
322
|
+
"output": "2"
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
"input": "pattern_tracker(\"987654321\")",
|
|
326
|
+
"output": "0"
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
"input": "pattern_tracker(\"01234567\")",
|
|
330
|
+
"output": "7"
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
"input": "pattern_tracker(\"abc\")",
|
|
334
|
+
"output": "0"
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
"input": "pattern_tracker(\"1a2b3c4\")",
|
|
338
|
+
"output": "0"
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
"input": "pattern_tracker(\"112233\")",
|
|
342
|
+
"output": "2"
|
|
343
|
+
}
|
|
344
|
+
]
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
"id": 9,
|
|
348
|
+
"level": 4,
|
|
349
|
+
"difficulty": "medium",
|
|
350
|
+
"name": "py_anagram",
|
|
351
|
+
"file": "py_anagram.py",
|
|
352
|
+
"description": {
|
|
353
|
+
"en": "Write a function that checks if two strings are anagrams.\nThey must contain exactly the same letters with the same quantity,\nignoring case and spaces.",
|
|
354
|
+
"de": "Write a function that checks if two strings are anagrams.\nThey must contain exactly the same letters with the same quantity,\nignoring case and spaces.",
|
|
355
|
+
"fr": "Write a function that checks if two strings are anagrams.\nThey must contain exactly the same letters with the same quantity,\nignoring case and spaces.",
|
|
356
|
+
"es": "Write a function that checks if two strings are anagrams.\nThey must contain exactly the same letters with the same quantity,\nignoring case and spaces.",
|
|
357
|
+
"it": "Write a function that checks if two strings are anagrams.\nThey must contain exactly the same letters with the same quantity,\nignoring case and spaces."
|
|
358
|
+
},
|
|
359
|
+
"signature": "def anagram(s1: str, s2: str) -> bool:",
|
|
360
|
+
"examples": [
|
|
361
|
+
{
|
|
362
|
+
"input": "anagram(\"listen\", \"silent\")",
|
|
363
|
+
"output": "True"
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
"input": "anagram(\"Triangle\", \"Integral\")",
|
|
367
|
+
"output": "True"
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
"input": "anagram(\"Dormitory\", \"Dirty Room\")",
|
|
371
|
+
"output": "True"
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
"input": "anagram(\"hello\", \"world\")",
|
|
375
|
+
"output": "False"
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"input": "anagram(\"\", \"\")",
|
|
379
|
+
"output": "True"
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"input": "anagram(\"abc\", \"abcc\")",
|
|
383
|
+
"output": "False"
|
|
384
|
+
}
|
|
385
|
+
]
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
"id": 10,
|
|
389
|
+
"level": 4,
|
|
390
|
+
"difficulty": "medium",
|
|
391
|
+
"name": "py_shadow_merge",
|
|
392
|
+
"file": "py_shadow_merge.py",
|
|
393
|
+
"description": {
|
|
394
|
+
"en": "Write a function that merges two sorted lists into one sorted list.",
|
|
395
|
+
"de": "Schreibe eine Funktion, die zwei sortierte Listen zu einer sortierten Liste zusammenfГјhrt.",
|
|
396
|
+
"fr": "Г‰cris une fonction qui fusionne deux listes triГ©es en une seule liste triГ©e.",
|
|
397
|
+
"es": "Escribe una funciГіn que fusione dos listas ordenadas en una sola lista ordenada.",
|
|
398
|
+
"it": "Scrivi una funzione che unisca due liste ordinate in un’unica lista ordinata."
|
|
399
|
+
},
|
|
400
|
+
"signature": "def shadow_merge(list1: list[int], list2: list[int]) -> list[int]:",
|
|
401
|
+
"examples": [
|
|
402
|
+
{
|
|
403
|
+
"input": "shadow_merge([1,3,5], [2,4,6])",
|
|
404
|
+
"output": "[1,2,3,4,5,6]"
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
"input": "shadow_merge([1,2,3], [4,5,6])",
|
|
408
|
+
"output": "[1,2,3,4,5,6]"
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
"input": "shadow_merge([1], [2,3,4])",
|
|
412
|
+
"output": "[1,2,3,4]"
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
"input": "shadow_merge([], [1,2,3])",
|
|
416
|
+
"output": "[1,2,3]"
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
"input": "shadow_merge([1,1,2], [1,3,3])",
|
|
420
|
+
"output": "[1,1,1,2,3,3]"
|
|
421
|
+
}
|
|
422
|
+
]
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
"id": 11,
|
|
426
|
+
"level": 4,
|
|
427
|
+
"difficulty": "medium",
|
|
428
|
+
"name": "py_string_permutation_checker",
|
|
429
|
+
"file": "py_string_permutation_checker.py",
|
|
430
|
+
"description": {
|
|
431
|
+
"en": "Write a function that determines if two strings are permutations of each other.\nCase sensitive. Whitespace and punctuation count as regular characters.\nEmpty strings are permutations of each other.",
|
|
432
|
+
"de": "Schreibe eine Funktion, die feststellt, ob zwei Strings Permutationen voneinander sind.\nZwei Strings sind Permutationen, wenn sie dieselben Zeichen mit denselben Häufigkeiten enthalten.\n\nDie Funktion soll:\n- Prüfen, ob beide Strings genau dieselben Zeichen enthalten\n- Zeichenhäufigkeiten zählen (groß-/kleinschreibungssensitiv)\n- True zurückgeben, wenn es Permutationen sind, sonst False\n- Leere Strings behandeln (zwei leere Strings sind Permutationen)\n- Leerzeichen und Satzzeichen wie normale Zeichen behandeln",
|
|
433
|
+
"fr": "Écris une fonction qui détermine si deux chaînes sont des permutations l’une de l’autre.\nDeux chaînes sont des permutations si elles contiennent les mêmes caractères avec les mêmes fréquences.\n\nLa fonction doit :\n- Vérifier que les deux chaînes contiennent exactement les mêmes caractères\n- Compter les fréquences (sensible à la casse)\n- Retourner True si ce sont des permutations, sinon False\n- Gérer les chaînes vides (deux chaînes vides sont des permutations)\n- Traiter espaces et ponctuation comme des caractères normaux",
|
|
434
|
+
"es": "Escribe una funciГіn que determine si dos cadenas son permutaciones entre sГ.\nDos cadenas son permutaciones si contienen los mismos caracteres con las mismas frecuencias.\n\nLa funciГіn debe:\n- Comprobar que ambas cadenas tienen exactamente los mismos caracteres\n- Contar frecuencias (distingue mayГєsculas y minГєsculas)\n- Devolver True si son permutaciones, False en caso contrario\n- Manejar cadenas vacГas (dos vacГas son permutaciГіn)\n- Tratar espacios y puntuaciГіn como caracteres normales",
|
|
435
|
+
"it": "Scrivi una funzione che determini se due stringhe sono permutazioni l’una dell’altra.\nDue stringhe sono permutazioni se contengono gli stessi caratteri con le stesse frequenze.\n\nLa funzione deve:\n- Verificare che entrambe le stringhe contengano esattamente gli stessi caratteri\n- Contare le frequenze (case-sensitive)\n- Restituire True se sono permutazioni, altrimenti False\n- Gestire stringhe vuote (due vuote sono permutazioni)\n- Considerare spazi e punteggiatura come caratteri normali"
|
|
436
|
+
},
|
|
437
|
+
"signature": "def string_permutation_checker(s1: str, s2: str) -> bool:",
|
|
438
|
+
"examples": [
|
|
439
|
+
{
|
|
440
|
+
"input": "string_permutation_checker(\"abc\", \"bca\")",
|
|
441
|
+
"output": "True"
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
"input": "string_permutation_checker(\"abc\", \"def\")",
|
|
445
|
+
"output": "False"
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
"input": "string_permutation_checker(\"listen\", \"silent\")",
|
|
449
|
+
"output": "True"
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
"input": "string_permutation_checker(\"hello\", \"bello\")",
|
|
453
|
+
"output": "False"
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
"input": "string_permutation_checker(\"\", \"\")",
|
|
457
|
+
"output": "True"
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
"input": "string_permutation_checker(\"a\", \"\")",
|
|
461
|
+
"output": "False"
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
"input": "string_permutation_checker(\"Abc\", \"abc\")",
|
|
465
|
+
"output": "False"
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
"input": "string_permutation_checker(\"a gentleman\",\"elegant man\")",
|
|
469
|
+
"output": "True"
|
|
470
|
+
}
|
|
471
|
+
]
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
"id": 12,
|
|
475
|
+
"level": 5,
|
|
476
|
+
"difficulty": "hard",
|
|
477
|
+
"name": "py_string_sculptor",
|
|
478
|
+
"file": "py_string_sculptor.py",
|
|
479
|
+
"description": {
|
|
480
|
+
"en": "Write a function that transforms a string by alternating the case of\nalphabetic characters only.\nNon-alphabetic characters remain unchanged and are NOT counted in the\nalternation index.\nThe first alphabetic character should be lowercase, the second uppercase, etc.\nSpaces reset the alternation (next alpha after a space is lowercase again).",
|
|
481
|
+
"de": "Schreibe eine Funktion, die einen String transformiert, indem nur alphabetische Zeichen abwechselnd groß/klein geschrieben werden. Nicht-alphabetische Zeichen bleiben unverändert und zählen nicht für die Abwechslung. Das erste alphabetische Zeichen soll klein, das zweite groß, das dritte klein usw. sein.",
|
|
482
|
+
"fr": "Écris une fonction qui transforme une chaîne en alternant la casse des seuls caractères alphabétiques. Les autres caractères restent inchangés et ne comptent pas pour l’alternance. Le premier caractère alphabétique doit être en minuscule, le second en majuscule, le troisième en minuscule, etc.",
|
|
483
|
+
"es": "Escribe una funciГіn que transforme una cadena alternando mayГєsculas y minГєsculas solo en caracteres alfabГ©ticos. Los demГЎs permanecen igual y no cuentan para la alternancia. El primer carГЎcter alfabГ©tico debe ser minГєscula, el segundo mayГєscula, el tercero minГєscula, y asГ sucesivamente.",
|
|
484
|
+
"it": "Scrivi una funzione che trasformi una stringa alternando maiuscole e minuscole solo per i caratteri alfabetici. Gli altri restano invariati e non contano per l’alternanza. Il primo carattere alfabetico deve essere minuscolo, il secondo maiuscolo, il terzo minuscolo, e così via."
|
|
485
|
+
},
|
|
486
|
+
"signature": "def string_sculptor(text: str) -> str:",
|
|
487
|
+
"examples": [
|
|
488
|
+
{
|
|
489
|
+
"input": "string_sculptor(\"hello\")",
|
|
490
|
+
"output": "\"hElLo\""
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
"input": "string_sculptor(\"Hello World\")",
|
|
494
|
+
"output": "\"hElLo wOrLd\""
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
"input": "string_sculptor(\"abc123def\")",
|
|
498
|
+
"output": "\"aBc123DeF\""
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
"input": "string_sculptor(\"Python3.9!\")",
|
|
502
|
+
"output": "\"pYtHoN3.9!\""
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
"input": "string_sculptor(\"\")",
|
|
506
|
+
"output": "\"\""
|
|
507
|
+
}
|
|
508
|
+
]
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
"id": 13,
|
|
512
|
+
"level": 5,
|
|
513
|
+
"difficulty": "hard",
|
|
514
|
+
"name": "py_twist_sequence",
|
|
515
|
+
"file": "py_twist_sequence.py",
|
|
516
|
+
"description": {
|
|
517
|
+
"en": "Write a function that rotates an array to the right by k positions.\nRotating right by k means the last k elements move to the front.",
|
|
518
|
+
"de": "Write a function that rotates an array to the right by k positions, rotating right by k means the last k elements move to the front.",
|
|
519
|
+
"fr": "Write a function that rotates an array to the right by k positions, rotating right by k means the last k elements move to the front.",
|
|
520
|
+
"es": "Write a function that rotates an array to the right by k positions, rotating right by k means the last k elements move to the front.",
|
|
521
|
+
"it": "Write a function that rotates an array to the right by k positions, rotating right by k means the last k elements move to the front."
|
|
522
|
+
},
|
|
523
|
+
"signature": "def twist_sequence(arr: list[int], k: int) -> list[int]:",
|
|
524
|
+
"examples": [
|
|
525
|
+
{
|
|
526
|
+
"input": "twist_sequence([1,2,3,4,5], 2)",
|
|
527
|
+
"output": "[4,5,1,2,3]"
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
"input": "twist_sequence([1,2,3], 1)",
|
|
531
|
+
"output": "[3,1,2]"
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
"input": "twist_sequence([1,2,3,4], 0)",
|
|
535
|
+
"output": "[1,2,3,4]"
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
"input": "twist_sequence([1,2,3], 5)",
|
|
539
|
+
"output": "[2,3,1]"
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
"input": "twist_sequence([], 3)",
|
|
543
|
+
"output": "[]"
|
|
544
|
+
}
|
|
545
|
+
]
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
"id": 14,
|
|
549
|
+
"level": 6,
|
|
550
|
+
"difficulty": "hard",
|
|
551
|
+
"name": "py_whisper_cipher",
|
|
552
|
+
"file": "py_whisper_cipher.py",
|
|
553
|
+
"description": {
|
|
554
|
+
"en": "Write a function that creates a Caesar cipher by shifting letters in a\nstring by a given amount.\nNon-alphabetic characters should remain unchanged.\nThe shift can be negative (shift left).",
|
|
555
|
+
"de": "Schreibe eine Funktion, die eine einfache Verschlüsselung erzeugt, indem Buchstaben in einem String um einen gegebenen Betrag verschoben werden. Nicht-alphabetische Zeichen bleiben unverändert.",
|
|
556
|
+
"fr": "Écris une fonction qui réalise un chiffrement simple en décalant les lettres d’une chaîne d’un montant donné. Les caractères non alphabétiques restent inchangés.",
|
|
557
|
+
"es": "Escribe una funciГіn que cree un cifrado simple desplazando las letras de una cadena una cantidad dada. Los caracteres no alfabГ©ticos deben permanecer iguales.",
|
|
558
|
+
"it": "Scrivi una funzione che crei una cifratura semplice spostando le lettere di una stringa di una quantitГ data. I caratteri non alfabetici restano invariati."
|
|
559
|
+
},
|
|
560
|
+
"signature": "def whisper_cipher(text: str, shift: int) -> str:",
|
|
561
|
+
"examples": [
|
|
562
|
+
{
|
|
563
|
+
"input": "whisper_cipher(\"hello\", 3)",
|
|
564
|
+
"output": "\"khoor\""
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
"input": "whisper_cipher(\"Hello World!\", 1)",
|
|
568
|
+
"output": "\"Ifmmp Xpsme!\""
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
"input": "whisper_cipher(\"xyz\", 3)",
|
|
572
|
+
"output": "\"abc\""
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
"input": "whisper_cipher(\"ABC123def\", 5)",
|
|
576
|
+
"output": "\"FGH123ijk\""
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
"input": "whisper_cipher(\"\", 10)",
|
|
580
|
+
"output": "\"\""
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
"input": "whisper_cipher(\"abc\", -3)",
|
|
584
|
+
"output": "\"xyz\""
|
|
585
|
+
}
|
|
586
|
+
]
|
|
587
|
+
}
|
|
588
|
+
]
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
from .models import Task
|
|
2
|
+
from .task_generator import TaskManager
|
|
3
|
+
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.panel import Panel
|
|
6
|
+
|
|
7
|
+
from rich.prompt import Prompt
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from rich.progress import Progress, BarColumn, TextColumn
|
|
11
|
+
from rich.align import Align
|
|
12
|
+
|
|
13
|
+
from rich import box
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def choose_mode() -> bool:
|
|
19
|
+
console.clear()
|
|
20
|
+
console.print(
|
|
21
|
+
Panel.fit(
|
|
22
|
+
"[bold cyan]Real mode[/bold cyan] [dim](1)[/dim]\n"
|
|
23
|
+
"[bold yellow]Practice mode[/bold yellow] [dim](2)[/dim]",
|
|
24
|
+
title="[bold]Choose your Exam mode[/bold]",
|
|
25
|
+
border_style="cyan",
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
mode = Prompt.ask(
|
|
29
|
+
"Your answer", choices=["1", "2"], show_choices=False
|
|
30
|
+
)
|
|
31
|
+
return mode == "1"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def points_bar(points: int, total: int = 100) -> Progress:
|
|
35
|
+
progress = Progress(
|
|
36
|
+
TextColumn("[bold]Progress[/bold]"),
|
|
37
|
+
BarColumn(bar_width=40),
|
|
38
|
+
TextColumn(f"[bold green]{points}[/bold green]/{total}"),
|
|
39
|
+
)
|
|
40
|
+
progress.add_task("points", total=total, completed=points)
|
|
41
|
+
return progress
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def real_mode_screen(manager: TaskManager, task: Task) -> None:
|
|
45
|
+
upld_path = manager.path / "rendu" / task.name / task.file
|
|
46
|
+
|
|
47
|
+
console.print(
|
|
48
|
+
Panel.fit(
|
|
49
|
+
"[bold]Welcome to the exam[/bold]",
|
|
50
|
+
border_style="cyan",
|
|
51
|
+
box=box.HEAVY,
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
console.rule(f"[bold cyan]{task.name}[/bold cyan]", style="cyan")
|
|
55
|
+
console.print(points_bar(manager.points))
|
|
56
|
+
|
|
57
|
+
info = Table.grid(padding=(0, 1))
|
|
58
|
+
info.add_column(style="bold")
|
|
59
|
+
info.add_column()
|
|
60
|
+
info.add_row("Level:", str(manager.current_level))
|
|
61
|
+
info.add_row("Upload to:", f"[italic]{upld_path}[/italic]")
|
|
62
|
+
console.print(info)
|
|
63
|
+
|
|
64
|
+
commands = Table(
|
|
65
|
+
title="Commands",
|
|
66
|
+
box=box.SIMPLE,
|
|
67
|
+
show_header=False,
|
|
68
|
+
title_style="bold magenta",
|
|
69
|
+
)
|
|
70
|
+
commands.add_column(style="bold green")
|
|
71
|
+
commands.add_column(style="dim")
|
|
72
|
+
commands.add_row("grademe", "check the answer")
|
|
73
|
+
commands.add_row("status", "show this message again")
|
|
74
|
+
commands.add_row("finish", "finish the exam")
|
|
75
|
+
console.print(commands)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def grademe(manager: TaskManager, task: Task) -> Task:
|
|
79
|
+
check = manager.check_task()
|
|
80
|
+
|
|
81
|
+
if check:
|
|
82
|
+
manager.points += 16
|
|
83
|
+
manager.current_level += 1
|
|
84
|
+
|
|
85
|
+
if manager.points >= 100:
|
|
86
|
+
console.print(
|
|
87
|
+
Panel.fit(
|
|
88
|
+
Align.center(
|
|
89
|
+
f"[bold green]You passed the exam"
|
|
90
|
+
f", congratulations![/bold green]\n"
|
|
91
|
+
f"[bold]{manager.points}/100[/bold]"
|
|
92
|
+
),
|
|
93
|
+
border_style="green",
|
|
94
|
+
box=box.DOUBLE,
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
raise SystemExit(0)
|
|
98
|
+
|
|
99
|
+
console.print(
|
|
100
|
+
Panel(
|
|
101
|
+
f"[bold green]Task passed +16 points[/bold green] "
|
|
102
|
+
f"({manager.points}/100)",
|
|
103
|
+
border_style="green",
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
next_task = manager.get_next_task()
|
|
108
|
+
if next_task is None:
|
|
109
|
+
console.print(
|
|
110
|
+
Panel.fit(
|
|
111
|
+
Align.center(
|
|
112
|
+
"[bold yellow]No more tasks left.[/bold yellow]\n"
|
|
113
|
+
f"[bold]Final score: {manager.points}/100[/bold]"
|
|
114
|
+
),
|
|
115
|
+
border_style="yellow",
|
|
116
|
+
box=box.DOUBLE,
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
raise SystemExit(0)
|
|
120
|
+
|
|
121
|
+
return next_task
|
|
122
|
+
else:
|
|
123
|
+
console.print(
|
|
124
|
+
Panel(
|
|
125
|
+
"[bold red]Failed.[/bold red] You can retry",
|
|
126
|
+
border_style="red",
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
return task
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def real_mode() -> None:
|
|
133
|
+
manager = TaskManager()
|
|
134
|
+
task = manager.get_next_task()
|
|
135
|
+
|
|
136
|
+
if task is None:
|
|
137
|
+
console.clear()
|
|
138
|
+
console.print(
|
|
139
|
+
Panel(
|
|
140
|
+
"[bold red]No tasks available to start the exam.[/bold red]",
|
|
141
|
+
border_style="red",
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
raise SystemExit(1)
|
|
145
|
+
|
|
146
|
+
console.clear()
|
|
147
|
+
manager.create_task_files()
|
|
148
|
+
real_mode_screen(manager, task)
|
|
149
|
+
|
|
150
|
+
while True:
|
|
151
|
+
user_input = Prompt.ask(
|
|
152
|
+
"[bold cyan]exam42[/bold cyan]",
|
|
153
|
+
).strip()
|
|
154
|
+
|
|
155
|
+
match user_input:
|
|
156
|
+
case "status":
|
|
157
|
+
console.clear()
|
|
158
|
+
real_mode_screen(manager, task)
|
|
159
|
+
case "grademe":
|
|
160
|
+
console.clear()
|
|
161
|
+
real_mode_screen(manager, task)
|
|
162
|
+
task = grademe(manager, task)
|
|
163
|
+
manager.create_task_files()
|
|
164
|
+
case "finish":
|
|
165
|
+
console.clear()
|
|
166
|
+
console.print("[yellow]Exam finished by user.[/yellow]")
|
|
167
|
+
raise SystemExit(0)
|
|
168
|
+
case _:
|
|
169
|
+
console.clear()
|
|
170
|
+
real_mode_screen(manager, task)
|
|
171
|
+
console.print(f"[red]Unknown command:[/red] {user_input!r}")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def task_list_table(manager: TaskManager) -> Table:
|
|
175
|
+
table = Table(title="Available tasks", header_style="bold magenta")
|
|
176
|
+
table.add_column("#", justify="right")
|
|
177
|
+
table.add_column("Name")
|
|
178
|
+
table.add_column("Level", justify="center")
|
|
179
|
+
|
|
180
|
+
for idx, task in enumerate(manager.data, start=1):
|
|
181
|
+
table.add_row(str(idx), task.name, str(task.level))
|
|
182
|
+
|
|
183
|
+
return table
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def practice_mode_screen(manager: TaskManager, task: Task) -> None:
|
|
187
|
+
upld_path = manager.path / "rendu" / task.name / task.file
|
|
188
|
+
|
|
189
|
+
console.print(
|
|
190
|
+
Panel.fit(
|
|
191
|
+
"[bold]Welcome to practice mode[/bold]",
|
|
192
|
+
border_style="yellow",
|
|
193
|
+
box=box.HEAVY,
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
console.rule(f"[bold yellow]{task.name}[/bold yellow]", style="yellow")
|
|
197
|
+
|
|
198
|
+
info = Table.grid(padding=(0, 1))
|
|
199
|
+
info.add_column(style="bold")
|
|
200
|
+
info.add_column()
|
|
201
|
+
info.add_row("Level:", str(task.level))
|
|
202
|
+
info.add_row("Upload to:", f"[italic]{upld_path}[/italic]")
|
|
203
|
+
console.print(info)
|
|
204
|
+
|
|
205
|
+
commands = Table(
|
|
206
|
+
title="Commands",
|
|
207
|
+
box=box.SIMPLE,
|
|
208
|
+
show_header=False,
|
|
209
|
+
title_style="bold magenta",
|
|
210
|
+
)
|
|
211
|
+
commands.add_column(style="bold yellow")
|
|
212
|
+
commands.add_column(style="dim")
|
|
213
|
+
commands.add_row("grademe", "check the answer")
|
|
214
|
+
commands.add_row("list", "show all available tasks")
|
|
215
|
+
commands.add_row("choose", "pick a task by number")
|
|
216
|
+
commands.add_row("skip", "move to the next task in order")
|
|
217
|
+
commands.add_row("status", "show this message again")
|
|
218
|
+
commands.add_row("finish", "finish practice")
|
|
219
|
+
console.print(commands)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def choose_task(manager: TaskManager) -> Task:
|
|
223
|
+
console.print(task_list_table(manager))
|
|
224
|
+
|
|
225
|
+
numbers = [str(i) for i in range(1, len(manager.data) + 1)]
|
|
226
|
+
choice = Prompt.ask(
|
|
227
|
+
"Pick a task number", choices=numbers, show_choices=False
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
task = manager.data[int(choice) - 1]
|
|
231
|
+
manager.set_current_task(task)
|
|
232
|
+
return task
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def practice_mode() -> None:
|
|
236
|
+
console.clear()
|
|
237
|
+
console.print(
|
|
238
|
+
Panel.fit(
|
|
239
|
+
"[bold]Welcome to practice mode[/bold]",
|
|
240
|
+
border_style="yellow",
|
|
241
|
+
box=box.HEAVY,
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
manager = TaskManager(real_mode=False)
|
|
246
|
+
|
|
247
|
+
console.print("[dim]Pick a task to start with.[/dim]")
|
|
248
|
+
task = choose_task(manager)
|
|
249
|
+
|
|
250
|
+
console.clear()
|
|
251
|
+
manager.create_task_files()
|
|
252
|
+
practice_mode_screen(manager, task)
|
|
253
|
+
|
|
254
|
+
while True:
|
|
255
|
+
user_input = Prompt.ask(
|
|
256
|
+
"[bold yellow]practice42[/bold yellow]",
|
|
257
|
+
).strip()
|
|
258
|
+
|
|
259
|
+
match user_input:
|
|
260
|
+
case "status":
|
|
261
|
+
console.clear()
|
|
262
|
+
practice_mode_screen(manager, task)
|
|
263
|
+
case "list":
|
|
264
|
+
console.clear()
|
|
265
|
+
practice_mode_screen(manager, task)
|
|
266
|
+
console.print(task_list_table(manager))
|
|
267
|
+
case "choose":
|
|
268
|
+
console.clear()
|
|
269
|
+
practice_mode_screen(manager, task)
|
|
270
|
+
task = choose_task(manager)
|
|
271
|
+
manager.create_task_files()
|
|
272
|
+
console.clear()
|
|
273
|
+
practice_mode_screen(manager, task)
|
|
274
|
+
case "skip":
|
|
275
|
+
next_task = manager.get_next_task()
|
|
276
|
+
console.clear()
|
|
277
|
+
if next_task is None:
|
|
278
|
+
practice_mode_screen(manager, task)
|
|
279
|
+
console.print(
|
|
280
|
+
Panel(
|
|
281
|
+
"[yellow]No more tasks after this one.[/yellow]",
|
|
282
|
+
border_style="yellow",
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
else:
|
|
286
|
+
task = next_task
|
|
287
|
+
manager.create_task_files()
|
|
288
|
+
practice_mode_screen(manager, task)
|
|
289
|
+
case "grademe":
|
|
290
|
+
console.clear()
|
|
291
|
+
practice_mode_screen(manager, task)
|
|
292
|
+
check = manager.check_task()
|
|
293
|
+
if check:
|
|
294
|
+
console.print(
|
|
295
|
+
Panel(
|
|
296
|
+
"[bold green]Correct! Well done.[/bold green]",
|
|
297
|
+
border_style="green",
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
else:
|
|
301
|
+
console.print(
|
|
302
|
+
Panel(
|
|
303
|
+
"[bold red]Failed.[/bold red] You can retry",
|
|
304
|
+
border_style="red",
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
case "finish":
|
|
308
|
+
console.clear()
|
|
309
|
+
console.print("[yellow]Practice finished by user.[/yellow]")
|
|
310
|
+
raise SystemExit(0)
|
|
311
|
+
case _:
|
|
312
|
+
console.clear()
|
|
313
|
+
practice_mode_screen(manager, task)
|
|
314
|
+
console.print(f"[red]Unknown command:[/red] {user_input!r}")
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def cli() -> None:
|
|
318
|
+
real = choose_mode()
|
|
319
|
+
if real:
|
|
320
|
+
real_mode()
|
|
321
|
+
else:
|
|
322
|
+
practice_mode()
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
if __name__ == "__main__":
|
|
326
|
+
cli()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Description(BaseModel):
|
|
5
|
+
en: str
|
|
6
|
+
de: str
|
|
7
|
+
fr: str
|
|
8
|
+
es: str
|
|
9
|
+
it: str
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Example(BaseModel):
|
|
13
|
+
input: str
|
|
14
|
+
output: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Task(BaseModel):
|
|
18
|
+
id: int
|
|
19
|
+
level: int
|
|
20
|
+
difficulty: str
|
|
21
|
+
name: str
|
|
22
|
+
file: str
|
|
23
|
+
description: Description
|
|
24
|
+
signature: str
|
|
25
|
+
examples: list[Example]
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import random
|
|
5
|
+
from .models import Task
|
|
6
|
+
|
|
7
|
+
from pydantic import TypeAdapter
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import importlib.util
|
|
11
|
+
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
|
|
17
|
+
console = Console()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_tasks() -> list[Task]:
|
|
21
|
+
adapter = TypeAdapter(list[Task])
|
|
22
|
+
with open("data.json", "r", encoding="utf-8") as f:
|
|
23
|
+
return adapter.validate_python(json.load(f))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TaskManager:
|
|
27
|
+
def __init__(self, real_mode: bool = True):
|
|
28
|
+
self.data = sorted(get_tasks(), key=lambda a: a.level)
|
|
29
|
+
self.real_mode = real_mode
|
|
30
|
+
self.points = 0
|
|
31
|
+
self.current_level = 1
|
|
32
|
+
self.current_task: int | None = None
|
|
33
|
+
self.path = self._create_folder()
|
|
34
|
+
|
|
35
|
+
def _create_folder(
|
|
36
|
+
self, path_str: str = "exam42", fallback: int = 1
|
|
37
|
+
) -> Path:
|
|
38
|
+
path = Path(f"./{path_str}")
|
|
39
|
+
if path.exists():
|
|
40
|
+
return self._create_folder(
|
|
41
|
+
"exam42" + "-" + str(fallback), fallback=fallback + 1
|
|
42
|
+
)
|
|
43
|
+
path.mkdir(parents=True)
|
|
44
|
+
(path / "subjects").mkdir(parents=True)
|
|
45
|
+
(path / "rendu").mkdir(parents=True)
|
|
46
|
+
return path
|
|
47
|
+
|
|
48
|
+
def get_next_task(self) -> Task:
|
|
49
|
+
if self.real_mode:
|
|
50
|
+
return self._get_task_by_real_mode()
|
|
51
|
+
else:
|
|
52
|
+
return self._get_next_task()
|
|
53
|
+
|
|
54
|
+
def get_data_by_level(self, level) -> list[Task]:
|
|
55
|
+
data = []
|
|
56
|
+
for d in self.data:
|
|
57
|
+
if d.level == level:
|
|
58
|
+
data.append(d)
|
|
59
|
+
return data
|
|
60
|
+
|
|
61
|
+
def get_task_by_id(self, id) -> Task | None:
|
|
62
|
+
for t in self.data:
|
|
63
|
+
if t.id == id:
|
|
64
|
+
return t
|
|
65
|
+
|
|
66
|
+
def set_current_task(self, task: Task) -> None:
|
|
67
|
+
self.current_task = task.id
|
|
68
|
+
|
|
69
|
+
def _get_task_by_real_mode(self) -> Task | None:
|
|
70
|
+
data = self.get_data_by_level(self.current_level)
|
|
71
|
+
if not data:
|
|
72
|
+
return None
|
|
73
|
+
task = random.choice(data)
|
|
74
|
+
self.current_task = task.id
|
|
75
|
+
return task
|
|
76
|
+
|
|
77
|
+
def _get_next_task(self) -> Task:
|
|
78
|
+
if not self.current_task:
|
|
79
|
+
self.current_task = self.data[0].id
|
|
80
|
+
return self.data[0]
|
|
81
|
+
|
|
82
|
+
task = self.get_task_by_id(self.current_task)
|
|
83
|
+
next_task_index = self.data.index(task) + 1
|
|
84
|
+
|
|
85
|
+
if next_task_index < len(self.data):
|
|
86
|
+
next_task = self.data[next_task_index]
|
|
87
|
+
self.current_task = next_task.id
|
|
88
|
+
return next_task
|
|
89
|
+
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
def create_task_files(self):
|
|
93
|
+
task = self.get_task_by_id(self.current_task)
|
|
94
|
+
if not task:
|
|
95
|
+
raise FileExistsError()
|
|
96
|
+
|
|
97
|
+
path_subject = self.path / "subjects" / task.name
|
|
98
|
+
if not path_subject.exists():
|
|
99
|
+
path_subject.mkdir(parents=True)
|
|
100
|
+
|
|
101
|
+
path_rendu = self.path / "rendu" / task.name
|
|
102
|
+
if not path_rendu.exists():
|
|
103
|
+
path_rendu.mkdir(parents=True)
|
|
104
|
+
|
|
105
|
+
text_examples = "\n\n"
|
|
106
|
+
for e in task.examples:
|
|
107
|
+
text_examples += e.input + "\n"
|
|
108
|
+
text_examples += e.output + "\n\n"
|
|
109
|
+
|
|
110
|
+
for lang, text in task.description.model_dump().items():
|
|
111
|
+
file_name = f"{task.name}.{lang}.txt"
|
|
112
|
+
file_path = path_subject / file_name
|
|
113
|
+
|
|
114
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
115
|
+
f.write(
|
|
116
|
+
text + "\n\n" + task.signature + text_examples
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def check_task(self) -> bool:
|
|
120
|
+
task = self.get_task_by_id(self.current_task)
|
|
121
|
+
|
|
122
|
+
file_path = self.path / "rendu" / task.name / task.file
|
|
123
|
+
|
|
124
|
+
console.rule(f"[bold cyan]Checking: {task.name}[/bold cyan]")
|
|
125
|
+
|
|
126
|
+
if not file_path.is_file():
|
|
127
|
+
console.print(
|
|
128
|
+
Panel(
|
|
129
|
+
f"[red]File not found:[/red] {file_path}",
|
|
130
|
+
title="[bold red]Error[/bold red]",
|
|
131
|
+
border_style="red",
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
module_name = task.file[:-3]
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
spec = importlib.util.spec_from_file_location(
|
|
140
|
+
module_name, file_path
|
|
141
|
+
)
|
|
142
|
+
module = importlib.util.module_from_spec(spec)
|
|
143
|
+
spec.loader.exec_module(module)
|
|
144
|
+
except Exception as exc:
|
|
145
|
+
console.print(
|
|
146
|
+
Panel(
|
|
147
|
+
f"[red]Failed to import module:[/red]\n{exc}",
|
|
148
|
+
title="[bold red]Import error[/bold red]",
|
|
149
|
+
border_style="red",
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
table = Table(show_header=True, header_style="bold magenta")
|
|
155
|
+
table.add_column("Test", justify="right")
|
|
156
|
+
table.add_column("Input", overflow="fold")
|
|
157
|
+
table.add_column("Expected", overflow="fold")
|
|
158
|
+
table.add_column("Got", overflow="fold")
|
|
159
|
+
table.add_column("Status", justify="center")
|
|
160
|
+
|
|
161
|
+
all_passed = True
|
|
162
|
+
|
|
163
|
+
for idx, example in enumerate(task.examples, start=1):
|
|
164
|
+
try:
|
|
165
|
+
result = eval(example.input, vars(module))
|
|
166
|
+
expected_result = ast.literal_eval(example.output)
|
|
167
|
+
|
|
168
|
+
if result != expected_result:
|
|
169
|
+
all_passed = False
|
|
170
|
+
table.add_row(
|
|
171
|
+
str(idx),
|
|
172
|
+
example.input,
|
|
173
|
+
repr(expected_result),
|
|
174
|
+
f"[red]{result!r}[/red]",
|
|
175
|
+
"[bold red]FAIL[/bold red]",
|
|
176
|
+
)
|
|
177
|
+
else:
|
|
178
|
+
table.add_row(
|
|
179
|
+
str(idx),
|
|
180
|
+
example.input,
|
|
181
|
+
repr(expected_result),
|
|
182
|
+
f"[green]{result!r}[/green]",
|
|
183
|
+
"[bold green]OK[/bold green]",
|
|
184
|
+
)
|
|
185
|
+
except Exception as exc:
|
|
186
|
+
all_passed = False
|
|
187
|
+
table.add_row(
|
|
188
|
+
str(idx),
|
|
189
|
+
example.input,
|
|
190
|
+
example.output,
|
|
191
|
+
f"[red]Exception: {exc}[/red]",
|
|
192
|
+
"[bold red]ERROR[/bold red]",
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
console.print(table)
|
|
196
|
+
|
|
197
|
+
if all_passed:
|
|
198
|
+
console.print(
|
|
199
|
+
Panel(
|
|
200
|
+
f"[bold green]All tests passed![/bold green] "
|
|
201
|
+
f"({len(task.examples)}/{len(task.examples)})",
|
|
202
|
+
border_style="green",
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
else:
|
|
206
|
+
console.print(
|
|
207
|
+
Panel(
|
|
208
|
+
"[bold red]Some tests failed.[/bold red]",
|
|
209
|
+
border_style="red",
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
return all_passed
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: examshell-python
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Interactive 42-style exam practice shell for Python exercises
|
|
5
|
+
Author: Denis
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: pydantic>=2.0
|
|
10
|
+
Requires-Dist: rich>=13.0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/examshell/__init__.py
|
|
4
|
+
src/examshell/__main__.py
|
|
5
|
+
src/examshell/config.py
|
|
6
|
+
src/examshell/main.py
|
|
7
|
+
src/examshell/models.py
|
|
8
|
+
src/examshell/task_generator.py
|
|
9
|
+
src/examshell/data/data.json
|
|
10
|
+
src/examshell_python.egg-info/PKG-INFO
|
|
11
|
+
src/examshell_python.egg-info/SOURCES.txt
|
|
12
|
+
src/examshell_python.egg-info/dependency_links.txt
|
|
13
|
+
src/examshell_python.egg-info/entry_points.txt
|
|
14
|
+
src/examshell_python.egg-info/requires.txt
|
|
15
|
+
src/examshell_python.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
examshell
|