|
7 | 7 | from copy import deepcopy |
8 | 8 | from functools import cached_property |
9 | 9 | from importlib import import_module |
| 10 | +from io import StringIO |
10 | 11 | from pathlib import Path |
| 12 | +from random import shuffle |
11 | 13 | from textwrap import dedent, indent |
| 14 | +from tokenize import Untokenizer, generate_tokens |
12 | 15 | from types import FunctionType |
13 | | -from typing import Type, Union, get_type_hints, List |
| 16 | +from typing import Type, Union, List, get_type_hints |
14 | 17 |
|
| 18 | +import pygments |
15 | 19 | from astcheck import is_ast_like |
16 | 20 | from asttokens import ASTTokens |
17 | 21 | from littleutils import setattrs, only |
|
22 | 26 | generate_for_type, |
23 | 27 | inputs_string, |
24 | 28 | ) |
25 | | -from main.utils import no_weird_whitespace, snake, unwrapped_markdown, returns_stdout, NoMethodWrapper, bind_self, \ |
26 | | - highlighted_markdown |
| 29 | +from main.utils import highlighted_markdown, lexer, html_formatter, shuffled_well, no_weird_whitespace, snake, \ |
| 30 | + unwrapped_markdown, returns_stdout, NoMethodWrapper, bind_self |
27 | 31 |
|
28 | 32 |
|
29 | 33 | def clean_program(program, cls): |
@@ -139,6 +143,54 @@ class inner_cls(inner_cls, cls): |
139 | 143 | messages=messages, |
140 | 144 | hints=hints) |
141 | 145 |
|
| 146 | + if hints: |
| 147 | + cls.get_solution = get_solution(cls) |
| 148 | + |
| 149 | + |
| 150 | +def get_solution(step): |
| 151 | + if issubclass(step, ExerciseStep): |
| 152 | + if step.solution.__name__ == "solution": |
| 153 | + program, _ = clean_program(step.solution, None) |
| 154 | + else: |
| 155 | + program = clean_solution_function(step.solution, dedent(inspect.getsource(step.solution))) |
| 156 | + else: |
| 157 | + program = step.program |
| 158 | + |
| 159 | + untokenizer = Untokenizer() |
| 160 | + tokens = generate_tokens(StringIO(program).readline) |
| 161 | + untokenizer.untokenize(tokens) |
| 162 | + tokens = untokenizer.tokens |
| 163 | + |
| 164 | + masked_indices = [] |
| 165 | + mask = [False] * len(tokens) |
| 166 | + for i, token in enumerate(tokens): |
| 167 | + if not token.isspace(): |
| 168 | + masked_indices.append(i) |
| 169 | + mask[i] = True |
| 170 | + shuffle(masked_indices) |
| 171 | + |
| 172 | + if step.parsons_solution: |
| 173 | + lines = shuffled_well([ |
| 174 | + dict( |
| 175 | + id=str(i), |
| 176 | + content=line, |
| 177 | + ) |
| 178 | + for i, line in enumerate( |
| 179 | + pygments.highlight(program, lexer, html_formatter) |
| 180 | + .splitlines() |
| 181 | + ) |
| 182 | + if line.strip() |
| 183 | + ]) |
| 184 | + else: |
| 185 | + lines = None |
| 186 | + |
| 187 | + return dict( |
| 188 | + tokens=tokens, |
| 189 | + maskedIndices=masked_indices, |
| 190 | + mask=mask, |
| 191 | + lines=lines, |
| 192 | + ) |
| 193 | + |
142 | 194 |
|
143 | 195 | pages = {} |
144 | 196 | page_slugs_list = [] |
@@ -206,6 +258,7 @@ def step_dicts(self): |
206 | 258 | text=text, |
207 | 259 | name=name, |
208 | 260 | hints=getattr(step, "hints", []), |
| 261 | + solution=getattr(step, "get_solution", None), |
209 | 262 | ) |
210 | 263 | for name, text, step in |
211 | 264 | zip(self.step_names, self.step_texts, self.steps) |
@@ -262,6 +315,8 @@ class Step(ABC): |
262 | 315 | tests = {} |
263 | 316 | expected_code_source = None |
264 | 317 | disallowed: List[Disallowed] = [] |
| 318 | + parsons_solution = False |
| 319 | + get_solution = None |
265 | 320 |
|
266 | 321 | def __init__(self, *args): |
267 | 322 | self.args = args |
|
0 commit comments