pythonlings 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonlings/__init__.py +0 -0
- pythonlings/__main__.py +4 -0
- pythonlings/app.py +84 -0
- pythonlings/cli.py +318 -0
- pythonlings/core/__init__.py +0 -0
- pythonlings/core/curriculum.py +106 -0
- pythonlings/core/docs.py +68 -0
- pythonlings/core/exercise.py +33 -0
- pythonlings/core/manifest.py +110 -0
- pythonlings/core/reset.py +43 -0
- pythonlings/core/runner.py +114 -0
- pythonlings/core/solutions.py +17 -0
- pythonlings/core/state.py +86 -0
- pythonlings/curriculum/checks/async/async1.py +4 -0
- pythonlings/curriculum/checks/async/async10.py +4 -0
- pythonlings/curriculum/checks/async/async2.py +4 -0
- pythonlings/curriculum/checks/async/async3.py +4 -0
- pythonlings/curriculum/checks/async/async4.py +4 -0
- pythonlings/curriculum/checks/async/async5.py +4 -0
- pythonlings/curriculum/checks/async/async6.py +4 -0
- pythonlings/curriculum/checks/async/async7.py +4 -0
- pythonlings/curriculum/checks/async/async8.py +2 -0
- pythonlings/curriculum/checks/async/async9.py +4 -0
- pythonlings/curriculum/checks/classes/classes1.py +3 -0
- pythonlings/curriculum/checks/classes/classes10.py +5 -0
- pythonlings/curriculum/checks/classes/classes11.py +5 -0
- pythonlings/curriculum/checks/classes/classes12.py +16 -0
- pythonlings/curriculum/checks/classes/classes2.py +3 -0
- pythonlings/curriculum/checks/classes/classes3.py +4 -0
- pythonlings/curriculum/checks/classes/classes4.py +4 -0
- pythonlings/curriculum/checks/classes/classes5.py +5 -0
- pythonlings/curriculum/checks/classes/classes6.py +4 -0
- pythonlings/curriculum/checks/classes/classes7.py +3 -0
- pythonlings/curriculum/checks/classes/classes8.py +3 -0
- pythonlings/curriculum/checks/classes/classes9.py +4 -0
- pythonlings/curriculum/checks/collections/collections1.py +4 -0
- pythonlings/curriculum/checks/collections/collections10.py +15 -0
- pythonlings/curriculum/checks/collections/collections2.py +4 -0
- pythonlings/curriculum/checks/collections/collections3.py +5 -0
- pythonlings/curriculum/checks/collections/collections4.py +4 -0
- pythonlings/curriculum/checks/collections/collections5.py +5 -0
- pythonlings/curriculum/checks/collections/collections6.py +6 -0
- pythonlings/curriculum/checks/collections/collections7.py +4 -0
- pythonlings/curriculum/checks/collections/collections8.py +5 -0
- pythonlings/curriculum/checks/collections/collections9.py +9 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions1.py +3 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions10.py +5 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions2.py +3 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions3.py +3 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions4.py +6 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions5.py +3 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions6.py +3 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions7.py +4 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions8.py +5 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions9.py +3 -0
- pythonlings/curriculum/checks/conditionals/conditionals1.py +2 -0
- pythonlings/curriculum/checks/conditionals/conditionals10.py +10 -0
- pythonlings/curriculum/checks/conditionals/conditionals2.py +5 -0
- pythonlings/curriculum/checks/conditionals/conditionals3.py +9 -0
- pythonlings/curriculum/checks/conditionals/conditionals4.py +7 -0
- pythonlings/curriculum/checks/conditionals/conditionals5.py +9 -0
- pythonlings/curriculum/checks/conditionals/conditionals6.py +7 -0
- pythonlings/curriculum/checks/conditionals/conditionals7.py +7 -0
- pythonlings/curriculum/checks/conditionals/conditionals8.py +9 -0
- pythonlings/curriculum/checks/conditionals/conditionals9.py +7 -0
- pythonlings/curriculum/checks/context_managers/context_managers1.py +15 -0
- pythonlings/curriculum/checks/context_managers/context_managers2.py +10 -0
- pythonlings/curriculum/checks/context_managers/context_managers3.py +2 -0
- pythonlings/curriculum/checks/context_managers/context_managers4.py +3 -0
- pythonlings/curriculum/checks/context_managers/context_managers5.py +5 -0
- pythonlings/curriculum/checks/context_managers/context_managers6.py +4 -0
- pythonlings/curriculum/checks/context_managers/context_managers7.py +16 -0
- pythonlings/curriculum/checks/context_managers/context_managers8.py +15 -0
- pythonlings/curriculum/checks/dataclasses/dataclasses1.py +3 -0
- pythonlings/curriculum/checks/dataclasses/dataclasses2.py +5 -0
- pythonlings/curriculum/checks/dataclasses/dataclasses3.py +4 -0
- pythonlings/curriculum/checks/dataclasses/dataclasses4.py +3 -0
- pythonlings/curriculum/checks/dataclasses/dataclasses5.py +9 -0
- pythonlings/curriculum/checks/dataclasses/dataclasses6.py +6 -0
- pythonlings/curriculum/checks/dataclasses/dataclasses7.py +6 -0
- pythonlings/curriculum/checks/dataclasses/dataclasses8.py +5 -0
- pythonlings/curriculum/checks/datetime/datetime1.py +4 -0
- pythonlings/curriculum/checks/datetime/datetime2.py +4 -0
- pythonlings/curriculum/checks/datetime/datetime3.py +6 -0
- pythonlings/curriculum/checks/datetime/datetime4.py +2 -0
- pythonlings/curriculum/checks/datetime/datetime5.py +2 -0
- pythonlings/curriculum/checks/datetime/datetime6.py +2 -0
- pythonlings/curriculum/checks/datetime/datetime7.py +2 -0
- pythonlings/curriculum/checks/datetime/datetime8.py +5 -0
- pythonlings/curriculum/checks/decorators/decorators1.py +8 -0
- pythonlings/curriculum/checks/decorators/decorators10.py +12 -0
- pythonlings/curriculum/checks/decorators/decorators2.py +5 -0
- pythonlings/curriculum/checks/decorators/decorators3.py +4 -0
- pythonlings/curriculum/checks/decorators/decorators4.py +7 -0
- pythonlings/curriculum/checks/decorators/decorators5.py +5 -0
- pythonlings/curriculum/checks/decorators/decorators6.py +8 -0
- pythonlings/curriculum/checks/decorators/decorators7.py +12 -0
- pythonlings/curriculum/checks/decorators/decorators8.py +20 -0
- pythonlings/curriculum/checks/decorators/decorators9.py +4 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries1.py +3 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries10.py +5 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries2.py +2 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries3.py +5 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries4.py +5 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries5.py +11 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries6.py +4 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries7.py +5 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries8.py +4 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries9.py +4 -0
- pythonlings/curriculum/checks/enums/enums1.py +4 -0
- pythonlings/curriculum/checks/enums/enums2.py +3 -0
- pythonlings/curriculum/checks/enums/enums3.py +3 -0
- pythonlings/curriculum/checks/enums/enums4.py +2 -0
- pythonlings/curriculum/checks/enums/enums5.py +4 -0
- pythonlings/curriculum/checks/enums/enums6.py +3 -0
- pythonlings/curriculum/checks/exceptions/exceptions1.py +4 -0
- pythonlings/curriculum/checks/exceptions/exceptions10.py +39 -0
- pythonlings/curriculum/checks/exceptions/exceptions2.py +5 -0
- pythonlings/curriculum/checks/exceptions/exceptions3.py +8 -0
- pythonlings/curriculum/checks/exceptions/exceptions4.py +5 -0
- pythonlings/curriculum/checks/exceptions/exceptions5.py +10 -0
- pythonlings/curriculum/checks/exceptions/exceptions6.py +17 -0
- pythonlings/curriculum/checks/exceptions/exceptions7.py +15 -0
- pythonlings/curriculum/checks/exceptions/exceptions8.py +6 -0
- pythonlings/curriculum/checks/exceptions/exceptions9.py +16 -0
- pythonlings/curriculum/checks/file_io/file_io1.py +15 -0
- pythonlings/curriculum/checks/file_io/file_io10.py +16 -0
- pythonlings/curriculum/checks/file_io/file_io2.py +10 -0
- pythonlings/curriculum/checks/file_io/file_io3.py +15 -0
- pythonlings/curriculum/checks/file_io/file_io4.py +14 -0
- pythonlings/curriculum/checks/file_io/file_io5.py +15 -0
- pythonlings/curriculum/checks/file_io/file_io6.py +10 -0
- pythonlings/curriculum/checks/file_io/file_io7.py +15 -0
- pythonlings/curriculum/checks/file_io/file_io8.py +12 -0
- pythonlings/curriculum/checks/file_io/file_io9.py +10 -0
- pythonlings/curriculum/checks/functional/functional1.py +4 -0
- pythonlings/curriculum/checks/functional/functional10.py +7 -0
- pythonlings/curriculum/checks/functional/functional2.py +5 -0
- pythonlings/curriculum/checks/functional/functional3.py +3 -0
- pythonlings/curriculum/checks/functional/functional4.py +4 -0
- pythonlings/curriculum/checks/functional/functional5.py +6 -0
- pythonlings/curriculum/checks/functional/functional6.py +8 -0
- pythonlings/curriculum/checks/functional/functional7.py +8 -0
- pythonlings/curriculum/checks/functional/functional8.py +7 -0
- pythonlings/curriculum/checks/functional/functional9.py +8 -0
- pythonlings/curriculum/checks/functions/functions1.py +9 -0
- pythonlings/curriculum/checks/functions/functions10.py +12 -0
- pythonlings/curriculum/checks/functions/functions2.py +4 -0
- pythonlings/curriculum/checks/functions/functions3.py +6 -0
- pythonlings/curriculum/checks/functions/functions4.py +6 -0
- pythonlings/curriculum/checks/functions/functions5.py +6 -0
- pythonlings/curriculum/checks/functions/functions6.py +8 -0
- pythonlings/curriculum/checks/functions/functions7.py +7 -0
- pythonlings/curriculum/checks/functions/functions8.py +6 -0
- pythonlings/curriculum/checks/functions/functions9.py +7 -0
- pythonlings/curriculum/checks/generators/generators1.py +4 -0
- pythonlings/curriculum/checks/generators/generators10.py +7 -0
- pythonlings/curriculum/checks/generators/generators2.py +3 -0
- pythonlings/curriculum/checks/generators/generators3.py +4 -0
- pythonlings/curriculum/checks/generators/generators4.py +9 -0
- pythonlings/curriculum/checks/generators/generators5.py +10 -0
- pythonlings/curriculum/checks/generators/generators6.py +8 -0
- pythonlings/curriculum/checks/generators/generators7.py +11 -0
- pythonlings/curriculum/checks/generators/generators8.py +8 -0
- pythonlings/curriculum/checks/generators/generators9.py +13 -0
- pythonlings/curriculum/checks/itertools/itertools1.py +2 -0
- pythonlings/curriculum/checks/itertools/itertools2.py +2 -0
- pythonlings/curriculum/checks/itertools/itertools3.py +2 -0
- pythonlings/curriculum/checks/itertools/itertools4.py +2 -0
- pythonlings/curriculum/checks/itertools/itertools5.py +2 -0
- pythonlings/curriculum/checks/itertools/itertools6.py +2 -0
- pythonlings/curriculum/checks/itertools/itertools7.py +2 -0
- pythonlings/curriculum/checks/itertools/itertools8.py +3 -0
- pythonlings/curriculum/checks/json/json1.py +3 -0
- pythonlings/curriculum/checks/json/json2.py +2 -0
- pythonlings/curriculum/checks/json/json3.py +2 -0
- pythonlings/curriculum/checks/json/json4.py +2 -0
- pythonlings/curriculum/checks/json/json5.py +2 -0
- pythonlings/curriculum/checks/json/json6.py +2 -0
- pythonlings/curriculum/checks/json/json7.py +3 -0
- pythonlings/curriculum/checks/json/json8.py +4 -0
- pythonlings/curriculum/checks/lists/lists1.py +2 -0
- pythonlings/curriculum/checks/lists/lists10.py +5 -0
- pythonlings/curriculum/checks/lists/lists2.py +3 -0
- pythonlings/curriculum/checks/lists/lists3.py +4 -0
- pythonlings/curriculum/checks/lists/lists4.py +3 -0
- pythonlings/curriculum/checks/lists/lists5.py +4 -0
- pythonlings/curriculum/checks/lists/lists6.py +3 -0
- pythonlings/curriculum/checks/lists/lists7.py +4 -0
- pythonlings/curriculum/checks/lists/lists8.py +7 -0
- pythonlings/curriculum/checks/lists/lists9.py +4 -0
- pythonlings/curriculum/checks/loops/loops1.py +2 -0
- pythonlings/curriculum/checks/loops/loops10.py +6 -0
- pythonlings/curriculum/checks/loops/loops2.py +2 -0
- pythonlings/curriculum/checks/loops/loops3.py +2 -0
- pythonlings/curriculum/checks/loops/loops4.py +3 -0
- pythonlings/curriculum/checks/loops/loops5.py +2 -0
- pythonlings/curriculum/checks/loops/loops6.py +2 -0
- pythonlings/curriculum/checks/loops/loops7.py +2 -0
- pythonlings/curriculum/checks/loops/loops8.py +2 -0
- pythonlings/curriculum/checks/loops/loops9.py +3 -0
- pythonlings/curriculum/checks/modules/modules1.py +2 -0
- pythonlings/curriculum/checks/modules/modules2.py +2 -0
- pythonlings/curriculum/checks/modules/modules3.py +5 -0
- pythonlings/curriculum/checks/modules/modules4.py +8 -0
- pythonlings/curriculum/checks/modules/modules5.py +7 -0
- pythonlings/curriculum/checks/modules/modules6.py +4 -0
- pythonlings/curriculum/checks/modules/modules7.py +4 -0
- pythonlings/curriculum/checks/modules/modules8.py +15 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced1.py +4 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced10.py +3 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced11.py +3 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced12.py +4 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced2.py +4 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced3.py +3 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced4.py +3 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced5.py +3 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced6.py +2 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced7.py +3 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced8.py +3 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced9.py +4 -0
- pythonlings/curriculum/checks/pathlib/pathlib1.py +5 -0
- pythonlings/curriculum/checks/pathlib/pathlib2.py +4 -0
- pythonlings/curriculum/checks/pathlib/pathlib3.py +6 -0
- pythonlings/curriculum/checks/pathlib/pathlib4.py +4 -0
- pythonlings/curriculum/checks/pathlib/pathlib5.py +4 -0
- pythonlings/curriculum/checks/pathlib/pathlib6.py +5 -0
- pythonlings/curriculum/checks/recursion/recursion1.py +5 -0
- pythonlings/curriculum/checks/recursion/recursion2.py +5 -0
- pythonlings/curriculum/checks/recursion/recursion3.py +6 -0
- pythonlings/curriculum/checks/recursion/recursion4.py +6 -0
- pythonlings/curriculum/checks/recursion/recursion5.py +7 -0
- pythonlings/curriculum/checks/recursion/recursion6.py +7 -0
- pythonlings/curriculum/checks/recursion/recursion7.py +7 -0
- pythonlings/curriculum/checks/recursion/recursion8.py +15 -0
- pythonlings/curriculum/checks/regex/regex1.py +3 -0
- pythonlings/curriculum/checks/regex/regex10.py +17 -0
- pythonlings/curriculum/checks/regex/regex2.py +3 -0
- pythonlings/curriculum/checks/regex/regex3.py +2 -0
- pythonlings/curriculum/checks/regex/regex4.py +4 -0
- pythonlings/curriculum/checks/regex/regex5.py +4 -0
- pythonlings/curriculum/checks/regex/regex6.py +6 -0
- pythonlings/curriculum/checks/regex/regex7.py +5 -0
- pythonlings/curriculum/checks/regex/regex8.py +6 -0
- pythonlings/curriculum/checks/regex/regex9.py +8 -0
- pythonlings/curriculum/checks/sets/sets1.py +3 -0
- pythonlings/curriculum/checks/sets/sets10.py +15 -0
- pythonlings/curriculum/checks/sets/sets2.py +5 -0
- pythonlings/curriculum/checks/sets/sets3.py +5 -0
- pythonlings/curriculum/checks/sets/sets4.py +3 -0
- pythonlings/curriculum/checks/sets/sets5.py +3 -0
- pythonlings/curriculum/checks/sets/sets6.py +3 -0
- pythonlings/curriculum/checks/sets/sets7.py +5 -0
- pythonlings/curriculum/checks/sets/sets8.py +5 -0
- pythonlings/curriculum/checks/sets/sets9.py +3 -0
- pythonlings/curriculum/checks/strings/strings1.py +3 -0
- pythonlings/curriculum/checks/strings/strings10.py +5 -0
- pythonlings/curriculum/checks/strings/strings2.py +3 -0
- pythonlings/curriculum/checks/strings/strings3.py +3 -0
- pythonlings/curriculum/checks/strings/strings4.py +2 -0
- pythonlings/curriculum/checks/strings/strings5.py +4 -0
- pythonlings/curriculum/checks/strings/strings6.py +3 -0
- pythonlings/curriculum/checks/strings/strings7.py +3 -0
- pythonlings/curriculum/checks/strings/strings8.py +3 -0
- pythonlings/curriculum/checks/strings/strings9.py +3 -0
- pythonlings/curriculum/checks/testing/testing1.py +2 -0
- pythonlings/curriculum/checks/testing/testing10.py +9 -0
- pythonlings/curriculum/checks/testing/testing11.py +7 -0
- pythonlings/curriculum/checks/testing/testing12.py +19 -0
- pythonlings/curriculum/checks/testing/testing2.py +2 -0
- pythonlings/curriculum/checks/testing/testing3.py +12 -0
- pythonlings/curriculum/checks/testing/testing4.py +6 -0
- pythonlings/curriculum/checks/testing/testing5.py +6 -0
- pythonlings/curriculum/checks/testing/testing6.py +14 -0
- pythonlings/curriculum/checks/testing/testing7.py +14 -0
- pythonlings/curriculum/checks/testing/testing8.py +27 -0
- pythonlings/curriculum/checks/testing/testing9.py +14 -0
- pythonlings/curriculum/checks/tuples/tuples1.py +3 -0
- pythonlings/curriculum/checks/tuples/tuples10.py +6 -0
- pythonlings/curriculum/checks/tuples/tuples2.py +3 -0
- pythonlings/curriculum/checks/tuples/tuples3.py +4 -0
- pythonlings/curriculum/checks/tuples/tuples4.py +3 -0
- pythonlings/curriculum/checks/tuples/tuples5.py +4 -0
- pythonlings/curriculum/checks/tuples/tuples6.py +5 -0
- pythonlings/curriculum/checks/tuples/tuples7.py +3 -0
- pythonlings/curriculum/checks/tuples/tuples8.py +4 -0
- pythonlings/curriculum/checks/tuples/tuples9.py +5 -0
- pythonlings/curriculum/checks/type_hints/type_hints1.py +3 -0
- pythonlings/curriculum/checks/type_hints/type_hints2.py +5 -0
- pythonlings/curriculum/checks/type_hints/type_hints3.py +5 -0
- pythonlings/curriculum/checks/type_hints/type_hints4.py +5 -0
- pythonlings/curriculum/checks/type_hints/type_hints5.py +6 -0
- pythonlings/curriculum/checks/type_hints/type_hints6.py +7 -0
- pythonlings/curriculum/checks/type_hints/type_hints7.py +9 -0
- pythonlings/curriculum/checks/type_hints/type_hints8.py +9 -0
- pythonlings/curriculum/checks/variables/variables1.py +7 -0
- pythonlings/curriculum/checks/variables/variables10.py +4 -0
- pythonlings/curriculum/checks/variables/variables2.py +19 -0
- pythonlings/curriculum/checks/variables/variables3.py +4 -0
- pythonlings/curriculum/checks/variables/variables4.py +2 -0
- pythonlings/curriculum/checks/variables/variables5.py +2 -0
- pythonlings/curriculum/checks/variables/variables6.py +4 -0
- pythonlings/curriculum/checks/variables/variables7.py +3 -0
- pythonlings/curriculum/checks/variables/variables8.py +4 -0
- pythonlings/curriculum/checks/variables/variables9.py +3 -0
- pythonlings/curriculum/exercises/async/async1.py +7 -0
- pythonlings/curriculum/exercises/async/async10.py +14 -0
- pythonlings/curriculum/exercises/async/async2.py +10 -0
- pythonlings/curriculum/exercises/async/async3.py +10 -0
- pythonlings/curriculum/exercises/async/async4.py +15 -0
- pythonlings/curriculum/exercises/async/async5.py +13 -0
- pythonlings/curriculum/exercises/async/async6.py +14 -0
- pythonlings/curriculum/exercises/async/async7.py +13 -0
- pythonlings/curriculum/exercises/async/async8.py +11 -0
- pythonlings/curriculum/exercises/async/async9.py +13 -0
- pythonlings/curriculum/exercises/classes/classes1.py +13 -0
- pythonlings/curriculum/exercises/classes/classes10.py +22 -0
- pythonlings/curriculum/exercises/classes/classes11.py +24 -0
- pythonlings/curriculum/exercises/classes/classes12.py +33 -0
- pythonlings/curriculum/exercises/classes/classes2.py +13 -0
- pythonlings/curriculum/exercises/classes/classes3.py +13 -0
- pythonlings/curriculum/exercises/classes/classes4.py +16 -0
- pythonlings/curriculum/exercises/classes/classes5.py +18 -0
- pythonlings/curriculum/exercises/classes/classes6.py +15 -0
- pythonlings/curriculum/exercises/classes/classes7.py +18 -0
- pythonlings/curriculum/exercises/classes/classes8.py +14 -0
- pythonlings/curriculum/exercises/classes/classes9.py +18 -0
- pythonlings/curriculum/exercises/collections/collections1.py +11 -0
- pythonlings/curriculum/exercises/collections/collections10.py +37 -0
- pythonlings/curriculum/exercises/collections/collections2.py +18 -0
- pythonlings/curriculum/exercises/collections/collections3.py +17 -0
- pythonlings/curriculum/exercises/collections/collections4.py +26 -0
- pythonlings/curriculum/exercises/collections/collections5.py +14 -0
- pythonlings/curriculum/exercises/collections/collections6.py +18 -0
- pythonlings/curriculum/exercises/collections/collections7.py +20 -0
- pythonlings/curriculum/exercises/collections/collections8.py +23 -0
- pythonlings/curriculum/exercises/collections/collections9.py +21 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions1.py +10 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions10.py +15 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions2.py +12 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions3.py +12 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions4.py +14 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions5.py +12 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions6.py +12 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions7.py +12 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions8.py +15 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions9.py +13 -0
- pythonlings/curriculum/exercises/conditionals/conditionals1.py +15 -0
- pythonlings/curriculum/exercises/conditionals/conditionals10.py +18 -0
- pythonlings/curriculum/exercises/conditionals/conditionals2.py +13 -0
- pythonlings/curriculum/exercises/conditionals/conditionals3.py +18 -0
- pythonlings/curriculum/exercises/conditionals/conditionals4.py +20 -0
- pythonlings/curriculum/exercises/conditionals/conditionals5.py +17 -0
- pythonlings/curriculum/exercises/conditionals/conditionals6.py +15 -0
- pythonlings/curriculum/exercises/conditionals/conditionals7.py +18 -0
- pythonlings/curriculum/exercises/conditionals/conditionals8.py +20 -0
- pythonlings/curriculum/exercises/conditionals/conditionals9.py +12 -0
- pythonlings/curriculum/exercises/context_managers/context_managers1.py +18 -0
- pythonlings/curriculum/exercises/context_managers/context_managers2.py +19 -0
- pythonlings/curriculum/exercises/context_managers/context_managers3.py +24 -0
- pythonlings/curriculum/exercises/context_managers/context_managers4.py +30 -0
- pythonlings/curriculum/exercises/context_managers/context_managers5.py +30 -0
- pythonlings/curriculum/exercises/context_managers/context_managers6.py +26 -0
- pythonlings/curriculum/exercises/context_managers/context_managers7.py +30 -0
- pythonlings/curriculum/exercises/context_managers/context_managers8.py +45 -0
- pythonlings/curriculum/exercises/dataclasses/dataclasses1.py +14 -0
- pythonlings/curriculum/exercises/dataclasses/dataclasses2.py +23 -0
- pythonlings/curriculum/exercises/dataclasses/dataclasses3.py +21 -0
- pythonlings/curriculum/exercises/dataclasses/dataclasses4.py +25 -0
- pythonlings/curriculum/exercises/dataclasses/dataclasses5.py +25 -0
- pythonlings/curriculum/exercises/dataclasses/dataclasses6.py +21 -0
- pythonlings/curriculum/exercises/dataclasses/dataclasses7.py +27 -0
- pythonlings/curriculum/exercises/dataclasses/dataclasses8.py +30 -0
- pythonlings/curriculum/exercises/datetime/datetime1.py +8 -0
- pythonlings/curriculum/exercises/datetime/datetime2.py +9 -0
- pythonlings/curriculum/exercises/datetime/datetime3.py +9 -0
- pythonlings/curriculum/exercises/datetime/datetime4.py +9 -0
- pythonlings/curriculum/exercises/datetime/datetime5.py +10 -0
- pythonlings/curriculum/exercises/datetime/datetime6.py +10 -0
- pythonlings/curriculum/exercises/datetime/datetime7.py +10 -0
- pythonlings/curriculum/exercises/datetime/datetime8.py +11 -0
- pythonlings/curriculum/exercises/decorators/decorators1.py +19 -0
- pythonlings/curriculum/exercises/decorators/decorators10.py +32 -0
- pythonlings/curriculum/exercises/decorators/decorators2.py +22 -0
- pythonlings/curriculum/exercises/decorators/decorators3.py +18 -0
- pythonlings/curriculum/exercises/decorators/decorators4.py +24 -0
- pythonlings/curriculum/exercises/decorators/decorators5.py +25 -0
- pythonlings/curriculum/exercises/decorators/decorators6.py +19 -0
- pythonlings/curriculum/exercises/decorators/decorators7.py +25 -0
- pythonlings/curriculum/exercises/decorators/decorators8.py +25 -0
- pythonlings/curriculum/exercises/decorators/decorators9.py +26 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries1.py +6 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries10.py +17 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries2.py +9 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries3.py +16 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries4.py +15 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries5.py +15 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries6.py +18 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries7.py +17 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries8.py +17 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries9.py +25 -0
- pythonlings/curriculum/exercises/enums/enums1.py +12 -0
- pythonlings/curriculum/exercises/enums/enums2.py +11 -0
- pythonlings/curriculum/exercises/enums/enums3.py +14 -0
- pythonlings/curriculum/exercises/enums/enums4.py +13 -0
- pythonlings/curriculum/exercises/enums/enums5.py +14 -0
- pythonlings/curriculum/exercises/enums/enums6.py +15 -0
- pythonlings/curriculum/exercises/exceptions/exceptions1.py +15 -0
- pythonlings/curriculum/exercises/exceptions/exceptions10.py +19 -0
- pythonlings/curriculum/exercises/exceptions/exceptions2.py +12 -0
- pythonlings/curriculum/exercises/exceptions/exceptions3.py +13 -0
- pythonlings/curriculum/exercises/exceptions/exceptions4.py +17 -0
- pythonlings/curriculum/exercises/exceptions/exceptions5.py +20 -0
- pythonlings/curriculum/exercises/exceptions/exceptions6.py +13 -0
- pythonlings/curriculum/exercises/exceptions/exceptions7.py +15 -0
- pythonlings/curriculum/exercises/exceptions/exceptions8.py +17 -0
- pythonlings/curriculum/exercises/exceptions/exceptions9.py +20 -0
- pythonlings/curriculum/exercises/file_io/file_io1.py +18 -0
- pythonlings/curriculum/exercises/file_io/file_io10.py +25 -0
- pythonlings/curriculum/exercises/file_io/file_io2.py +16 -0
- pythonlings/curriculum/exercises/file_io/file_io3.py +18 -0
- pythonlings/curriculum/exercises/file_io/file_io4.py +21 -0
- pythonlings/curriculum/exercises/file_io/file_io5.py +19 -0
- pythonlings/curriculum/exercises/file_io/file_io6.py +22 -0
- pythonlings/curriculum/exercises/file_io/file_io7.py +20 -0
- pythonlings/curriculum/exercises/file_io/file_io8.py +18 -0
- pythonlings/curriculum/exercises/file_io/file_io9.py +19 -0
- pythonlings/curriculum/exercises/functional/functional1.py +7 -0
- pythonlings/curriculum/exercises/functional/functional10.py +19 -0
- pythonlings/curriculum/exercises/functional/functional2.py +8 -0
- pythonlings/curriculum/exercises/functional/functional3.py +11 -0
- pythonlings/curriculum/exercises/functional/functional4.py +11 -0
- pythonlings/curriculum/exercises/functional/functional5.py +11 -0
- pythonlings/curriculum/exercises/functional/functional6.py +14 -0
- pythonlings/curriculum/exercises/functional/functional7.py +12 -0
- pythonlings/curriculum/exercises/functional/functional8.py +13 -0
- pythonlings/curriculum/exercises/functional/functional9.py +15 -0
- pythonlings/curriculum/exercises/functions/functions1.py +8 -0
- pythonlings/curriculum/exercises/functions/functions10.py +19 -0
- pythonlings/curriculum/exercises/functions/functions2.py +9 -0
- pythonlings/curriculum/exercises/functions/functions3.py +8 -0
- pythonlings/curriculum/exercises/functions/functions4.py +8 -0
- pythonlings/curriculum/exercises/functions/functions5.py +11 -0
- pythonlings/curriculum/exercises/functions/functions6.py +16 -0
- pythonlings/curriculum/exercises/functions/functions7.py +11 -0
- pythonlings/curriculum/exercises/functions/functions8.py +10 -0
- pythonlings/curriculum/exercises/functions/functions9.py +16 -0
- pythonlings/curriculum/exercises/generators/generators1.py +13 -0
- pythonlings/curriculum/exercises/generators/generators10.py +16 -0
- pythonlings/curriculum/exercises/generators/generators2.py +17 -0
- pythonlings/curriculum/exercises/generators/generators3.py +19 -0
- pythonlings/curriculum/exercises/generators/generators4.py +12 -0
- pythonlings/curriculum/exercises/generators/generators5.py +24 -0
- pythonlings/curriculum/exercises/generators/generators6.py +20 -0
- pythonlings/curriculum/exercises/generators/generators7.py +14 -0
- pythonlings/curriculum/exercises/generators/generators8.py +17 -0
- pythonlings/curriculum/exercises/generators/generators9.py +26 -0
- pythonlings/curriculum/exercises/itertools/itertools1.py +10 -0
- pythonlings/curriculum/exercises/itertools/itertools2.py +8 -0
- pythonlings/curriculum/exercises/itertools/itertools3.py +10 -0
- pythonlings/curriculum/exercises/itertools/itertools4.py +12 -0
- pythonlings/curriculum/exercises/itertools/itertools5.py +9 -0
- pythonlings/curriculum/exercises/itertools/itertools6.py +9 -0
- pythonlings/curriculum/exercises/itertools/itertools7.py +10 -0
- pythonlings/curriculum/exercises/itertools/itertools8.py +18 -0
- pythonlings/curriculum/exercises/json/json1.py +10 -0
- pythonlings/curriculum/exercises/json/json2.py +9 -0
- pythonlings/curriculum/exercises/json/json3.py +10 -0
- pythonlings/curriculum/exercises/json/json4.py +13 -0
- pythonlings/curriculum/exercises/json/json5.py +9 -0
- pythonlings/curriculum/exercises/json/json6.py +10 -0
- pythonlings/curriculum/exercises/json/json7.py +10 -0
- pythonlings/curriculum/exercises/json/json8.py +12 -0
- pythonlings/curriculum/exercises/lists/lists1.py +7 -0
- pythonlings/curriculum/exercises/lists/lists10.py +28 -0
- pythonlings/curriculum/exercises/lists/lists2.py +13 -0
- pythonlings/curriculum/exercises/lists/lists3.py +16 -0
- pythonlings/curriculum/exercises/lists/lists4.py +16 -0
- pythonlings/curriculum/exercises/lists/lists5.py +16 -0
- pythonlings/curriculum/exercises/lists/lists6.py +15 -0
- pythonlings/curriculum/exercises/lists/lists7.py +13 -0
- pythonlings/curriculum/exercises/lists/lists8.py +15 -0
- pythonlings/curriculum/exercises/lists/lists9.py +19 -0
- pythonlings/curriculum/exercises/loops/loops1.py +8 -0
- pythonlings/curriculum/exercises/loops/loops10.py +22 -0
- pythonlings/curriculum/exercises/loops/loops2.py +10 -0
- pythonlings/curriculum/exercises/loops/loops3.py +10 -0
- pythonlings/curriculum/exercises/loops/loops4.py +12 -0
- pythonlings/curriculum/exercises/loops/loops5.py +13 -0
- pythonlings/curriculum/exercises/loops/loops6.py +10 -0
- pythonlings/curriculum/exercises/loops/loops7.py +13 -0
- pythonlings/curriculum/exercises/loops/loops8.py +11 -0
- pythonlings/curriculum/exercises/loops/loops9.py +11 -0
- pythonlings/curriculum/exercises/modules/modules1.py +10 -0
- pythonlings/curriculum/exercises/modules/modules2.py +10 -0
- pythonlings/curriculum/exercises/modules/modules3.py +13 -0
- pythonlings/curriculum/exercises/modules/modules4.py +18 -0
- pythonlings/curriculum/exercises/modules/modules5.py +17 -0
- pythonlings/curriculum/exercises/modules/modules6.py +12 -0
- pythonlings/curriculum/exercises/modules/modules7.py +16 -0
- pythonlings/curriculum/exercises/modules/modules8.py +23 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced1.py +11 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced10.py +18 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced11.py +13 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced12.py +12 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced2.py +13 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced3.py +22 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced4.py +12 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced5.py +11 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced6.py +12 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced7.py +14 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced8.py +14 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced9.py +9 -0
- pythonlings/curriculum/exercises/pathlib/pathlib1.py +9 -0
- pythonlings/curriculum/exercises/pathlib/pathlib2.py +9 -0
- pythonlings/curriculum/exercises/pathlib/pathlib3.py +11 -0
- pythonlings/curriculum/exercises/pathlib/pathlib4.py +9 -0
- pythonlings/curriculum/exercises/pathlib/pathlib5.py +9 -0
- pythonlings/curriculum/exercises/pathlib/pathlib6.py +10 -0
- pythonlings/curriculum/exercises/recursion/recursion1.py +12 -0
- pythonlings/curriculum/exercises/recursion/recursion2.py +12 -0
- pythonlings/curriculum/exercises/recursion/recursion3.py +12 -0
- pythonlings/curriculum/exercises/recursion/recursion4.py +12 -0
- pythonlings/curriculum/exercises/recursion/recursion5.py +14 -0
- pythonlings/curriculum/exercises/recursion/recursion6.py +13 -0
- pythonlings/curriculum/exercises/recursion/recursion7.py +20 -0
- pythonlings/curriculum/exercises/recursion/recursion8.py +27 -0
- pythonlings/curriculum/exercises/regex/regex1.py +16 -0
- pythonlings/curriculum/exercises/regex/regex10.py +31 -0
- pythonlings/curriculum/exercises/regex/regex2.py +16 -0
- pythonlings/curriculum/exercises/regex/regex3.py +16 -0
- pythonlings/curriculum/exercises/regex/regex4.py +19 -0
- pythonlings/curriculum/exercises/regex/regex5.py +19 -0
- pythonlings/curriculum/exercises/regex/regex6.py +23 -0
- pythonlings/curriculum/exercises/regex/regex7.py +24 -0
- pythonlings/curriculum/exercises/regex/regex8.py +19 -0
- pythonlings/curriculum/exercises/regex/regex9.py +22 -0
- pythonlings/curriculum/exercises/sets/sets1.py +6 -0
- pythonlings/curriculum/exercises/sets/sets10.py +21 -0
- pythonlings/curriculum/exercises/sets/sets2.py +12 -0
- pythonlings/curriculum/exercises/sets/sets3.py +15 -0
- pythonlings/curriculum/exercises/sets/sets4.py +14 -0
- pythonlings/curriculum/exercises/sets/sets5.py +14 -0
- pythonlings/curriculum/exercises/sets/sets6.py +14 -0
- pythonlings/curriculum/exercises/sets/sets7.py +16 -0
- pythonlings/curriculum/exercises/sets/sets8.py +16 -0
- pythonlings/curriculum/exercises/sets/sets9.py +15 -0
- pythonlings/curriculum/exercises/strings/strings1.py +6 -0
- pythonlings/curriculum/exercises/strings/strings10.py +11 -0
- pythonlings/curriculum/exercises/strings/strings2.py +9 -0
- pythonlings/curriculum/exercises/strings/strings3.py +9 -0
- pythonlings/curriculum/exercises/strings/strings4.py +8 -0
- pythonlings/curriculum/exercises/strings/strings5.py +10 -0
- pythonlings/curriculum/exercises/strings/strings6.py +10 -0
- pythonlings/curriculum/exercises/strings/strings7.py +10 -0
- pythonlings/curriculum/exercises/strings/strings8.py +9 -0
- pythonlings/curriculum/exercises/strings/strings9.py +10 -0
- pythonlings/curriculum/exercises/testing/testing1.py +16 -0
- pythonlings/curriculum/exercises/testing/testing10.py +21 -0
- pythonlings/curriculum/exercises/testing/testing11.py +27 -0
- pythonlings/curriculum/exercises/testing/testing12.py +51 -0
- pythonlings/curriculum/exercises/testing/testing2.py +15 -0
- pythonlings/curriculum/exercises/testing/testing3.py +19 -0
- pythonlings/curriculum/exercises/testing/testing4.py +19 -0
- pythonlings/curriculum/exercises/testing/testing5.py +18 -0
- pythonlings/curriculum/exercises/testing/testing6.py +27 -0
- pythonlings/curriculum/exercises/testing/testing7.py +27 -0
- pythonlings/curriculum/exercises/testing/testing8.py +29 -0
- pythonlings/curriculum/exercises/testing/testing9.py +30 -0
- pythonlings/curriculum/exercises/tuples/tuples1.py +7 -0
- pythonlings/curriculum/exercises/tuples/tuples10.py +26 -0
- pythonlings/curriculum/exercises/tuples/tuples2.py +11 -0
- pythonlings/curriculum/exercises/tuples/tuples3.py +9 -0
- pythonlings/curriculum/exercises/tuples/tuples4.py +8 -0
- pythonlings/curriculum/exercises/tuples/tuples5.py +12 -0
- pythonlings/curriculum/exercises/tuples/tuples6.py +15 -0
- pythonlings/curriculum/exercises/tuples/tuples7.py +14 -0
- pythonlings/curriculum/exercises/tuples/tuples8.py +13 -0
- pythonlings/curriculum/exercises/tuples/tuples9.py +18 -0
- pythonlings/curriculum/exercises/type_hints/type_hints1.py +9 -0
- pythonlings/curriculum/exercises/type_hints/type_hints2.py +13 -0
- pythonlings/curriculum/exercises/type_hints/type_hints3.py +13 -0
- pythonlings/curriculum/exercises/type_hints/type_hints4.py +21 -0
- pythonlings/curriculum/exercises/type_hints/type_hints5.py +15 -0
- pythonlings/curriculum/exercises/type_hints/type_hints6.py +16 -0
- pythonlings/curriculum/exercises/type_hints/type_hints7.py +19 -0
- pythonlings/curriculum/exercises/type_hints/type_hints8.py +27 -0
- pythonlings/curriculum/exercises/variables/variables1.py +10 -0
- pythonlings/curriculum/exercises/variables/variables10.py +14 -0
- pythonlings/curriculum/exercises/variables/variables2.py +11 -0
- pythonlings/curriculum/exercises/variables/variables3.py +11 -0
- pythonlings/curriculum/exercises/variables/variables4.py +9 -0
- pythonlings/curriculum/exercises/variables/variables5.py +11 -0
- pythonlings/curriculum/exercises/variables/variables6.py +9 -0
- pythonlings/curriculum/exercises/variables/variables7.py +10 -0
- pythonlings/curriculum/exercises/variables/variables8.py +12 -0
- pythonlings/curriculum/exercises/variables/variables9.py +10 -0
- pythonlings/curriculum/info.toml +1755 -0
- pythonlings/curriculum/solutions/.keep +1 -0
- pythonlings/curriculum/solutions/_answers.py +1654 -0
- pythonlings/curriculum/solutions/async1.py +2 -0
- pythonlings/curriculum/solutions/async10.py +2 -0
- pythonlings/curriculum/solutions/async2.py +2 -0
- pythonlings/curriculum/solutions/async3.py +2 -0
- pythonlings/curriculum/solutions/async4.py +2 -0
- pythonlings/curriculum/solutions/async5.py +2 -0
- pythonlings/curriculum/solutions/async6.py +2 -0
- pythonlings/curriculum/solutions/async7.py +2 -0
- pythonlings/curriculum/solutions/async8.py +2 -0
- pythonlings/curriculum/solutions/async9.py +2 -0
- pythonlings/curriculum/solutions/classes1.py +2 -0
- pythonlings/curriculum/solutions/classes10.py +2 -0
- pythonlings/curriculum/solutions/classes11.py +2 -0
- pythonlings/curriculum/solutions/classes12.py +2 -0
- pythonlings/curriculum/solutions/classes2.py +2 -0
- pythonlings/curriculum/solutions/classes3.py +2 -0
- pythonlings/curriculum/solutions/classes4.py +2 -0
- pythonlings/curriculum/solutions/classes5.py +2 -0
- pythonlings/curriculum/solutions/classes6.py +2 -0
- pythonlings/curriculum/solutions/classes7.py +2 -0
- pythonlings/curriculum/solutions/classes8.py +2 -0
- pythonlings/curriculum/solutions/classes9.py +2 -0
- pythonlings/curriculum/solutions/collections1.py +2 -0
- pythonlings/curriculum/solutions/collections10.py +2 -0
- pythonlings/curriculum/solutions/collections2.py +2 -0
- pythonlings/curriculum/solutions/collections3.py +2 -0
- pythonlings/curriculum/solutions/collections4.py +2 -0
- pythonlings/curriculum/solutions/collections5.py +2 -0
- pythonlings/curriculum/solutions/collections6.py +2 -0
- pythonlings/curriculum/solutions/collections7.py +2 -0
- pythonlings/curriculum/solutions/collections8.py +2 -0
- pythonlings/curriculum/solutions/collections9.py +2 -0
- pythonlings/curriculum/solutions/comprehensions1.py +2 -0
- pythonlings/curriculum/solutions/comprehensions10.py +2 -0
- pythonlings/curriculum/solutions/comprehensions2.py +2 -0
- pythonlings/curriculum/solutions/comprehensions3.py +2 -0
- pythonlings/curriculum/solutions/comprehensions4.py +2 -0
- pythonlings/curriculum/solutions/comprehensions5.py +2 -0
- pythonlings/curriculum/solutions/comprehensions6.py +2 -0
- pythonlings/curriculum/solutions/comprehensions7.py +2 -0
- pythonlings/curriculum/solutions/comprehensions8.py +2 -0
- pythonlings/curriculum/solutions/comprehensions9.py +2 -0
- pythonlings/curriculum/solutions/conditionals1.py +2 -0
- pythonlings/curriculum/solutions/conditionals10.py +2 -0
- pythonlings/curriculum/solutions/conditionals2.py +2 -0
- pythonlings/curriculum/solutions/conditionals3.py +2 -0
- pythonlings/curriculum/solutions/conditionals4.py +2 -0
- pythonlings/curriculum/solutions/conditionals5.py +2 -0
- pythonlings/curriculum/solutions/conditionals6.py +2 -0
- pythonlings/curriculum/solutions/conditionals7.py +2 -0
- pythonlings/curriculum/solutions/conditionals8.py +2 -0
- pythonlings/curriculum/solutions/conditionals9.py +2 -0
- pythonlings/curriculum/solutions/context_managers1.py +2 -0
- pythonlings/curriculum/solutions/context_managers2.py +2 -0
- pythonlings/curriculum/solutions/context_managers3.py +2 -0
- pythonlings/curriculum/solutions/context_managers4.py +2 -0
- pythonlings/curriculum/solutions/context_managers5.py +2 -0
- pythonlings/curriculum/solutions/context_managers6.py +2 -0
- pythonlings/curriculum/solutions/context_managers7.py +2 -0
- pythonlings/curriculum/solutions/context_managers8.py +2 -0
- pythonlings/curriculum/solutions/dataclasses1.py +2 -0
- pythonlings/curriculum/solutions/dataclasses2.py +2 -0
- pythonlings/curriculum/solutions/dataclasses3.py +2 -0
- pythonlings/curriculum/solutions/dataclasses4.py +2 -0
- pythonlings/curriculum/solutions/dataclasses5.py +2 -0
- pythonlings/curriculum/solutions/dataclasses6.py +2 -0
- pythonlings/curriculum/solutions/dataclasses7.py +2 -0
- pythonlings/curriculum/solutions/dataclasses8.py +2 -0
- pythonlings/curriculum/solutions/datetime1.py +2 -0
- pythonlings/curriculum/solutions/datetime2.py +2 -0
- pythonlings/curriculum/solutions/datetime3.py +2 -0
- pythonlings/curriculum/solutions/datetime4.py +2 -0
- pythonlings/curriculum/solutions/datetime5.py +2 -0
- pythonlings/curriculum/solutions/datetime6.py +2 -0
- pythonlings/curriculum/solutions/datetime7.py +2 -0
- pythonlings/curriculum/solutions/datetime8.py +2 -0
- pythonlings/curriculum/solutions/decorators1.py +2 -0
- pythonlings/curriculum/solutions/decorators10.py +2 -0
- pythonlings/curriculum/solutions/decorators2.py +2 -0
- pythonlings/curriculum/solutions/decorators3.py +2 -0
- pythonlings/curriculum/solutions/decorators4.py +2 -0
- pythonlings/curriculum/solutions/decorators5.py +2 -0
- pythonlings/curriculum/solutions/decorators6.py +2 -0
- pythonlings/curriculum/solutions/decorators7.py +2 -0
- pythonlings/curriculum/solutions/decorators8.py +2 -0
- pythonlings/curriculum/solutions/decorators9.py +2 -0
- pythonlings/curriculum/solutions/dictionaries1.py +2 -0
- pythonlings/curriculum/solutions/dictionaries10.py +2 -0
- pythonlings/curriculum/solutions/dictionaries2.py +2 -0
- pythonlings/curriculum/solutions/dictionaries3.py +2 -0
- pythonlings/curriculum/solutions/dictionaries4.py +2 -0
- pythonlings/curriculum/solutions/dictionaries5.py +2 -0
- pythonlings/curriculum/solutions/dictionaries6.py +2 -0
- pythonlings/curriculum/solutions/dictionaries7.py +2 -0
- pythonlings/curriculum/solutions/dictionaries8.py +2 -0
- pythonlings/curriculum/solutions/dictionaries9.py +2 -0
- pythonlings/curriculum/solutions/enums1.py +2 -0
- pythonlings/curriculum/solutions/enums2.py +2 -0
- pythonlings/curriculum/solutions/enums3.py +2 -0
- pythonlings/curriculum/solutions/enums4.py +2 -0
- pythonlings/curriculum/solutions/enums5.py +2 -0
- pythonlings/curriculum/solutions/enums6.py +2 -0
- pythonlings/curriculum/solutions/exceptions1.py +2 -0
- pythonlings/curriculum/solutions/exceptions10.py +2 -0
- pythonlings/curriculum/solutions/exceptions2.py +2 -0
- pythonlings/curriculum/solutions/exceptions3.py +2 -0
- pythonlings/curriculum/solutions/exceptions4.py +2 -0
- pythonlings/curriculum/solutions/exceptions5.py +2 -0
- pythonlings/curriculum/solutions/exceptions6.py +2 -0
- pythonlings/curriculum/solutions/exceptions7.py +2 -0
- pythonlings/curriculum/solutions/exceptions8.py +2 -0
- pythonlings/curriculum/solutions/exceptions9.py +2 -0
- pythonlings/curriculum/solutions/file_io1.py +2 -0
- pythonlings/curriculum/solutions/file_io10.py +2 -0
- pythonlings/curriculum/solutions/file_io2.py +2 -0
- pythonlings/curriculum/solutions/file_io3.py +2 -0
- pythonlings/curriculum/solutions/file_io4.py +2 -0
- pythonlings/curriculum/solutions/file_io5.py +2 -0
- pythonlings/curriculum/solutions/file_io6.py +2 -0
- pythonlings/curriculum/solutions/file_io7.py +2 -0
- pythonlings/curriculum/solutions/file_io8.py +2 -0
- pythonlings/curriculum/solutions/file_io9.py +2 -0
- pythonlings/curriculum/solutions/functional1.py +2 -0
- pythonlings/curriculum/solutions/functional10.py +2 -0
- pythonlings/curriculum/solutions/functional2.py +2 -0
- pythonlings/curriculum/solutions/functional3.py +2 -0
- pythonlings/curriculum/solutions/functional4.py +2 -0
- pythonlings/curriculum/solutions/functional5.py +2 -0
- pythonlings/curriculum/solutions/functional6.py +2 -0
- pythonlings/curriculum/solutions/functional7.py +2 -0
- pythonlings/curriculum/solutions/functional8.py +2 -0
- pythonlings/curriculum/solutions/functional9.py +2 -0
- pythonlings/curriculum/solutions/functions1.py +2 -0
- pythonlings/curriculum/solutions/functions10.py +2 -0
- pythonlings/curriculum/solutions/functions2.py +2 -0
- pythonlings/curriculum/solutions/functions3.py +2 -0
- pythonlings/curriculum/solutions/functions4.py +2 -0
- pythonlings/curriculum/solutions/functions5.py +2 -0
- pythonlings/curriculum/solutions/functions6.py +2 -0
- pythonlings/curriculum/solutions/functions7.py +2 -0
- pythonlings/curriculum/solutions/functions8.py +2 -0
- pythonlings/curriculum/solutions/functions9.py +2 -0
- pythonlings/curriculum/solutions/generators1.py +2 -0
- pythonlings/curriculum/solutions/generators10.py +2 -0
- pythonlings/curriculum/solutions/generators2.py +2 -0
- pythonlings/curriculum/solutions/generators3.py +2 -0
- pythonlings/curriculum/solutions/generators4.py +2 -0
- pythonlings/curriculum/solutions/generators5.py +2 -0
- pythonlings/curriculum/solutions/generators6.py +2 -0
- pythonlings/curriculum/solutions/generators7.py +2 -0
- pythonlings/curriculum/solutions/generators8.py +2 -0
- pythonlings/curriculum/solutions/generators9.py +2 -0
- pythonlings/curriculum/solutions/itertools1.py +2 -0
- pythonlings/curriculum/solutions/itertools2.py +2 -0
- pythonlings/curriculum/solutions/itertools3.py +2 -0
- pythonlings/curriculum/solutions/itertools4.py +2 -0
- pythonlings/curriculum/solutions/itertools5.py +2 -0
- pythonlings/curriculum/solutions/itertools6.py +2 -0
- pythonlings/curriculum/solutions/itertools7.py +2 -0
- pythonlings/curriculum/solutions/itertools8.py +2 -0
- pythonlings/curriculum/solutions/json1.py +2 -0
- pythonlings/curriculum/solutions/json2.py +2 -0
- pythonlings/curriculum/solutions/json3.py +2 -0
- pythonlings/curriculum/solutions/json4.py +2 -0
- pythonlings/curriculum/solutions/json5.py +2 -0
- pythonlings/curriculum/solutions/json6.py +2 -0
- pythonlings/curriculum/solutions/json7.py +2 -0
- pythonlings/curriculum/solutions/json8.py +2 -0
- pythonlings/curriculum/solutions/lists1.py +2 -0
- pythonlings/curriculum/solutions/lists10.py +2 -0
- pythonlings/curriculum/solutions/lists2.py +2 -0
- pythonlings/curriculum/solutions/lists3.py +2 -0
- pythonlings/curriculum/solutions/lists4.py +2 -0
- pythonlings/curriculum/solutions/lists5.py +2 -0
- pythonlings/curriculum/solutions/lists6.py +2 -0
- pythonlings/curriculum/solutions/lists7.py +2 -0
- pythonlings/curriculum/solutions/lists8.py +2 -0
- pythonlings/curriculum/solutions/lists9.py +2 -0
- pythonlings/curriculum/solutions/loops1.py +2 -0
- pythonlings/curriculum/solutions/loops10.py +2 -0
- pythonlings/curriculum/solutions/loops2.py +2 -0
- pythonlings/curriculum/solutions/loops3.py +2 -0
- pythonlings/curriculum/solutions/loops4.py +2 -0
- pythonlings/curriculum/solutions/loops5.py +2 -0
- pythonlings/curriculum/solutions/loops6.py +2 -0
- pythonlings/curriculum/solutions/loops7.py +2 -0
- pythonlings/curriculum/solutions/loops8.py +2 -0
- pythonlings/curriculum/solutions/loops9.py +2 -0
- pythonlings/curriculum/solutions/modules1.py +2 -0
- pythonlings/curriculum/solutions/modules2.py +2 -0
- pythonlings/curriculum/solutions/modules3.py +2 -0
- pythonlings/curriculum/solutions/modules4.py +2 -0
- pythonlings/curriculum/solutions/modules5.py +2 -0
- pythonlings/curriculum/solutions/modules6.py +2 -0
- pythonlings/curriculum/solutions/modules7.py +2 -0
- pythonlings/curriculum/solutions/modules8.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced1.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced10.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced11.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced12.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced2.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced3.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced4.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced5.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced6.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced7.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced8.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced9.py +2 -0
- pythonlings/curriculum/solutions/pathlib1.py +2 -0
- pythonlings/curriculum/solutions/pathlib2.py +2 -0
- pythonlings/curriculum/solutions/pathlib3.py +2 -0
- pythonlings/curriculum/solutions/pathlib4.py +2 -0
- pythonlings/curriculum/solutions/pathlib5.py +2 -0
- pythonlings/curriculum/solutions/pathlib6.py +2 -0
- pythonlings/curriculum/solutions/recursion1.py +2 -0
- pythonlings/curriculum/solutions/recursion2.py +2 -0
- pythonlings/curriculum/solutions/recursion3.py +2 -0
- pythonlings/curriculum/solutions/recursion4.py +2 -0
- pythonlings/curriculum/solutions/recursion5.py +2 -0
- pythonlings/curriculum/solutions/recursion6.py +2 -0
- pythonlings/curriculum/solutions/recursion7.py +2 -0
- pythonlings/curriculum/solutions/recursion8.py +2 -0
- pythonlings/curriculum/solutions/regex1.py +2 -0
- pythonlings/curriculum/solutions/regex10.py +2 -0
- pythonlings/curriculum/solutions/regex2.py +2 -0
- pythonlings/curriculum/solutions/regex3.py +2 -0
- pythonlings/curriculum/solutions/regex4.py +2 -0
- pythonlings/curriculum/solutions/regex5.py +2 -0
- pythonlings/curriculum/solutions/regex6.py +2 -0
- pythonlings/curriculum/solutions/regex7.py +2 -0
- pythonlings/curriculum/solutions/regex8.py +2 -0
- pythonlings/curriculum/solutions/regex9.py +2 -0
- pythonlings/curriculum/solutions/sets1.py +2 -0
- pythonlings/curriculum/solutions/sets10.py +2 -0
- pythonlings/curriculum/solutions/sets2.py +2 -0
- pythonlings/curriculum/solutions/sets3.py +2 -0
- pythonlings/curriculum/solutions/sets4.py +2 -0
- pythonlings/curriculum/solutions/sets5.py +2 -0
- pythonlings/curriculum/solutions/sets6.py +2 -0
- pythonlings/curriculum/solutions/sets7.py +2 -0
- pythonlings/curriculum/solutions/sets8.py +2 -0
- pythonlings/curriculum/solutions/sets9.py +2 -0
- pythonlings/curriculum/solutions/strings1.py +2 -0
- pythonlings/curriculum/solutions/strings10.py +2 -0
- pythonlings/curriculum/solutions/strings2.py +2 -0
- pythonlings/curriculum/solutions/strings3.py +2 -0
- pythonlings/curriculum/solutions/strings4.py +2 -0
- pythonlings/curriculum/solutions/strings5.py +2 -0
- pythonlings/curriculum/solutions/strings6.py +2 -0
- pythonlings/curriculum/solutions/strings7.py +2 -0
- pythonlings/curriculum/solutions/strings8.py +2 -0
- pythonlings/curriculum/solutions/strings9.py +2 -0
- pythonlings/curriculum/solutions/testing1.py +2 -0
- pythonlings/curriculum/solutions/testing10.py +2 -0
- pythonlings/curriculum/solutions/testing11.py +2 -0
- pythonlings/curriculum/solutions/testing12.py +2 -0
- pythonlings/curriculum/solutions/testing2.py +2 -0
- pythonlings/curriculum/solutions/testing3.py +2 -0
- pythonlings/curriculum/solutions/testing4.py +2 -0
- pythonlings/curriculum/solutions/testing5.py +2 -0
- pythonlings/curriculum/solutions/testing6.py +2 -0
- pythonlings/curriculum/solutions/testing7.py +2 -0
- pythonlings/curriculum/solutions/testing8.py +2 -0
- pythonlings/curriculum/solutions/testing9.py +2 -0
- pythonlings/curriculum/solutions/tuples1.py +2 -0
- pythonlings/curriculum/solutions/tuples10.py +2 -0
- pythonlings/curriculum/solutions/tuples2.py +2 -0
- pythonlings/curriculum/solutions/tuples3.py +2 -0
- pythonlings/curriculum/solutions/tuples4.py +2 -0
- pythonlings/curriculum/solutions/tuples5.py +2 -0
- pythonlings/curriculum/solutions/tuples6.py +2 -0
- pythonlings/curriculum/solutions/tuples7.py +2 -0
- pythonlings/curriculum/solutions/tuples8.py +2 -0
- pythonlings/curriculum/solutions/tuples9.py +2 -0
- pythonlings/curriculum/solutions/type_hints1.py +2 -0
- pythonlings/curriculum/solutions/type_hints2.py +2 -0
- pythonlings/curriculum/solutions/type_hints3.py +2 -0
- pythonlings/curriculum/solutions/type_hints4.py +2 -0
- pythonlings/curriculum/solutions/type_hints5.py +2 -0
- pythonlings/curriculum/solutions/type_hints6.py +2 -0
- pythonlings/curriculum/solutions/type_hints7.py +2 -0
- pythonlings/curriculum/solutions/type_hints8.py +2 -0
- pythonlings/curriculum/solutions/variables1.py +2 -0
- pythonlings/curriculum/solutions/variables10.py +2 -0
- pythonlings/curriculum/solutions/variables2.py +2 -0
- pythonlings/curriculum/solutions/variables3.py +2 -0
- pythonlings/curriculum/solutions/variables4.py +2 -0
- pythonlings/curriculum/solutions/variables5.py +2 -0
- pythonlings/curriculum/solutions/variables6.py +2 -0
- pythonlings/curriculum/solutions/variables7.py +2 -0
- pythonlings/curriculum/solutions/variables8.py +2 -0
- pythonlings/curriculum/solutions/variables9.py +2 -0
- pythonlings/docs/NOTICE.md +9 -0
- pythonlings/docs/__init__.py +1 -0
- pythonlings/docs/index.json +160 -0
- pythonlings/docs/topics/__init__.py +1 -0
- pythonlings/docs/topics/async.md +97 -0
- pythonlings/docs/topics/classes.md +97 -0
- pythonlings/docs/topics/collections.md +96 -0
- pythonlings/docs/topics/comprehensions.md +97 -0
- pythonlings/docs/topics/conditionals.md +88 -0
- pythonlings/docs/topics/context_managers.md +97 -0
- pythonlings/docs/topics/dataclasses.md +97 -0
- pythonlings/docs/topics/datetime.md +97 -0
- pythonlings/docs/topics/decorators.md +91 -0
- pythonlings/docs/topics/dictionaries.md +96 -0
- pythonlings/docs/topics/enums.md +96 -0
- pythonlings/docs/topics/exceptions.md +96 -0
- pythonlings/docs/topics/file_io.md +96 -0
- pythonlings/docs/topics/functional.md +96 -0
- pythonlings/docs/topics/functions.md +97 -0
- pythonlings/docs/topics/generators.md +97 -0
- pythonlings/docs/topics/itertools.md +96 -0
- pythonlings/docs/topics/json.md +97 -0
- pythonlings/docs/topics/lists.md +97 -0
- pythonlings/docs/topics/loops.md +95 -0
- pythonlings/docs/topics/modules.md +97 -0
- pythonlings/docs/topics/oop_advanced.md +71 -0
- pythonlings/docs/topics/pathlib.md +79 -0
- pythonlings/docs/topics/recursion.md +73 -0
- pythonlings/docs/topics/regex.md +96 -0
- pythonlings/docs/topics/sets.md +97 -0
- pythonlings/docs/topics/strings.md +96 -0
- pythonlings/docs/topics/testing.md +97 -0
- pythonlings/docs/topics/tuples.md +97 -0
- pythonlings/docs/topics/type_hints.md +97 -0
- pythonlings/docs/topics/variables.md +97 -0
- pythonlings/pythonlings.tcss +123 -0
- pythonlings/screens/__init__.py +0 -0
- pythonlings/screens/docs.py +93 -0
- pythonlings/screens/topic_picker.py +107 -0
- pythonlings/screens/track.py +234 -0
- pythonlings/widgets/__init__.py +0 -0
- pythonlings/widgets/editor_pane.py +33 -0
- pythonlings/widgets/exercise_tree.py +33 -0
- pythonlings/widgets/output_panel.py +180 -0
- pythonlings/widgets/progress.py +15 -0
- pythonlings-0.3.0.dist-info/METADATA +259 -0
- pythonlings-0.3.0.dist-info/RECORD +942 -0
- pythonlings-0.3.0.dist-info/WHEEL +4 -0
- pythonlings-0.3.0.dist-info/entry_points.txt +2 -0
- pythonlings-0.3.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# pythonlings/screens/docs.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import webbrowser
|
|
5
|
+
from urllib.parse import urldefrag
|
|
6
|
+
|
|
7
|
+
from rich.markup import escape
|
|
8
|
+
from textual.app import ComposeResult
|
|
9
|
+
from textual.binding import Binding
|
|
10
|
+
from textual.containers import Vertical, VerticalScroll
|
|
11
|
+
from textual.screen import ModalScreen
|
|
12
|
+
from textual.widgets import Markdown, Static
|
|
13
|
+
|
|
14
|
+
from pythonlings.core.docs import load_snippet
|
|
15
|
+
from pythonlings.core.exercise import Exercise
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DocsScreen(ModalScreen[None]):
|
|
19
|
+
"""Small in-app reference window for the current exercise."""
|
|
20
|
+
|
|
21
|
+
BINDINGS = [
|
|
22
|
+
Binding("escape", "close", "Close", priority=True),
|
|
23
|
+
Binding("o", "open_browser", "Open"),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
def __init__(self, exercise: Exercise) -> None:
|
|
27
|
+
super().__init__()
|
|
28
|
+
self.exercise = exercise
|
|
29
|
+
|
|
30
|
+
def compose(self) -> ComposeResult:
|
|
31
|
+
yield Vertical(
|
|
32
|
+
Static(f"[bold]Docs: {escape(self.exercise.name)}[/bold]", id="docs-title"),
|
|
33
|
+
VerticalScroll(
|
|
34
|
+
Markdown(self._reference_markdown(), id="docs-content", open_links=False),
|
|
35
|
+
id="docs-scroll",
|
|
36
|
+
),
|
|
37
|
+
Static("O Open official docs | Esc Close", id="docs-footer"),
|
|
38
|
+
id="docs-window",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def action_close(self) -> None:
|
|
42
|
+
self.dismiss()
|
|
43
|
+
|
|
44
|
+
def action_open_browser(self) -> None:
|
|
45
|
+
if self.exercise.docs:
|
|
46
|
+
webbrowser.open(self.exercise.docs)
|
|
47
|
+
|
|
48
|
+
def _reference_markdown(self) -> str:
|
|
49
|
+
docs = self.exercise.docs or "(no official docs link configured)"
|
|
50
|
+
hint = self.exercise.hint.strip() or "Use this reference while solving."
|
|
51
|
+
snippet = load_snippet(self.exercise.topic, self.exercise.docs)
|
|
52
|
+
if snippet is None:
|
|
53
|
+
local_reference = "No local reference is bundled for this topic."
|
|
54
|
+
snippet_source = ""
|
|
55
|
+
else:
|
|
56
|
+
local_reference = _display_reference(snippet.text)
|
|
57
|
+
snippet_source = snippet.source_url
|
|
58
|
+
|
|
59
|
+
source_section = ""
|
|
60
|
+
if snippet_source and _page_url(snippet_source) != _page_url(docs):
|
|
61
|
+
source_section = f"\n\n## Local snippet source\n\n{snippet_source}"
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
f"## Local reference\n\n{local_reference}\n\n"
|
|
65
|
+
f"## Quick note\n\n{hint}\n\n"
|
|
66
|
+
f"## Official Python docs\n\n{docs}{source_section}\n"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _display_reference(text: str) -> str:
|
|
71
|
+
"""Remove file metadata so the modal starts with useful reference text."""
|
|
72
|
+
lines = text.strip().splitlines()
|
|
73
|
+
if lines and lines[0].startswith("# "):
|
|
74
|
+
lines = lines[1:]
|
|
75
|
+
lines = _drop_leading_blanks(lines)
|
|
76
|
+
|
|
77
|
+
if lines and lines[0].startswith("Source: "):
|
|
78
|
+
lines = lines[1:]
|
|
79
|
+
lines = _drop_leading_blanks(lines)
|
|
80
|
+
|
|
81
|
+
if lines and lines[0].startswith("This local reference is generated"):
|
|
82
|
+
lines = lines[1:]
|
|
83
|
+
return "\n".join(_drop_leading_blanks(lines)).strip()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _drop_leading_blanks(lines: list[str]) -> list[str]:
|
|
87
|
+
while lines and not lines[0].strip():
|
|
88
|
+
lines = lines[1:]
|
|
89
|
+
return lines
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _page_url(url: str) -> str:
|
|
93
|
+
return urldefrag(url).url
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# pythonlings/screens/topic_picker.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from textual.app import ComposeResult
|
|
5
|
+
from textual.binding import Binding
|
|
6
|
+
from textual.containers import VerticalScroll
|
|
7
|
+
from textual.screen import Screen
|
|
8
|
+
from textual.widgets import Footer, Header, ListItem, ListView, Static
|
|
9
|
+
|
|
10
|
+
from pythonlings.core.state import save as save_state
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TopicPickerScreen(Screen[None]):
|
|
14
|
+
"""Entry screen: choose a topic to work on."""
|
|
15
|
+
|
|
16
|
+
BINDINGS = [
|
|
17
|
+
Binding("escape", "quit", "Quit", priority=True),
|
|
18
|
+
Binding("ctrl+q", "quit", "Quit"),
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
def compose(self) -> ComposeResult:
|
|
22
|
+
yield Header()
|
|
23
|
+
yield Static("", id="topic-banner")
|
|
24
|
+
yield VerticalScroll(ListView(id="topics"))
|
|
25
|
+
yield Footer()
|
|
26
|
+
|
|
27
|
+
def on_mount(self) -> None:
|
|
28
|
+
self.app.title = "pythonlings"
|
|
29
|
+
self.app.sub_title = "choose a topic"
|
|
30
|
+
self._populate()
|
|
31
|
+
|
|
32
|
+
def on_screen_resume(self) -> None:
|
|
33
|
+
self._populate()
|
|
34
|
+
|
|
35
|
+
def _populate(self) -> None:
|
|
36
|
+
listview = self.query_one("#topics", ListView)
|
|
37
|
+
listview.clear()
|
|
38
|
+
self.query_one("#topic-banner", Static).update(self._banner_text())
|
|
39
|
+
manifest = self.app.manifest
|
|
40
|
+
completed = self.app.state.completed
|
|
41
|
+
selected_index = 0
|
|
42
|
+
preferred = self._preferred_topic()
|
|
43
|
+
for topic_index, topic in enumerate(manifest.topics()):
|
|
44
|
+
exs = manifest.exercises_in(topic)
|
|
45
|
+
done = sum(1 for ex in exs if ex.name in completed)
|
|
46
|
+
if done == len(exs):
|
|
47
|
+
mark = "✓"
|
|
48
|
+
status = "Done"
|
|
49
|
+
elif done:
|
|
50
|
+
mark = "●"
|
|
51
|
+
status = "Continue"
|
|
52
|
+
else:
|
|
53
|
+
mark = " "
|
|
54
|
+
status = "Start"
|
|
55
|
+
label = f"{mark} {topic:<18} {done}/{len(exs):<5} {status}"
|
|
56
|
+
listview.append(
|
|
57
|
+
ListItem(Static(label, classes="topic-row"), name=topic)
|
|
58
|
+
)
|
|
59
|
+
if topic == preferred:
|
|
60
|
+
selected_index = topic_index
|
|
61
|
+
if listview.children:
|
|
62
|
+
listview.index = selected_index
|
|
63
|
+
listview.focus()
|
|
64
|
+
|
|
65
|
+
def _topic_progress(self, topic: str) -> tuple[int, int]:
|
|
66
|
+
exs = self.app.manifest.exercises_in(topic)
|
|
67
|
+
done = sum(1 for ex in exs if ex.name in self.app.state.completed)
|
|
68
|
+
return done, len(exs)
|
|
69
|
+
|
|
70
|
+
def _first_incomplete_topic(self) -> str | None:
|
|
71
|
+
for topic in self.app.manifest.topics():
|
|
72
|
+
done, total = self._topic_progress(topic)
|
|
73
|
+
if done < total:
|
|
74
|
+
return topic
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
def _preferred_topic(self) -> str | None:
|
|
78
|
+
topic = self.app.state.last_topic
|
|
79
|
+
if topic in self.app.manifest.topics():
|
|
80
|
+
done, total = self._topic_progress(topic)
|
|
81
|
+
if done < total:
|
|
82
|
+
return topic
|
|
83
|
+
return self._first_incomplete_topic() or (
|
|
84
|
+
self.app.manifest.topics()[0] if self.app.manifest.topics() else None
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def _banner_text(self) -> str:
|
|
88
|
+
first = self._first_incomplete_topic()
|
|
89
|
+
if first is None:
|
|
90
|
+
return self.app.manifest.final_message
|
|
91
|
+
if not self.app.state.seen_intro:
|
|
92
|
+
return f"Start here: {first}"
|
|
93
|
+
if self.app.state.last_topic is None:
|
|
94
|
+
return "Choose a topic to practice."
|
|
95
|
+
return "Topics"
|
|
96
|
+
|
|
97
|
+
def on_list_view_selected(self, event: ListView.Selected) -> None:
|
|
98
|
+
topic = event.item.name
|
|
99
|
+
if topic:
|
|
100
|
+
from pythonlings.screens.track import TrackScreen
|
|
101
|
+
|
|
102
|
+
self.app.state.record_resume(topic, None)
|
|
103
|
+
save_state(self.app.root, self.app.state)
|
|
104
|
+
self.app.push_screen(TrackScreen(topic))
|
|
105
|
+
|
|
106
|
+
def action_quit(self) -> None:
|
|
107
|
+
self.app.exit(0)
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# pythonlings/screens/track.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from textual.app import ComposeResult
|
|
5
|
+
from textual.binding import Binding
|
|
6
|
+
from textual.containers import Horizontal
|
|
7
|
+
from textual.screen import Screen
|
|
8
|
+
from textual.timer import Timer
|
|
9
|
+
from textual.widgets import Footer, Header, TextArea
|
|
10
|
+
|
|
11
|
+
from pythonlings.core.exercise import Exercise, RunResult
|
|
12
|
+
from pythonlings.core.runner import run as run_exercise
|
|
13
|
+
from pythonlings.core.state import next_pending, save as save_state
|
|
14
|
+
from pythonlings.widgets.editor_pane import EditorPane
|
|
15
|
+
from pythonlings.widgets.exercise_tree import ExerciseTree
|
|
16
|
+
from pythonlings.widgets.output_panel import OutputPanel
|
|
17
|
+
from pythonlings.widgets.progress import ProgressBar
|
|
18
|
+
|
|
19
|
+
_DEBOUNCE_SECONDS = 0.6
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TrackScreen(Screen[None]):
|
|
23
|
+
"""One topic's linear track: editor + output + auto-save loop."""
|
|
24
|
+
|
|
25
|
+
BINDINGS = [
|
|
26
|
+
Binding("f1", "toggle_hint", "Hint"),
|
|
27
|
+
Binding("f2", "reset", "Reset"),
|
|
28
|
+
Binding("f3", "toggle_list", "List"),
|
|
29
|
+
Binding("f4", "topics", "Topics"),
|
|
30
|
+
Binding("f5", "docs", "Docs"),
|
|
31
|
+
Binding("escape", "quit", "Quit", priority=True),
|
|
32
|
+
Binding("ctrl+q", "quit", "Quit"),
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
def __init__(self, topic: str, start_exercise: str | None = None) -> None:
|
|
36
|
+
super().__init__()
|
|
37
|
+
self.topic = topic
|
|
38
|
+
self._start_exercise = start_exercise
|
|
39
|
+
self._save_timer: Timer | None = None
|
|
40
|
+
self._loaded_text = ""
|
|
41
|
+
self._failure_counts: dict[str, int] = {}
|
|
42
|
+
self.current: str | None = None
|
|
43
|
+
|
|
44
|
+
def compose(self) -> ComposeResult:
|
|
45
|
+
yield Header()
|
|
46
|
+
yield ProgressBar(id="progress")
|
|
47
|
+
yield Horizontal(
|
|
48
|
+
ExerciseTree(),
|
|
49
|
+
EditorPane(id="editor"),
|
|
50
|
+
OutputPanel(id="output"),
|
|
51
|
+
id="main",
|
|
52
|
+
)
|
|
53
|
+
yield Footer()
|
|
54
|
+
|
|
55
|
+
def on_mount(self) -> None:
|
|
56
|
+
self.app.sub_title = f"topic: {self.topic}"
|
|
57
|
+
self.current = self._initial_exercise()
|
|
58
|
+
self._render_state()
|
|
59
|
+
if self.current is None:
|
|
60
|
+
self.query_one(OutputPanel).show_final(
|
|
61
|
+
f"Topic '{self.topic}' complete."
|
|
62
|
+
)
|
|
63
|
+
return
|
|
64
|
+
self._load_current()
|
|
65
|
+
self._run_current()
|
|
66
|
+
self.query_one(EditorPane).focus_editor()
|
|
67
|
+
|
|
68
|
+
# --- helpers ---------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
def _exercises(self) -> list[Exercise]:
|
|
71
|
+
return self.app.manifest.exercises_in(self.topic)
|
|
72
|
+
|
|
73
|
+
def _initial_exercise(self) -> str | None:
|
|
74
|
+
names = {ex.name for ex in self._exercises()}
|
|
75
|
+
if (
|
|
76
|
+
self._start_exercise in names
|
|
77
|
+
and self._start_exercise not in self.app.state.completed
|
|
78
|
+
):
|
|
79
|
+
return self._start_exercise
|
|
80
|
+
return next_pending(self._exercises(), self.app.state.completed)
|
|
81
|
+
|
|
82
|
+
def _render_state(self) -> None:
|
|
83
|
+
exs = self._exercises()
|
|
84
|
+
done = sum(1 for ex in exs if ex.name in self.app.state.completed)
|
|
85
|
+
self.query_one(ProgressBar).update_progress(done, len(exs))
|
|
86
|
+
self.query_one(ExerciseTree).render_topic(
|
|
87
|
+
self.topic, exs, self.app.state.completed, self.current
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def _progress_counts(self) -> tuple[int, int]:
|
|
91
|
+
exs = self._exercises()
|
|
92
|
+
done = sum(1 for ex in exs if ex.name in self.app.state.completed)
|
|
93
|
+
return done, len(exs)
|
|
94
|
+
|
|
95
|
+
def _exercise(self, name: str) -> Exercise:
|
|
96
|
+
for ex in self._exercises():
|
|
97
|
+
if ex.name == name:
|
|
98
|
+
return ex
|
|
99
|
+
raise KeyError(name)
|
|
100
|
+
|
|
101
|
+
def _load_current(self) -> None:
|
|
102
|
+
if self.current is None:
|
|
103
|
+
return
|
|
104
|
+
if self._save_timer is not None:
|
|
105
|
+
self._save_timer.stop()
|
|
106
|
+
self._save_timer = None
|
|
107
|
+
self.query_one(OutputPanel).reset_hint()
|
|
108
|
+
pane = self.query_one(EditorPane)
|
|
109
|
+
pane.load_exercise(self._exercise(self.current))
|
|
110
|
+
self._loaded_text = pane.text
|
|
111
|
+
self._failure_counts[self.current] = 0
|
|
112
|
+
self._record_resume(self.current)
|
|
113
|
+
|
|
114
|
+
def _record_resume(self, exercise: str | None) -> None:
|
|
115
|
+
self.app.state.record_resume(self.topic, exercise)
|
|
116
|
+
save_state(self.app.root, self.app.state)
|
|
117
|
+
|
|
118
|
+
# --- auto-save / run loop -------------------------------------------
|
|
119
|
+
|
|
120
|
+
def on_text_area_changed(self, event: TextArea.Changed) -> None:
|
|
121
|
+
if event.text_area is not self.query_one("#code", TextArea):
|
|
122
|
+
return
|
|
123
|
+
if self.query_one(EditorPane).text == self._loaded_text:
|
|
124
|
+
return
|
|
125
|
+
if self._save_timer is not None:
|
|
126
|
+
self._save_timer.stop()
|
|
127
|
+
self._save_timer = self.set_timer(_DEBOUNCE_SECONDS, self._flush_and_run)
|
|
128
|
+
|
|
129
|
+
def _flush_and_run(self) -> None:
|
|
130
|
+
self._save_timer = None
|
|
131
|
+
if self.current is None:
|
|
132
|
+
return
|
|
133
|
+
ex = self._exercise(self.current)
|
|
134
|
+
ex.path.write_text(self.query_one(EditorPane).text, encoding="utf-8")
|
|
135
|
+
self._run_current()
|
|
136
|
+
|
|
137
|
+
def _run_current(self) -> None:
|
|
138
|
+
if self.current is None:
|
|
139
|
+
return
|
|
140
|
+
ex = self._exercise(self.current)
|
|
141
|
+
completed, total = self._progress_counts()
|
|
142
|
+
self.query_one(OutputPanel).render_running(ex, completed, total)
|
|
143
|
+
self.run_worker(
|
|
144
|
+
lambda: self._run_blocking(ex), exclusive=True, thread=True
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def _run_blocking(self, exercise: Exercise) -> None:
|
|
148
|
+
result = run_exercise(exercise)
|
|
149
|
+
self.app.call_from_thread(self._apply_result, exercise, result)
|
|
150
|
+
|
|
151
|
+
def _apply_result(self, exercise: Exercise, result: RunResult) -> None:
|
|
152
|
+
if not self.is_attached:
|
|
153
|
+
return # the track screen was popped while a run was in flight
|
|
154
|
+
if exercise.name != self.current:
|
|
155
|
+
return
|
|
156
|
+
if result.exit_code != 0 or result.timed_out:
|
|
157
|
+
self._failure_counts[exercise.name] = (
|
|
158
|
+
self._failure_counts.get(exercise.name, 0) + 1
|
|
159
|
+
)
|
|
160
|
+
else:
|
|
161
|
+
self._failure_counts[exercise.name] = 0
|
|
162
|
+
completed, total = self._progress_counts()
|
|
163
|
+
self.query_one(OutputPanel).render_result(
|
|
164
|
+
exercise,
|
|
165
|
+
result,
|
|
166
|
+
failures=self._failure_counts.get(exercise.name, 0),
|
|
167
|
+
completed=completed,
|
|
168
|
+
total=total,
|
|
169
|
+
)
|
|
170
|
+
if not result.passed:
|
|
171
|
+
return
|
|
172
|
+
self.app.state.mark_done(exercise.name)
|
|
173
|
+
save_state(self.app.root, self.app.state)
|
|
174
|
+
self.current = next_pending(self._exercises(), self.app.state.completed)
|
|
175
|
+
self._render_state()
|
|
176
|
+
if self.current is None:
|
|
177
|
+
self._record_resume(None)
|
|
178
|
+
self.query_one(OutputPanel).show_final(
|
|
179
|
+
f"Topic '{self.topic}' complete — press F4 for topics."
|
|
180
|
+
)
|
|
181
|
+
return
|
|
182
|
+
self._load_current()
|
|
183
|
+
self._run_current()
|
|
184
|
+
|
|
185
|
+
# --- actions ---------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
def action_toggle_hint(self) -> None:
|
|
188
|
+
if self.current is None:
|
|
189
|
+
return
|
|
190
|
+
self.query_one(OutputPanel).toggle_hint(self._exercise(self.current).hint)
|
|
191
|
+
|
|
192
|
+
def action_reset(self) -> None:
|
|
193
|
+
from pythonlings.core.reset import restore
|
|
194
|
+
|
|
195
|
+
if self.current is None:
|
|
196
|
+
return
|
|
197
|
+
if self._save_timer is not None:
|
|
198
|
+
self._save_timer.stop()
|
|
199
|
+
self._save_timer = None
|
|
200
|
+
restore(self.app.root, self._exercise(self.current))
|
|
201
|
+
self._load_current()
|
|
202
|
+
self._run_current()
|
|
203
|
+
|
|
204
|
+
def action_toggle_list(self) -> None:
|
|
205
|
+
tree = self.query_one(ExerciseTree)
|
|
206
|
+
tree.display = not tree.display
|
|
207
|
+
|
|
208
|
+
def action_docs(self) -> None:
|
|
209
|
+
if self.current is None:
|
|
210
|
+
return
|
|
211
|
+
from pythonlings.screens.docs import DocsScreen
|
|
212
|
+
|
|
213
|
+
self.app.push_screen(DocsScreen(self._exercise(self.current)))
|
|
214
|
+
|
|
215
|
+
def action_topics(self) -> None:
|
|
216
|
+
self._flush_pending()
|
|
217
|
+
if self.current is not None:
|
|
218
|
+
self._record_resume(self.current)
|
|
219
|
+
self.app.pop_screen()
|
|
220
|
+
|
|
221
|
+
def action_quit(self) -> None:
|
|
222
|
+
self._flush_pending()
|
|
223
|
+
if self.current is not None:
|
|
224
|
+
self._record_resume(self.current)
|
|
225
|
+
self.app.exit(0)
|
|
226
|
+
|
|
227
|
+
def _flush_pending(self) -> None:
|
|
228
|
+
if self._save_timer is not None:
|
|
229
|
+
self._save_timer.stop()
|
|
230
|
+
self._save_timer = None
|
|
231
|
+
if self.current is not None:
|
|
232
|
+
self._exercise(self.current).path.write_text(
|
|
233
|
+
self.query_one(EditorPane).text, encoding="utf-8"
|
|
234
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# pythonlings/widgets/editor_pane.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from textual.app import ComposeResult
|
|
5
|
+
from textual.containers import Vertical
|
|
6
|
+
from textual.widgets import TextArea
|
|
7
|
+
|
|
8
|
+
from pythonlings.core.exercise import Exercise
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EditorPane(Vertical):
|
|
12
|
+
"""The in-TUI code editor for the current exercise."""
|
|
13
|
+
|
|
14
|
+
def compose(self) -> ComposeResult:
|
|
15
|
+
try:
|
|
16
|
+
editor = TextArea.code_editor("", language="python", id="code")
|
|
17
|
+
except Exception:
|
|
18
|
+
# tree-sitter Python grammar unavailable — fall back to plain text.
|
|
19
|
+
editor = TextArea.code_editor("", id="code")
|
|
20
|
+
yield editor
|
|
21
|
+
|
|
22
|
+
def load_exercise(self, exercise: Exercise) -> None:
|
|
23
|
+
"""Replace the editor contents with the exercise file from disk."""
|
|
24
|
+
area = self.query_one("#code", TextArea)
|
|
25
|
+
area.text = exercise.path.read_text(encoding="utf-8")
|
|
26
|
+
area.move_cursor((0, 0))
|
|
27
|
+
|
|
28
|
+
def focus_editor(self) -> None:
|
|
29
|
+
self.query_one("#code", TextArea).focus()
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def text(self) -> str:
|
|
33
|
+
return self.query_one("#code", TextArea).text
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# pythonlings/widgets/exercise_tree.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from textual.widgets import Tree
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from pythonlings.core.exercise import Exercise
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ExerciseTree(Tree[str]):
|
|
13
|
+
def __init__(self) -> None:
|
|
14
|
+
super().__init__("exercises", id="tree")
|
|
15
|
+
self.show_root = False
|
|
16
|
+
|
|
17
|
+
def render_topic(
|
|
18
|
+
self,
|
|
19
|
+
topic: str,
|
|
20
|
+
exercises: list["Exercise"],
|
|
21
|
+
completed: set[str],
|
|
22
|
+
current: str | None,
|
|
23
|
+
) -> None:
|
|
24
|
+
self.clear()
|
|
25
|
+
node = self.root.add(topic, expand=True)
|
|
26
|
+
for ex in exercises:
|
|
27
|
+
if ex.name in completed:
|
|
28
|
+
marker = "✓"
|
|
29
|
+
elif ex.name == current:
|
|
30
|
+
marker = "●"
|
|
31
|
+
else:
|
|
32
|
+
marker = "🔒"
|
|
33
|
+
node.add_leaf(f"{marker} {ex.name}", data=ex.name)
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# pythonlings/widgets/output_panel.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual.containers import Vertical
|
|
8
|
+
from textual.widgets import Static
|
|
9
|
+
|
|
10
|
+
from pythonlings.core.exercise import Exercise, RunResult
|
|
11
|
+
|
|
12
|
+
_INSTRUCTION = "Edit the code on the left. Checks update automatically."
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class OutputPanel(Vertical):
|
|
16
|
+
def compose(self) -> ComposeResult:
|
|
17
|
+
yield Static("", id="output-header")
|
|
18
|
+
yield Static("", id="goal")
|
|
19
|
+
yield Static("", id="docs")
|
|
20
|
+
yield Static("Loading…", id="status")
|
|
21
|
+
yield Static("", id="next-step")
|
|
22
|
+
yield Static("", id="details")
|
|
23
|
+
yield Static("", id="hint")
|
|
24
|
+
|
|
25
|
+
def renderable_text(self) -> str:
|
|
26
|
+
parts: list[str] = []
|
|
27
|
+
for widget_id in (
|
|
28
|
+
"output-header",
|
|
29
|
+
"goal",
|
|
30
|
+
"docs",
|
|
31
|
+
"status",
|
|
32
|
+
"next-step",
|
|
33
|
+
"details",
|
|
34
|
+
"hint",
|
|
35
|
+
):
|
|
36
|
+
parts.append(str(self.query_one(f"#{widget_id}", Static).content))
|
|
37
|
+
return "\n".join(parts)
|
|
38
|
+
|
|
39
|
+
def render_running(
|
|
40
|
+
self, exercise: Exercise, completed: int = 0, total: int = 0
|
|
41
|
+
) -> None:
|
|
42
|
+
self.remove_class("passed", "failed", "pending")
|
|
43
|
+
self._render_header(exercise, completed, total)
|
|
44
|
+
self.query_one("#goal", Static).update(
|
|
45
|
+
f"[bold]Goal[/bold]\n{self._goal_from(exercise)}"
|
|
46
|
+
)
|
|
47
|
+
self._render_docs(exercise)
|
|
48
|
+
self.query_one("#status", Static).update(
|
|
49
|
+
"[bold blue]Running checks...[/bold blue]"
|
|
50
|
+
)
|
|
51
|
+
self.query_one("#next-step", Static).update(
|
|
52
|
+
"Keep editing; results update automatically."
|
|
53
|
+
)
|
|
54
|
+
self.query_one("#details", Static).update("")
|
|
55
|
+
|
|
56
|
+
def render_result(
|
|
57
|
+
self,
|
|
58
|
+
exercise: Exercise,
|
|
59
|
+
result: RunResult,
|
|
60
|
+
failures: int = 0,
|
|
61
|
+
completed: int = 0,
|
|
62
|
+
total: int = 0,
|
|
63
|
+
) -> None:
|
|
64
|
+
self._render_header(exercise, completed, total)
|
|
65
|
+
self.query_one("#goal", Static).update(
|
|
66
|
+
f"[bold]Goal[/bold]\n{self._goal_from(exercise)}"
|
|
67
|
+
)
|
|
68
|
+
self._render_docs(exercise)
|
|
69
|
+
details = (result.stderr or result.stdout).rstrip()
|
|
70
|
+
self.query_one("#details", Static).update(
|
|
71
|
+
f"[bold]Details[/bold]\n{details}" if details else ""
|
|
72
|
+
)
|
|
73
|
+
self.remove_class("passed", "failed", "pending")
|
|
74
|
+
if result.timed_out:
|
|
75
|
+
self.add_class("failed")
|
|
76
|
+
self.query_one("#status", Static).update(
|
|
77
|
+
"[bold red]Not passing yet[/bold red]"
|
|
78
|
+
)
|
|
79
|
+
self.query_one("#next-step", Static).update(
|
|
80
|
+
f"Timed out after {result.duration_s:.1f}s. "
|
|
81
|
+
"Check for an infinite loop."
|
|
82
|
+
)
|
|
83
|
+
return
|
|
84
|
+
if result.exit_code != 0:
|
|
85
|
+
self.add_class("failed")
|
|
86
|
+
self.query_one("#status", Static).update(
|
|
87
|
+
"[bold red]Not passing yet[/bold red]"
|
|
88
|
+
)
|
|
89
|
+
nudge = (
|
|
90
|
+
self._hint_nudge(exercise)
|
|
91
|
+
if failures
|
|
92
|
+
else "Hint available: press F1."
|
|
93
|
+
)
|
|
94
|
+
self.query_one("#next-step", Static).update(
|
|
95
|
+
f"Read the details, fix the code, then pause typing.\n{nudge}"
|
|
96
|
+
)
|
|
97
|
+
return
|
|
98
|
+
if exercise.is_pending():
|
|
99
|
+
self.add_class("pending")
|
|
100
|
+
self.query_one("#status", Static).update(
|
|
101
|
+
"[bold yellow]Checks pass, remove marker[/bold yellow]"
|
|
102
|
+
)
|
|
103
|
+
self.query_one("#next-step", Static).update(
|
|
104
|
+
"Remove the # I AM NOT DONE line to advance."
|
|
105
|
+
)
|
|
106
|
+
return
|
|
107
|
+
self.add_class("passed")
|
|
108
|
+
self.query_one("#status", Static).update(
|
|
109
|
+
f"[bold green]✓ {exercise.name} complete[/bold green]"
|
|
110
|
+
)
|
|
111
|
+
self.query_one("#next-step", Static).update(
|
|
112
|
+
"Loading the next exercise."
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def show_final(self, message: str) -> None:
|
|
116
|
+
"""Render the curriculum-complete screen."""
|
|
117
|
+
self.remove_class("failed", "pending")
|
|
118
|
+
self.add_class("passed")
|
|
119
|
+
self.query_one("#hint", Static).remove_class("visible")
|
|
120
|
+
self.query_one("#output-header", Static).update(
|
|
121
|
+
"[bold green]All exercises complete[/bold green]"
|
|
122
|
+
)
|
|
123
|
+
self.query_one("#goal", Static).update("")
|
|
124
|
+
self.query_one("#docs", Static).update("")
|
|
125
|
+
self.query_one("#status", Static).update(message)
|
|
126
|
+
self.query_one("#next-step", Static).update("")
|
|
127
|
+
self.query_one("#details", Static).update("")
|
|
128
|
+
|
|
129
|
+
def _render_header(
|
|
130
|
+
self, exercise: Exercise, completed: int = 0, total: int = 0
|
|
131
|
+
) -> None:
|
|
132
|
+
header = self.query_one("#output-header", Static)
|
|
133
|
+
progress = f" [dim]{completed}/{total} complete[/dim]" if total else ""
|
|
134
|
+
header.update(
|
|
135
|
+
f"[bold]{exercise.name}[/bold]{progress} "
|
|
136
|
+
f"[dim]{self._display_path(exercise)}[/dim]\n"
|
|
137
|
+
f"{_INSTRUCTION}"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def _goal_from(self, exercise: Exercise) -> str:
|
|
141
|
+
for line in exercise.path.read_text(encoding="utf-8").splitlines()[:12]:
|
|
142
|
+
stripped = line.strip()
|
|
143
|
+
if stripped.startswith("# Goal:"):
|
|
144
|
+
return stripped.removeprefix("# Goal:").strip()
|
|
145
|
+
if stripped.startswith("# Exercise:"):
|
|
146
|
+
return stripped.removeprefix("# Exercise:").strip()
|
|
147
|
+
return exercise.name
|
|
148
|
+
|
|
149
|
+
def _render_docs(self, exercise: Exercise) -> None:
|
|
150
|
+
docs = exercise.docs.strip()
|
|
151
|
+
self.query_one("#docs", Static).update(
|
|
152
|
+
f"[bold]Docs[/bold]\n{docs}" if docs else ""
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def _hint_nudge(exercise: Exercise) -> str:
|
|
157
|
+
first = exercise.hint.strip().split(".")[0].strip()
|
|
158
|
+
return f"{first}." if first else "Hint available: press F1."
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def _display_path(exercise: Exercise) -> str:
|
|
162
|
+
# Prefer a short path relative to the working directory; fall back to
|
|
163
|
+
# the absolute path when that isn't possible (e.g. different drives).
|
|
164
|
+
try:
|
|
165
|
+
return os.path.relpath(exercise.path)
|
|
166
|
+
except ValueError:
|
|
167
|
+
return str(exercise.path)
|
|
168
|
+
|
|
169
|
+
def toggle_hint(self, text: str) -> None:
|
|
170
|
+
hint = self.query_one("#hint", Static)
|
|
171
|
+
if "visible" in hint.classes:
|
|
172
|
+
hint.remove_class("visible")
|
|
173
|
+
else:
|
|
174
|
+
hint.add_class("visible")
|
|
175
|
+
hint.update(f"[italic]Hint:[/italic] {text or '(no hint provided)'}")
|
|
176
|
+
|
|
177
|
+
def reset_hint(self) -> None:
|
|
178
|
+
hint = self.query_one("#hint", Static)
|
|
179
|
+
hint.remove_class("visible")
|
|
180
|
+
hint.update("")
|