PyKubeGrader 0.2.37__py3-none-any.whl → 0.2.38__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.
- {PyKubeGrader-0.2.37.dist-info → PyKubeGrader-0.2.38.dist-info}/METADATA +1 -1
- {PyKubeGrader-0.2.37.dist-info → PyKubeGrader-0.2.38.dist-info}/RECORD +10 -8
- {PyKubeGrader-0.2.37.dist-info → PyKubeGrader-0.2.38.dist-info}/entry_points.txt +1 -0
- pykubegrader/build/build_folder.py +21 -9
- pykubegrader/build/markdown_questions.py +93 -0
- pykubegrader/widgets/multiple_choice.py +15 -6
- pykubegrader/widgets/question_processor.py +34 -0
- {PyKubeGrader-0.2.37.dist-info → PyKubeGrader-0.2.38.dist-info}/LICENSE.txt +0 -0
- {PyKubeGrader-0.2.37.dist-info → PyKubeGrader-0.2.38.dist-info}/WHEEL +0 -0
- {PyKubeGrader-0.2.37.dist-info → PyKubeGrader-0.2.38.dist-info}/top_level.txt +0 -0
@@ -5,8 +5,9 @@ pykubegrader/utils.py,sha256=FrxuZ3gtTBTm5FQeH5c0bF9kFjA_AVtE5AYeFhzwKZ0,827
|
|
5
5
|
pykubegrader/validate.py,sha256=OKnItGyd-L8QPKcsE0KRuwBI_IxKiJzMLJKZiA2j3II,11184
|
6
6
|
pykubegrader/build/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
7
7
|
pykubegrader/build/api_notebook_builder.py,sha256=dlcVrGgsvxnt6GlAUN3e-FrpsPNJKXSHni1fstRCBik,20311
|
8
|
-
pykubegrader/build/build_folder.py,sha256=
|
8
|
+
pykubegrader/build/build_folder.py,sha256=WTdPFsV1PRJ9U_730ckcWezQfMEL2oCH4ZDoflmr30M,83138
|
9
9
|
pykubegrader/build/clean_folder.py,sha256=dfs9NuZ-EP6q_xYQnZKH74aEafEB6hpTAZcSkY14UWI,1328
|
10
|
+
pykubegrader/build/markdown_questions.py,sha256=GwU47GyXEXVqD9XDMRZ7VWesmY2kvWV5PLAw21Pgkhk,3118
|
10
11
|
pykubegrader/graders/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
11
12
|
pykubegrader/graders/late_assignments.py,sha256=_2-rA5RqO0BWY9WAQA_mbCxxPKTOiJOl-byD2CYWaE0,1393
|
12
13
|
pykubegrader/log_parser/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
@@ -16,7 +17,8 @@ pykubegrader/submit/submit_assignment.py,sha256=UgJXKWw5b8-bRSFnba4iHAyXnujULHcW
|
|
16
17
|
pykubegrader/tokens/tokens.py,sha256=X9f3SzrGCrAJp_BXhr6VJn5f0LxtgQ7HLPBw7zEF2BY,1198
|
17
18
|
pykubegrader/tokens/validate_token.py,sha256=MQtgz_USvSZ9JahJ48ybjp74F5aYz64lhtvuwVc4kQw,2712
|
18
19
|
pykubegrader/widgets/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
19
|
-
pykubegrader/widgets/multiple_choice.py,sha256=
|
20
|
+
pykubegrader/widgets/multiple_choice.py,sha256=oH1NjL2cuQDhn-9Ds8yBNAWDHr2anGS__F8uel7IsiI,2657
|
21
|
+
pykubegrader/widgets/question_processor.py,sha256=Kcm73lXyLNNqeiwmeBdPxdKDYTmAuX65RlqOZGo221Q,1031
|
20
22
|
pykubegrader/widgets/reading_question.py,sha256=y30_swHwzH8LrT8deWTnxctAAmR8BSxTlXAqMgUrAT4,3031
|
21
23
|
pykubegrader/widgets/select_many.py,sha256=l7YQ8QT5k71j36KC1f5LmKIAX2bXpvMDGc6nqIJ1PeQ,4116
|
22
24
|
pykubegrader/widgets/student_info.py,sha256=xhQgKehk1r5e6N_hnjAIovLdPvQju6ZqQTOiPG0aevg,3568
|
@@ -27,9 +29,9 @@ pykubegrader/widgets_base/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-
|
|
27
29
|
pykubegrader/widgets_base/multi_select.py,sha256=Cl0IN21wXLZuFu-zC65aS9tD4jMfzCRJ2DPjHao5_Ak,4044
|
28
30
|
pykubegrader/widgets_base/reading.py,sha256=xmvN1UIXwk32v9S-JhsXwDc7axPlgpvoxSeM3II8sxY,5393
|
29
31
|
pykubegrader/widgets_base/select.py,sha256=Fw3uFNOIWo1a3CvlzSx23bvi6bSmA3TqutuRbhD4Dp8,2525
|
30
|
-
PyKubeGrader-0.2.
|
31
|
-
PyKubeGrader-0.2.
|
32
|
-
PyKubeGrader-0.2.
|
33
|
-
PyKubeGrader-0.2.
|
34
|
-
PyKubeGrader-0.2.
|
35
|
-
PyKubeGrader-0.2.
|
32
|
+
PyKubeGrader-0.2.38.dist-info/LICENSE.txt,sha256=YTp-Ewc8Kems8PJEE27KnBPFnZSxoWvSg7nnknzPyYw,1546
|
33
|
+
PyKubeGrader-0.2.38.dist-info/METADATA,sha256=EB80QMUnx_Rfwz8FAOSk1suNMIkeWnGvm0CdDV20_aM,2779
|
34
|
+
PyKubeGrader-0.2.38.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
35
|
+
PyKubeGrader-0.2.38.dist-info/entry_points.txt,sha256=BbLXpFZObpOXA8e3p3GcFkL-sHdUnDLUcnYmc6zx3NI,201
|
36
|
+
PyKubeGrader-0.2.38.dist-info/top_level.txt,sha256=e550Klfze6higFxER1V62fnGOcIgiKRbsrl9CC4UdtQ,13
|
37
|
+
PyKubeGrader-0.2.38.dist-info/RECORD,,
|
@@ -1498,6 +1498,15 @@ def extract_TF(ipynb_file):
|
|
1498
1498
|
return []
|
1499
1499
|
|
1500
1500
|
|
1501
|
+
def extract_question(text):
|
1502
|
+
# Regular expression to capture the multiline title
|
1503
|
+
match = re.search(r"###\s+(.*?)\s+####", text, re.DOTALL)
|
1504
|
+
if match:
|
1505
|
+
# Stripping unnecessary whitespace and asterisks
|
1506
|
+
return match.group(1).strip().strip("**")
|
1507
|
+
return None
|
1508
|
+
|
1509
|
+
|
1501
1510
|
def extract_MCQ(ipynb_file):
|
1502
1511
|
"""
|
1503
1512
|
Extracts multiple-choice questions from markdown cells within sections marked by
|
@@ -1552,15 +1561,18 @@ def extract_MCQ(ipynb_file):
|
|
1552
1561
|
1 # Increment subquestion number for each question
|
1553
1562
|
)
|
1554
1563
|
|
1555
|
-
# Extract question text (### heading)
|
1556
|
-
question_text_match = re.search(
|
1557
|
-
|
1558
|
-
)
|
1559
|
-
question_text = (
|
1560
|
-
|
1561
|
-
|
1562
|
-
|
1563
|
-
)
|
1564
|
+
# # Extract question text (### heading)
|
1565
|
+
# question_text_match = re.search(
|
1566
|
+
# r"^###\s*\*\*(.+)\*\*", markdown_content, re.MULTILINE
|
1567
|
+
# )
|
1568
|
+
# question_text = (
|
1569
|
+
# question_text_match.group(1).strip()
|
1570
|
+
# if question_text_match
|
1571
|
+
# else None
|
1572
|
+
# )
|
1573
|
+
|
1574
|
+
# Extract question text enable multiple lines
|
1575
|
+
question_text = extract_question(markdown_content)
|
1564
1576
|
|
1565
1577
|
# Extract OPTIONS (lines after #### options)
|
1566
1578
|
options_match = re.search(
|
@@ -0,0 +1,93 @@
|
|
1
|
+
import argparse
|
2
|
+
import os
|
3
|
+
import nbformat as nbf
|
4
|
+
|
5
|
+
|
6
|
+
class MarkdownToNotebook:
|
7
|
+
def __init__(self, markdown_file: str):
|
8
|
+
"""
|
9
|
+
Initializes the MarkdownToNotebook converter with the Markdown file path.
|
10
|
+
|
11
|
+
Args:
|
12
|
+
markdown_file (str): Path to the Markdown file to convert.
|
13
|
+
"""
|
14
|
+
self.markdown_file = markdown_file
|
15
|
+
|
16
|
+
def convert_and_save(self):
|
17
|
+
"""
|
18
|
+
Converts the Markdown file into a Jupyter Notebook and saves it with the same name.
|
19
|
+
"""
|
20
|
+
if not os.path.exists(self.markdown_file):
|
21
|
+
print(f"Error: File '{self.markdown_file}' does not exist.")
|
22
|
+
return
|
23
|
+
|
24
|
+
notebook_name = os.path.splitext(self.markdown_file)[0] + ".ipynb"
|
25
|
+
|
26
|
+
with open(self.markdown_file, "r") as f:
|
27
|
+
content = f.read()
|
28
|
+
|
29
|
+
# Split content into lines
|
30
|
+
lines = content.splitlines()
|
31
|
+
nb = nbf.v4.new_notebook()
|
32
|
+
cells = []
|
33
|
+
current_cell = ""
|
34
|
+
current_type = None
|
35
|
+
|
36
|
+
for line in lines:
|
37
|
+
if line.startswith("# %%"):
|
38
|
+
if current_cell: # Save the previous cell
|
39
|
+
if current_type == "markdown":
|
40
|
+
cells.append(nbf.v4.new_markdown_cell(current_cell.strip()))
|
41
|
+
elif current_type == "code":
|
42
|
+
cells.append(nbf.v4.new_code_cell(current_cell.strip()))
|
43
|
+
elif current_type == "raw":
|
44
|
+
cells.append(nbf.v4.new_raw_cell(current_cell.strip()))
|
45
|
+
|
46
|
+
# Start a new cell
|
47
|
+
current_cell = ""
|
48
|
+
if "[markdown]" in line:
|
49
|
+
current_type = "markdown"
|
50
|
+
elif "# BEGIN" in line or "# END" in line:
|
51
|
+
current_type = "raw"
|
52
|
+
else:
|
53
|
+
current_type = "code"
|
54
|
+
else:
|
55
|
+
# Append lines to the current cell
|
56
|
+
if current_type == "markdown" and line.startswith("# "):
|
57
|
+
current_cell += line[2:] + "\n"
|
58
|
+
else:
|
59
|
+
current_cell += line + "\n"
|
60
|
+
|
61
|
+
# Save the last cell
|
62
|
+
if current_cell:
|
63
|
+
if current_type == "markdown":
|
64
|
+
cells.append(nbf.v4.new_markdown_cell(current_cell.strip()))
|
65
|
+
elif current_type == "code":
|
66
|
+
cells.append(nbf.v4.new_code_cell(current_cell.strip()))
|
67
|
+
elif current_type == "raw":
|
68
|
+
cells.append(nbf.v4.new_raw_cell(current_cell.strip()))
|
69
|
+
|
70
|
+
nb["cells"] = cells
|
71
|
+
|
72
|
+
# Write the notebook
|
73
|
+
with open(notebook_name, "w") as f:
|
74
|
+
nbf.write(nb, f)
|
75
|
+
|
76
|
+
print(f"Notebook saved as: {notebook_name}")
|
77
|
+
|
78
|
+
|
79
|
+
def main():
|
80
|
+
parser = argparse.ArgumentParser(
|
81
|
+
description="Convert a Markdown file with Jupyter-style cells into a Jupyter Notebook."
|
82
|
+
)
|
83
|
+
parser.add_argument(
|
84
|
+
"markdown_file", type=str, help="Path to the Markdown file to convert."
|
85
|
+
)
|
86
|
+
|
87
|
+
args = parser.parse_args()
|
88
|
+
converter = MarkdownToNotebook(markdown_file=args.markdown_file)
|
89
|
+
converter.convert_and_save()
|
90
|
+
|
91
|
+
|
92
|
+
if __name__ == "__main__":
|
93
|
+
main()
|
@@ -4,6 +4,7 @@ import panel as pn
|
|
4
4
|
|
5
5
|
from ..utils import list_of_lists
|
6
6
|
from ..widgets_base.select import SelectQuestion
|
7
|
+
from .question_processor import process_questions_and_codes
|
7
8
|
|
8
9
|
#
|
9
10
|
# Style function
|
@@ -15,14 +16,22 @@ def MCQ(
|
|
15
16
|
options: list[str] | list[list[str]],
|
16
17
|
initial_vals: list[str],
|
17
18
|
) -> Tuple[list[pn.pane.HTML], list[pn.widgets.RadioButtonGroup]]:
|
18
|
-
desc_width = "350px"
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
# Process descriptions through `process_questions_and_codes`
|
21
|
+
processed_titles, code_blocks = process_questions_and_codes(descriptions)
|
22
|
+
|
23
|
+
# Create rows for each description and its code block
|
24
|
+
desc_widgets = []
|
25
|
+
for title, code_block in zip(processed_titles, code_blocks):
|
26
|
+
# Create an HTML pane for the title
|
27
|
+
title_pane = pn.pane.HTML(
|
28
|
+
f"<div style='text-align: left; width: 100%;'><b>{title}</b></div>"
|
23
29
|
)
|
24
|
-
|
25
|
-
|
30
|
+
# Add the title and code block in a row
|
31
|
+
if code_block:
|
32
|
+
desc_widgets.append(pn.Column(title_pane, code_block, sizing_mode="stretch_width"))
|
33
|
+
else:
|
34
|
+
desc_widgets.append(pn.Column(title_pane, sizing_mode="stretch_width"))
|
26
35
|
|
27
36
|
radio_buttons = [
|
28
37
|
pn.widgets.RadioBoxGroup(
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import re
|
2
|
+
import panel as pn
|
3
|
+
|
4
|
+
|
5
|
+
def process_questions_and_codes(titles):
|
6
|
+
# Ensure titles is a list
|
7
|
+
if isinstance(titles, str):
|
8
|
+
titles = [titles]
|
9
|
+
|
10
|
+
processed_titles = []
|
11
|
+
code_blocks = []
|
12
|
+
|
13
|
+
for title in titles:
|
14
|
+
# Split the title at the "```python" delimiter
|
15
|
+
parts = title.split("```python", maxsplit=1)
|
16
|
+
|
17
|
+
# First part is the title, stripped of leading/trailing whitespace
|
18
|
+
title_without_code = parts[0].strip()
|
19
|
+
|
20
|
+
# Second part (if exists) contains the code block; split at closing ```
|
21
|
+
code = parts[1].split("```", maxsplit=1)[0].strip() if len(parts) > 1 else ""
|
22
|
+
|
23
|
+
# Append processed title
|
24
|
+
processed_titles.append(title_without_code)
|
25
|
+
|
26
|
+
# Append code block as Markdown if it exists
|
27
|
+
if code:
|
28
|
+
code_blocks.append(
|
29
|
+
pn.pane.Markdown(f"```python\n{code}\n```")
|
30
|
+
)
|
31
|
+
else:
|
32
|
+
code_blocks.append(None)
|
33
|
+
|
34
|
+
return processed_titles, code_blocks
|
File without changes
|
File without changes
|
File without changes
|