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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: PyKubeGrader
3
- Version: 0.2.37
3
+ Version: 0.2.38
4
4
  Summary: Add a short description here!
5
5
  Home-page: https://github.com/pyscaffold/pyscaffold/
6
6
  Author: jagar2
@@ -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=5jl_TAnxfd9sqWvggJr4i3E7mrEf03cmFDotVrgTZWQ,82697
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=NjD3-uXSnibpUQ0mO3hRp_O-rynFyl0Dz6IXE4tnCRI,2078
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.37.dist-info/LICENSE.txt,sha256=YTp-Ewc8Kems8PJEE27KnBPFnZSxoWvSg7nnknzPyYw,1546
31
- PyKubeGrader-0.2.37.dist-info/METADATA,sha256=r05PaIWX6hTV2O04ezd46J0HI5hmutQoW0kgHARq1E8,2779
32
- PyKubeGrader-0.2.37.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
33
- PyKubeGrader-0.2.37.dist-info/entry_points.txt,sha256=UPMdTT46fQwTYJWtrUwIWIbXbwyOPfNQgBFRa0frWzw,138
34
- PyKubeGrader-0.2.37.dist-info/top_level.txt,sha256=e550Klfze6higFxER1V62fnGOcIgiKRbsrl9CC4UdtQ,13
35
- PyKubeGrader-0.2.37.dist-info/RECORD,,
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,,
@@ -1,3 +1,4 @@
1
1
  [console_scripts]
2
+ markdown-question = pykubegrader.build.markdown_questions:main
2
3
  otter-folder-builder = pykubegrader.build.build_folder:main
3
4
  otter-folder-cleaner = pykubegrader.build.clean_folder:main
@@ -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
- r"^###\s*\*\*(.+)\*\*", markdown_content, re.MULTILINE
1558
- )
1559
- question_text = (
1560
- question_text_match.group(1).strip()
1561
- if question_text_match
1562
- else None
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
- desc_widgets = [
21
- pn.pane.HTML(
22
- f"<div style='text-align: left; width: {desc_width};'><b>{desc}</b></div>"
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
- for desc in descriptions
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