Haleshot commited on
Commit
d3bf5bc
·
unverified ·
1 Parent(s): 1f494a7

Refactor HTML generation in build.py by modularizing components (format) into dedicated functions for better readability and maintainability.

Browse files
Files changed (1) hide show
  1. scripts/build.py +201 -151
scripts/build.py CHANGED
@@ -30,8 +30,33 @@ def export_html_wasm(notebook_path: str, output_dir: str, as_app: bool = False)
30
  os.makedirs(os.path.dirname(output_file), exist_ok=True)
31
 
32
  cmd.extend([notebook_path, "-o", output_file])
33
- subprocess.run(cmd, capture_output=True, text=True, check=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  return True
 
 
 
35
  except subprocess.CalledProcessError as e:
36
  print(f"Error exporting {notebook_path}:")
37
  print(e.stderr)
@@ -880,17 +905,9 @@ def generate_eva_css() -> str:
880
  """
881
 
882
 
883
- def generate_index(courses: Dict[str, Dict[str, Any]], output_dir: str) -> None:
884
- """Generate the index.html file with Neon Genesis Evangelion aesthetics."""
885
- print("Generating index.html")
886
-
887
- index_path = os.path.join(output_dir, "index.html")
888
- os.makedirs(output_dir, exist_ok=True)
889
-
890
- try:
891
- with open(index_path, "w", encoding="utf-8") as f:
892
- f.write(
893
- """<!DOCTYPE html>
894
  <html lang="en" data-theme="light">
895
  <head>
896
  <meta charset="UTF-8">
@@ -898,7 +915,7 @@ def generate_index(courses: Dict[str, Dict[str, Any]], output_dir: str) -> None:
898
  <title>Marimo Learn - Interactive Educational Notebooks</title>
899
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
900
  <style>
901
- """ + generate_eva_css() + """
902
  </style>
903
  </head>
904
  <body>
@@ -915,8 +932,12 @@ def generate_index(courses: Dict[str, Dict[str, Any]], output_dir: str) -> None:
915
  <i class="fas fa-moon"></i>
916
  </button>
917
  </nav>
918
- </header>
 
919
 
 
 
 
920
  <section class="eva-hero">
921
  <h1>Interactive Learning with Marimo<span class="eva-cursor"></span></h1>
922
  <p>
@@ -925,8 +946,12 @@ def generate_index(courses: Dict[str, Dict[str, Any]], output_dir: str) -> None:
925
  Python notebook that makes data exploration delightful.
926
  </p>
927
  <a href="#courses" class="eva-button">Explore Courses</a>
928
- </section>
 
929
 
 
 
 
930
  <section id="features">
931
  <h2 class="eva-section-title">Why Marimo Learn?</h2>
932
  <div class="eva-features">
@@ -946,150 +971,128 @@ def generate_index(courses: Dict[str, Dict[str, Any]], output_dir: str) -> None:
946
  <p>From Python basics to advanced optimization techniques, our courses cover a wide range of topics.</p>
947
  </div>
948
  </div>
949
- </section>
950
 
 
 
 
 
951
  <section id="courses">
952
  <h2 class="eva-section-title">Explore Courses</h2>
953
  <div class="eva-search">
954
  <input type="text" id="courseSearch" placeholder="Search courses and notebooks...">
955
  <span class="eva-search-icon"><i class="fas fa-search"></i></span>
956
  </div>
957
- <div class="eva-courses">
958
- """
959
- )
960
-
961
- # Define the custom order for courses
962
- course_order = ["python", "probability", "polars", "optimization", "functional_programming"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
963
 
964
- # Create a dictionary of courses by ID for easy lookup
965
- courses_by_id = {course["id"]: course for course in courses.values()}
 
966
 
967
- # Determine which courses are "work in progress" based on description or notebook count
968
- work_in_progress = set()
969
- for course_id, course in courses_by_id.items():
970
- # Consider a course as "work in progress" if it has few notebooks or contains specific phrases
971
- if (len(course["notebooks"]) < 5 or
972
- "work in progress" in course["description"].lower() or
973
- "help us add" in course["description"].lower() or
974
- "check back later" in course["description"].lower()):
975
- work_in_progress.add(course_id)
976
 
977
- # First output courses in the specified order
978
- for course_id in course_order:
979
- if course_id in courses_by_id:
980
- course = courses_by_id[course_id]
981
-
982
- # Skip if no notebooks
983
- if not course["notebooks"]:
984
- continue
985
-
986
- # Count notebooks
987
- notebook_count = len(course["notebooks"])
988
-
989
- # Determine if this course is a work in progress
990
- is_wip = course_id in work_in_progress
991
-
992
- f.write(
993
- f'<div class="eva-course" data-course-id="{course["id"]}">\n'
994
- )
995
-
996
- # Add WIP badge if needed
997
- if is_wip:
998
- f.write(f' <div class="eva-course-badge"><i class="fas fa-code-branch"></i> In Progress</div>\n')
999
-
1000
- f.write(
1001
- f' <div class="eva-course-header">\n'
1002
- f' <h2 class="eva-course-title">{course["title"]}</h2>\n'
1003
- f' <span class="eva-course-toggle"><i class="fas fa-chevron-down"></i></span>\n'
1004
- f' </div>\n'
1005
- f' <div class="eva-course-front">\n'
1006
- f' <p class="eva-course-description">{course["description"]}</p>\n'
1007
- f' <div class="eva-course-stats">\n'
1008
- f' <span><i class="fas fa-book"></i> {notebook_count} notebook{"s" if notebook_count != 1 else ""}</span>\n'
1009
- f' </div>\n'
1010
- f' <button class="eva-button eva-course-button">View Notebooks</button>\n'
1011
- f' </div>\n'
1012
- f' <div class="eva-course-content">\n'
1013
- f' <div class="eva-notebooks">\n'
1014
- )
1015
-
1016
- for i, notebook in enumerate(course["notebooks"]):
1017
- # Use original file number instead of sequential numbering
1018
- notebook_number = notebook.get("original_number", f"{i+1:02d}")
1019
- f.write(
1020
- f' <div class="eva-notebook">\n'
1021
- f' <span class="eva-notebook-number">{notebook_number}</span>\n'
1022
- f' <a href="{notebook["path"].replace(".py", ".html")}" data-notebook-title="{notebook["display_name"]}">{notebook["display_name"]}</a>\n'
1023
- f' </div>\n'
1024
- )
1025
-
1026
- f.write(
1027
- f' </div>\n'
1028
- f' </div>\n'
1029
- f'</div>\n'
1030
- )
1031
-
1032
- # Remove from the dictionary so we don't output it again
1033
- del courses_by_id[course_id]
1034
 
1035
- # Then output any remaining courses alphabetically
1036
- sorted_remaining_courses = sorted(courses_by_id.values(), key=lambda x: x["title"])
1037
 
1038
- for course in sorted_remaining_courses:
1039
- # Skip if no notebooks
1040
- if not course["notebooks"]:
1041
- continue
1042
-
1043
- # Count notebooks
1044
- notebook_count = len(course["notebooks"])
1045
-
1046
- # Determine if this course is a work in progress
1047
- is_wip = course["id"] in work_in_progress
1048
-
1049
- f.write(
1050
- f'<div class="eva-course" data-course-id="{course["id"]}">\n'
1051
- )
1052
-
1053
- # Add WIP badge if needed
1054
- if is_wip:
1055
- f.write(f' <div class="eva-course-badge"><i class="fas fa-code-branch"></i> In Progress</div>\n')
1056
-
1057
- f.write(
1058
- f' <div class="eva-course-header">\n'
1059
- f' <h2 class="eva-course-title">{course["title"]}</h2>\n'
1060
- f' <span class="eva-course-toggle"><i class="fas fa-chevron-down"></i></span>\n'
1061
- f' </div>\n'
1062
- f' <div class="eva-course-front">\n'
1063
- f' <p class="eva-course-description">{course["description"]}</p>\n'
1064
- f' <div class="eva-course-stats">\n'
1065
- f' <span><i class="fas fa-book"></i> {notebook_count} notebook{"s" if notebook_count != 1 else ""}</span>\n'
1066
- f' </div>\n'
1067
- f' <button class="eva-button eva-course-button">View Notebooks</button>\n'
1068
- f' </div>\n'
1069
- f' <div class="eva-course-content">\n'
1070
- f' <div class="eva-notebooks">\n'
1071
- )
1072
-
1073
- for i, notebook in enumerate(course["notebooks"]):
1074
- # Use original file number instead of sequential numbering
1075
- notebook_number = notebook.get("original_number", f"{i+1:02d}")
1076
- f.write(
1077
- f' <div class="eva-notebook">\n'
1078
- f' <span class="eva-notebook-number">{notebook_number}</span>\n'
1079
- f' <a href="{notebook["path"].replace(".py", ".html")}" data-notebook-title="{notebook["display_name"]}">{notebook["display_name"]}</a>\n'
1080
- f' </div>\n'
1081
- )
1082
 
1083
- f.write(
1084
- f' </div>\n'
1085
- f' </div>\n'
1086
- f'</div>\n'
1087
- )
1088
-
1089
- f.write(
1090
- """ </div>
1091
- </section>
1092
 
 
 
 
1093
  <section id="contribute" class="eva-cta">
1094
  <h2>Contribute to Marimo Learn</h2>
1095
  <p>
@@ -1099,8 +1102,12 @@ def generate_index(courses: Dict[str, Dict[str, Any]], output_dir: str) -> None:
1099
  <a href="https://github.com/marimo-team/learn" target="_blank" class="eva-button">
1100
  <i class="fab fa-github"></i> Contribute on GitHub
1101
  </a>
1102
- </section>
 
1103
 
 
 
 
1104
  <footer class="eva-footer">
1105
  <div class="eva-footer-logo">
1106
  <a href="https://marimo.io" target="_blank">
@@ -1111,7 +1118,7 @@ def generate_index(courses: Dict[str, Dict[str, Any]], output_dir: str) -> None:
1111
  <a href="https://github.com/marimo-team" target="_blank" aria-label="GitHub"><i class="fab fa-github"></i></a>
1112
  <a href="https://marimo.io/discord?ref=learn" target="_blank" aria-label="Discord"><i class="fab fa-discord"></i></a>
1113
  <a href="https://twitter.com/marimo_io" target="_blank" aria-label="Twitter"><i class="fab fa-twitter"></i></a>
1114
- <a href="https://www.youtube.com/@marimo-io" target="_blank" aria-label="YouTube"><i class="fab fa-youtube"></i></a>
1115
  <a href="https://www.linkedin.com/company/marimo-io" target="_blank" aria-label="LinkedIn"><i class="fab fa-linkedin"></i></a>
1116
  </div>
1117
  <div class="eva-footer-links">
@@ -1122,9 +1129,12 @@ def generate_index(courses: Dict[str, Dict[str, Any]], output_dir: str) -> None:
1122
  <div class="eva-footer-copyright">
1123
  © 2025 Marimo Inc. All rights reserved.
1124
  </div>
1125
- </footer>
1126
- </div>
1127
 
 
 
 
1128
  <script>
1129
  // Set light theme as default immediately
1130
  document.documentElement.setAttribute('data-theme', 'light');
@@ -1391,10 +1401,50 @@ def generate_index(courses: Dict[str, Dict[str, Any]], output_dir: str) -> None:
1391
  });
1392
  });
1393
  });
1394
- </script>
 
 
 
 
 
 
1395
  </body>
1396
  </html>"""
1397
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1398
  except IOError as e:
1399
  print(f"Error generating index.html: {e}")
1400
 
 
30
  os.makedirs(os.path.dirname(output_file), exist_ok=True)
31
 
32
  cmd.extend([notebook_path, "-o", output_file])
33
+ print(f"Running command: {' '.join(cmd)}")
34
+
35
+ # Use Popen to handle interactive prompts
36
+ process = subprocess.Popen(
37
+ cmd,
38
+ stdin=subprocess.PIPE,
39
+ stdout=subprocess.PIPE,
40
+ stderr=subprocess.PIPE,
41
+ text=True
42
+ )
43
+
44
+ # Send 'Y' to the prompt
45
+ stdout, stderr = process.communicate(input="Y\n", timeout=60)
46
+
47
+ if process.returncode != 0:
48
+ print(f"Error exporting {notebook_path}:")
49
+ print(f"Command: {' '.join(cmd)}")
50
+ print(f"Return code: {process.returncode}")
51
+ print(f"Stdout: {stdout}")
52
+ print(f"Stderr: {stderr}")
53
+ return False
54
+
55
+ print(f"Successfully exported {notebook_path} to {output_file}")
56
  return True
57
+ except subprocess.TimeoutExpired:
58
+ print(f"Timeout exporting {notebook_path} - command took too long to execute")
59
+ return False
60
  except subprocess.CalledProcessError as e:
61
  print(f"Error exporting {notebook_path}:")
62
  print(e.stderr)
 
905
  """
906
 
907
 
908
+ def get_html_header():
909
+ """Generate the HTML header with CSS and meta tags."""
910
+ return """<!DOCTYPE html>
 
 
 
 
 
 
 
 
911
  <html lang="en" data-theme="light">
912
  <head>
913
  <meta charset="UTF-8">
 
915
  <title>Marimo Learn - Interactive Educational Notebooks</title>
916
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
917
  <style>
918
+ {css}
919
  </style>
920
  </head>
921
  <body>
 
932
  <i class="fas fa-moon"></i>
933
  </button>
934
  </nav>
935
+ </header>"""
936
+
937
 
938
+ def get_html_hero_section():
939
+ """Generate the hero section of the page."""
940
+ return """
941
  <section class="eva-hero">
942
  <h1>Interactive Learning with Marimo<span class="eva-cursor"></span></h1>
943
  <p>
 
946
  Python notebook that makes data exploration delightful.
947
  </p>
948
  <a href="#courses" class="eva-button">Explore Courses</a>
949
+ </section>"""
950
+
951
 
952
+ def get_html_features_section():
953
+ """Generate the features section of the page."""
954
+ return """
955
  <section id="features">
956
  <h2 class="eva-section-title">Why Marimo Learn?</h2>
957
  <div class="eva-features">
 
971
  <p>From Python basics to advanced optimization techniques, our courses cover a wide range of topics.</p>
972
  </div>
973
  </div>
974
+ </section>"""
975
 
976
+
977
+ def get_html_courses_start():
978
+ """Generate the beginning of the courses section."""
979
+ return """
980
  <section id="courses">
981
  <h2 class="eva-section-title">Explore Courses</h2>
982
  <div class="eva-search">
983
  <input type="text" id="courseSearch" placeholder="Search courses and notebooks...">
984
  <span class="eva-search-icon"><i class="fas fa-search"></i></span>
985
  </div>
986
+ <div class="eva-courses">"""
987
+
988
+
989
+ def generate_course_card(course, notebook_count, is_wip):
990
+ """Generate HTML for a single course card."""
991
+ html = f'<div class="eva-course" data-course-id="{course["id"]}">\n'
992
+
993
+ # Add WIP badge if needed
994
+ if is_wip:
995
+ html += ' <div class="eva-course-badge"><i class="fas fa-code-branch"></i> In Progress</div>\n'
996
+
997
+ html += f''' <div class="eva-course-header">
998
+ <h2 class="eva-course-title">{course["title"]}</h2>
999
+ <span class="eva-course-toggle"><i class="fas fa-chevron-down"></i></span>
1000
+ </div>
1001
+ <div class="eva-course-front">
1002
+ <p class="eva-course-description">{course["description"]}</p>
1003
+ <div class="eva-course-stats">
1004
+ <span><i class="fas fa-book"></i> {notebook_count} notebook{"s" if notebook_count != 1 else ""}</span>
1005
+ </div>
1006
+ <button class="eva-button eva-course-button">View Notebooks</button>
1007
+ </div>
1008
+ <div class="eva-course-content">
1009
+ <div class="eva-notebooks">
1010
+ '''
1011
+
1012
+ # Add notebooks
1013
+ for i, notebook in enumerate(course["notebooks"]):
1014
+ notebook_number = notebook.get("original_number", f"{i+1:02d}")
1015
+ html += f''' <div class="eva-notebook">
1016
+ <span class="eva-notebook-number">{notebook_number}</span>
1017
+ <a href="{notebook["path"].replace(".py", ".html")}" data-notebook-title="{notebook["display_name"]}">{notebook["display_name"]}</a>
1018
+ </div>
1019
+ '''
1020
+
1021
+ html += ''' </div>
1022
+ </div>
1023
+ </div>
1024
+ '''
1025
+ return html
1026
+
1027
+
1028
+ def generate_course_cards(courses):
1029
+ """Generate HTML for all course cards."""
1030
+ html = ""
1031
+
1032
+ # Define the custom order for courses
1033
+ course_order = ["python", "probability", "polars", "optimization", "functional_programming"]
1034
+
1035
+ # Create a dictionary of courses by ID for easy lookup
1036
+ courses_by_id = {course["id"]: course for course in courses.values()}
1037
+
1038
+ # Determine which courses are "work in progress" based on description or notebook count
1039
+ work_in_progress = set()
1040
+ for course_id, course in courses_by_id.items():
1041
+ # Consider a course as "work in progress" if it has few notebooks or contains specific phrases
1042
+ if (len(course["notebooks"]) < 5 or
1043
+ "work in progress" in course["description"].lower() or
1044
+ "help us add" in course["description"].lower() or
1045
+ "check back later" in course["description"].lower()):
1046
+ work_in_progress.add(course_id)
1047
+
1048
+ # First output courses in the specified order
1049
+ for course_id in course_order:
1050
+ if course_id in courses_by_id:
1051
+ course = courses_by_id[course_id]
1052
 
1053
+ # Skip if no notebooks
1054
+ if not course["notebooks"]:
1055
+ continue
1056
 
1057
+ # Count notebooks
1058
+ notebook_count = len(course["notebooks"])
 
 
 
 
 
 
 
1059
 
1060
+ # Determine if this course is a work in progress
1061
+ is_wip = course_id in work_in_progress
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1062
 
1063
+ html += generate_course_card(course, notebook_count, is_wip)
 
1064
 
1065
+ # Remove from the dictionary so we don't output it again
1066
+ del courses_by_id[course_id]
1067
+
1068
+ # Then output any remaining courses alphabetically
1069
+ sorted_remaining_courses = sorted(courses_by_id.values(), key=lambda x: x["title"])
1070
+
1071
+ for course in sorted_remaining_courses:
1072
+ # Skip if no notebooks
1073
+ if not course["notebooks"]:
1074
+ continue
1075
+
1076
+ # Count notebooks
1077
+ notebook_count = len(course["notebooks"])
1078
+
1079
+ # Determine if this course is a work in progress
1080
+ is_wip = course["id"] in work_in_progress
1081
+
1082
+ html += generate_course_card(course, notebook_count, is_wip)
1083
+
1084
+ return html
1085
+
1086
+
1087
+ def get_html_courses_end():
1088
+ """Generate the end of the courses section."""
1089
+ return """ </div>
1090
+ </section>"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1091
 
 
 
 
 
 
 
 
 
 
1092
 
1093
+ def get_html_contribute_section():
1094
+ """Generate the contribute section."""
1095
+ return """
1096
  <section id="contribute" class="eva-cta">
1097
  <h2>Contribute to Marimo Learn</h2>
1098
  <p>
 
1102
  <a href="https://github.com/marimo-team/learn" target="_blank" class="eva-button">
1103
  <i class="fab fa-github"></i> Contribute on GitHub
1104
  </a>
1105
+ </section>"""
1106
+
1107
 
1108
+ def get_html_footer():
1109
+ """Generate the page footer."""
1110
+ return """
1111
  <footer class="eva-footer">
1112
  <div class="eva-footer-logo">
1113
  <a href="https://marimo.io" target="_blank">
 
1118
  <a href="https://github.com/marimo-team" target="_blank" aria-label="GitHub"><i class="fab fa-github"></i></a>
1119
  <a href="https://marimo.io/discord?ref=learn" target="_blank" aria-label="Discord"><i class="fab fa-discord"></i></a>
1120
  <a href="https://twitter.com/marimo_io" target="_blank" aria-label="Twitter"><i class="fab fa-twitter"></i></a>
1121
+ <a href="https://www.youtube.com/@marimo-team" target="_blank" aria-label="YouTube"><i class="fab fa-youtube"></i></a>
1122
  <a href="https://www.linkedin.com/company/marimo-io" target="_blank" aria-label="LinkedIn"><i class="fab fa-linkedin"></i></a>
1123
  </div>
1124
  <div class="eva-footer-links">
 
1129
  <div class="eva-footer-copyright">
1130
  © 2025 Marimo Inc. All rights reserved.
1131
  </div>
1132
+ </footer>"""
1133
+
1134
 
1135
+ def get_html_scripts():
1136
+ """Generate the JavaScript for the page."""
1137
+ return """
1138
  <script>
1139
  // Set light theme as default immediately
1140
  document.documentElement.setAttribute('data-theme', 'light');
 
1401
  });
1402
  });
1403
  });
1404
+ </script>"""
1405
+
1406
+
1407
+ def get_html_footer_closing():
1408
+ """Generate closing HTML tags."""
1409
+ return """
1410
+ </div>
1411
  </body>
1412
  </html>"""
1413
+
1414
+
1415
+ def generate_index(courses: Dict[str, Dict[str, Any]], output_dir: str) -> None:
1416
+ """Generate the index.html file with Neon Genesis Evangelion aesthetics."""
1417
+ print("Generating index.html")
1418
+
1419
+ index_path = os.path.join(output_dir, "index.html")
1420
+ os.makedirs(output_dir, exist_ok=True)
1421
+
1422
+ try:
1423
+ with open(index_path, "w", encoding="utf-8") as f:
1424
+ # Build the page HTML from individual components
1425
+ header = get_html_header().format(css=generate_eva_css())
1426
+ hero = get_html_hero_section()
1427
+ features = get_html_features_section()
1428
+ courses_start = get_html_courses_start()
1429
+ course_cards = generate_course_cards(courses)
1430
+ courses_end = get_html_courses_end()
1431
+ contribute = get_html_contribute_section()
1432
+ footer = get_html_footer()
1433
+ scripts = get_html_scripts()
1434
+ closing = get_html_footer_closing()
1435
+
1436
+ # Write all elements to the file
1437
+ f.write(header)
1438
+ f.write(hero)
1439
+ f.write(features)
1440
+ f.write(courses_start)
1441
+ f.write(course_cards)
1442
+ f.write(courses_end)
1443
+ f.write(contribute)
1444
+ f.write(footer)
1445
+ f.write(scripts)
1446
+ f.write(closing)
1447
+
1448
  except IOError as e:
1449
  print(f"Error generating index.html: {e}")
1450