ratatui_ruby 0.4.0 → 0.5.0

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.
Files changed (351) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +1 -1
  3. data/.builds/ruby-3.3.yml +1 -1
  4. data/.builds/ruby-3.4.yml +1 -1
  5. data/.builds/ruby-4.0.0.yml +1 -1
  6. data/AGENTS.md +87 -171
  7. data/CHANGELOG.md +38 -1
  8. data/README.md +8 -3
  9. data/REUSE.toml +20 -0
  10. data/doc/application_architecture.md +105 -45
  11. data/doc/application_testing.md +5 -3
  12. data/doc/contributors/design/ruby_frontend.md +9 -5
  13. data/doc/contributors/developing_examples.md +76 -18
  14. data/doc/contributors/documentation_style.md +7 -0
  15. data/doc/contributors/index.md +2 -0
  16. data/doc/event_handling.md +10 -4
  17. data/doc/images/app_all_events.png +0 -0
  18. data/doc/images/app_color_picker.png +0 -0
  19. data/doc/images/verify_readme_usage.png +0 -0
  20. data/doc/images/widget_barchart_demo.png +0 -0
  21. data/doc/images/widget_block_padding.png +0 -0
  22. data/doc/images/widget_block_titles.png +0 -0
  23. data/doc/images/widget_box_demo.png +0 -0
  24. data/doc/images/widget_calendar_demo.png +0 -0
  25. data/doc/images/widget_cell_demo.png +0 -0
  26. data/doc/images/widget_chart_demo.png +0 -0
  27. data/doc/images/widget_gauge_demo.png +0 -0
  28. data/doc/images/widget_layout_split.png +0 -0
  29. data/doc/images/widget_line_gauge_demo.png +0 -0
  30. data/doc/images/widget_list_demo.png +0 -0
  31. data/doc/images/widget_ratatui_logo_demo.png +0 -0
  32. data/doc/images/widget_ratatui_mascot_demo.png +0 -0
  33. data/doc/images/widget_render.png +0 -0
  34. data/doc/images/widget_scrollbar_demo.png +0 -0
  35. data/doc/images/widget_sparkline_demo.png +0 -0
  36. data/doc/images/widget_style_colors.png +0 -0
  37. data/doc/images/widget_table_flex.png +0 -0
  38. data/doc/images/widget_tabs_demo.png +0 -0
  39. data/doc/interactive_design.md +25 -30
  40. data/doc/quickstart.md +147 -120
  41. data/examples/app_all_events/README.md +81 -0
  42. data/examples/app_all_events/app.rb +93 -0
  43. data/examples/app_all_events/model/event_color_cycle.rb +41 -0
  44. data/examples/app_all_events/model/event_entry.rb +75 -0
  45. data/examples/app_all_events/model/events.rb +180 -0
  46. data/examples/app_all_events/model/highlight.rb +57 -0
  47. data/examples/app_all_events/model/timestamp.rb +54 -0
  48. data/examples/app_all_events/test/snapshots/after_focus_lost.txt +24 -0
  49. data/examples/app_all_events/test/snapshots/after_focus_regained.txt +24 -0
  50. data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +24 -0
  51. data/examples/app_all_events/test/snapshots/after_key_a.txt +24 -0
  52. data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +24 -0
  53. data/examples/app_all_events/test/snapshots/after_mouse_click.txt +24 -0
  54. data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +24 -0
  55. data/examples/app_all_events/test/snapshots/after_multiple_events.txt +24 -0
  56. data/examples/app_all_events/test/snapshots/after_paste.txt +24 -0
  57. data/examples/app_all_events/test/snapshots/after_resize.txt +24 -0
  58. data/examples/app_all_events/test/snapshots/after_right_click.txt +24 -0
  59. data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +24 -0
  60. data/examples/app_all_events/test/snapshots/initial_state.txt +24 -0
  61. data/examples/app_all_events/view/app_view.rb +78 -0
  62. data/examples/app_all_events/view/controls_view.rb +50 -0
  63. data/examples/app_all_events/view/counts_view.rb +55 -0
  64. data/examples/app_all_events/view/live_view.rb +69 -0
  65. data/examples/app_all_events/view/log_view.rb +60 -0
  66. data/examples/app_all_events/view.rb +7 -0
  67. data/examples/app_all_events/view_state.rb +42 -0
  68. data/examples/app_color_picker/README.md +94 -0
  69. data/examples/app_color_picker/app.rb +112 -0
  70. data/examples/app_color_picker/clipboard.rb +84 -0
  71. data/examples/app_color_picker/color.rb +191 -0
  72. data/examples/app_color_picker/copy_dialog.rb +170 -0
  73. data/examples/app_color_picker/harmony.rb +56 -0
  74. data/examples/app_color_picker/input.rb +142 -0
  75. data/examples/app_color_picker/palette.rb +80 -0
  76. data/examples/app_color_picker/scene.rb +201 -0
  77. data/examples/{login_form → app_login_form}/app.rb +39 -42
  78. data/examples/{map_demo → app_map_demo}/app.rb +24 -21
  79. data/examples/{table_select → app_table_select}/app.rb +68 -65
  80. data/examples/{quickstart_dsl → verify_quickstart_dsl}/app.rb +15 -6
  81. data/examples/verify_quickstart_layout/app.rb +69 -0
  82. data/examples/{quickstart_lifecycle → verify_quickstart_lifecycle}/app.rb +19 -10
  83. data/examples/verify_readme_usage/app.rb +34 -0
  84. data/examples/widget_barchart_demo/app.rb +238 -0
  85. data/examples/{block_padding → widget_block_padding}/app.rb +17 -13
  86. data/examples/{block_titles → widget_block_titles}/app.rb +25 -17
  87. data/examples/{box_demo → widget_box_demo}/app.rb +99 -65
  88. data/examples/widget_calendar_demo/app.rb +109 -0
  89. data/examples/widget_cell_demo/app.rb +104 -0
  90. data/examples/widget_chart_demo/app.rb +213 -0
  91. data/examples/widget_gauge_demo/app.rb +212 -0
  92. data/examples/widget_layout_split/app.rb +246 -0
  93. data/examples/widget_line_gauge_demo/app.rb +217 -0
  94. data/examples/widget_list_demo/app.rb +382 -0
  95. data/examples/widget_list_styles/app.rb +141 -0
  96. data/examples/widget_popup_demo/app.rb +104 -0
  97. data/examples/widget_ratatui_logo_demo/app.rb +103 -0
  98. data/examples/widget_ratatui_mascot_demo/app.rb +93 -0
  99. data/examples/widget_rect/app.rb +205 -0
  100. data/examples/widget_render/app.rb +184 -0
  101. data/examples/widget_rich_text/app.rb +137 -0
  102. data/examples/widget_scroll_text/app.rb +108 -0
  103. data/examples/widget_scrollbar_demo/app.rb +153 -0
  104. data/examples/widget_sparkline_demo/app.rb +274 -0
  105. data/examples/widget_style_colors/app.rb +19 -21
  106. data/examples/widget_table_flex/app.rb +95 -0
  107. data/examples/widget_tabs_demo/app.rb +167 -0
  108. data/ext/ratatui_ruby/Cargo.lock +1 -1
  109. data/ext/ratatui_ruby/Cargo.toml +1 -1
  110. data/ext/ratatui_ruby/src/events.rs +121 -36
  111. data/ext/ratatui_ruby/src/frame.rs +115 -0
  112. data/ext/ratatui_ruby/src/lib.rs +79 -26
  113. data/ext/ratatui_ruby/src/rendering.rs +8 -4
  114. data/ext/ratatui_ruby/src/style.rs +138 -57
  115. data/ext/ratatui_ruby/src/terminal.rs +5 -9
  116. data/ext/ratatui_ruby/src/text.rs +13 -6
  117. data/ext/ratatui_ruby/src/widgets/barchart.rs +56 -54
  118. data/ext/ratatui_ruby/src/widgets/block.rs +7 -6
  119. data/ext/ratatui_ruby/src/widgets/canvas.rs +21 -3
  120. data/ext/ratatui_ruby/src/widgets/chart.rs +20 -10
  121. data/ext/ratatui_ruby/src/widgets/layout.rs +9 -4
  122. data/ext/ratatui_ruby/src/widgets/list.rs +32 -9
  123. data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
  124. data/ext/ratatui_ruby/src/widgets/paragraph.rs +1 -1
  125. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +19 -8
  126. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +17 -10
  127. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +4 -2
  128. data/ext/ratatui_ruby/src/widgets/sparkline.rs +14 -11
  129. data/ext/ratatui_ruby/src/widgets/table.rs +8 -4
  130. data/ext/ratatui_ruby/src/widgets/tabs.rs +11 -11
  131. data/lib/ratatui_ruby/cell.rb +3 -3
  132. data/lib/ratatui_ruby/event/key.rb +1 -1
  133. data/lib/ratatui_ruby/event/none.rb +43 -0
  134. data/lib/ratatui_ruby/event.rb +56 -4
  135. data/lib/ratatui_ruby/frame.rb +87 -0
  136. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +11 -11
  137. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +1 -5
  138. data/lib/ratatui_ruby/schema/bar_chart.rb +217 -217
  139. data/lib/ratatui_ruby/schema/block.rb +163 -168
  140. data/lib/ratatui_ruby/schema/calendar.rb +66 -67
  141. data/lib/ratatui_ruby/schema/canvas.rb +63 -63
  142. data/lib/ratatui_ruby/schema/center.rb +46 -46
  143. data/lib/ratatui_ruby/schema/chart.rb +135 -143
  144. data/lib/ratatui_ruby/schema/clear.rb +42 -42
  145. data/lib/ratatui_ruby/schema/constraint.rb +76 -76
  146. data/lib/ratatui_ruby/schema/cursor.rb +25 -25
  147. data/lib/ratatui_ruby/schema/gauge.rb +53 -53
  148. data/lib/ratatui_ruby/schema/layout.rb +87 -87
  149. data/lib/ratatui_ruby/schema/line_gauge.rb +62 -62
  150. data/lib/ratatui_ruby/schema/list.rb +86 -84
  151. data/lib/ratatui_ruby/schema/overlay.rb +31 -31
  152. data/lib/ratatui_ruby/schema/paragraph.rb +80 -80
  153. data/lib/ratatui_ruby/schema/ratatui_logo.rb +10 -6
  154. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +10 -5
  155. data/lib/ratatui_ruby/schema/rect.rb +60 -60
  156. data/lib/ratatui_ruby/schema/scrollbar.rb +119 -119
  157. data/lib/ratatui_ruby/schema/shape/label.rb +1 -1
  158. data/lib/ratatui_ruby/schema/sparkline.rb +111 -110
  159. data/lib/ratatui_ruby/schema/style.rb +46 -46
  160. data/lib/ratatui_ruby/schema/table.rb +112 -119
  161. data/lib/ratatui_ruby/schema/tabs.rb +66 -67
  162. data/lib/ratatui_ruby/session/autodoc.rb +417 -0
  163. data/lib/ratatui_ruby/session.rb +40 -23
  164. data/lib/ratatui_ruby/test_helper.rb +185 -19
  165. data/lib/ratatui_ruby/version.rb +1 -1
  166. data/lib/ratatui_ruby.rb +65 -39
  167. data/{examples/sparkline_demo → sig/examples/app_all_events}/app.rbs +3 -2
  168. data/sig/examples/app_all_events/model/event_entry.rbs +16 -0
  169. data/sig/examples/app_all_events/model/events.rbs +15 -0
  170. data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
  171. data/sig/examples/app_all_events/view/app_view.rbs +8 -0
  172. data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
  173. data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
  174. data/sig/examples/app_all_events/view/live_view.rbs +6 -0
  175. data/sig/examples/app_all_events/view/log_view.rbs +6 -0
  176. data/sig/examples/app_all_events/view.rbs +8 -0
  177. data/sig/examples/app_all_events/view_state.rbs +15 -0
  178. data/{examples/list_demo → sig/examples/app_color_picker}/app.rbs +2 -2
  179. data/sig/examples/app_login_form/app.rbs +11 -0
  180. data/sig/examples/app_map_demo/app.rbs +11 -0
  181. data/sig/examples/app_table_select/app.rbs +11 -0
  182. data/sig/examples/verify_quickstart_dsl/app.rbs +11 -0
  183. data/sig/examples/verify_quickstart_lifecycle/app.rbs +11 -0
  184. data/sig/examples/verify_readme_usage/app.rbs +11 -0
  185. data/sig/examples/widget_block_padding/app.rbs +11 -0
  186. data/sig/examples/widget_block_titles/app.rbs +11 -0
  187. data/sig/examples/widget_box_demo/app.rbs +11 -0
  188. data/sig/examples/widget_calendar_demo/app.rbs +11 -0
  189. data/sig/examples/widget_cell_demo/app.rbs +11 -0
  190. data/sig/examples/widget_chart_demo/app.rbs +11 -0
  191. data/{examples/gauge_demo → sig/examples/widget_gauge_demo}/app.rbs +4 -0
  192. data/sig/examples/widget_layout_split/app.rbs +10 -0
  193. data/sig/examples/widget_line_gauge_demo/app.rbs +11 -0
  194. data/sig/examples/widget_list_demo/app.rbs +12 -0
  195. data/sig/examples/widget_list_styles/app.rbs +11 -0
  196. data/sig/examples/widget_popup_demo/app.rbs +11 -0
  197. data/sig/examples/widget_ratatui_logo_demo/app.rbs +11 -0
  198. data/sig/examples/widget_ratatui_mascot_demo/app.rbs +11 -0
  199. data/sig/examples/widget_rect/app.rbs +12 -0
  200. data/sig/examples/widget_render/app.rbs +10 -0
  201. data/sig/examples/widget_rich_text/app.rbs +11 -0
  202. data/sig/examples/widget_scroll_text/app.rbs +11 -0
  203. data/sig/examples/widget_scrollbar_demo/app.rbs +11 -0
  204. data/sig/examples/widget_sparkline_demo/app.rbs +10 -0
  205. data/{examples → sig/examples}/widget_style_colors/app.rbs +1 -1
  206. data/sig/examples/widget_table_flex/app.rbs +11 -0
  207. data/sig/ratatui_ruby/frame.rbs +9 -0
  208. data/sig/ratatui_ruby/ratatui_ruby.rbs +3 -2
  209. data/sig/ratatui_ruby/schema/draw.rbs +4 -0
  210. data/sig/ratatui_ruby/schema/layout.rbs +1 -1
  211. data/sig/ratatui_ruby/session.rbs +94 -0
  212. data/tasks/autodoc/inventory.rb +61 -0
  213. data/tasks/autodoc/member.rb +56 -0
  214. data/tasks/autodoc/name.rb +19 -0
  215. data/tasks/autodoc/notice.rb +26 -0
  216. data/tasks/autodoc/rbs.rb +38 -0
  217. data/tasks/autodoc/rdoc.rb +45 -0
  218. data/tasks/autodoc.rake +47 -0
  219. data/tasks/bump/history.rb +2 -2
  220. data/tasks/doc.rake +600 -6
  221. data/tasks/example_viewer.html.erb +172 -0
  222. data/tasks/lint.rake +8 -4
  223. data/tasks/resources/index.html.erb +6 -0
  224. data/tasks/sourcehut.rake +4 -4
  225. data/tasks/terminal_preview/app_screenshot.rb +1 -3
  226. data/tasks/terminal_preview/crash_report.rb +7 -9
  227. data/tasks/terminal_preview/launcher_script.rb +4 -6
  228. data/tasks/terminal_preview/preview_collection.rb +4 -6
  229. data/tasks/terminal_preview/safety_confirmation.rb +3 -5
  230. data/tasks/terminal_preview/saved_screenshot.rb +7 -9
  231. data/tasks/terminal_preview/terminal_window.rb +7 -9
  232. data/tasks/test.rake +1 -1
  233. data/tasks/website/index_page.rb +3 -3
  234. data/tasks/website/version.rb +10 -10
  235. data/tasks/website/version_menu.rb +10 -12
  236. data/tasks/website/versioned_documentation.rb +49 -17
  237. data/tasks/website/website.rb +6 -8
  238. data/tasks/website.rake +4 -4
  239. metadata +156 -125
  240. data/LICENSES/BSD-2-Clause.txt +0 -9
  241. data/doc/contributors/better_dx.md +0 -543
  242. data/doc/contributors/example_analysis.md +0 -82
  243. data/doc/images/all_events.png +0 -0
  244. data/doc/images/block_padding.png +0 -0
  245. data/doc/images/block_titles.png +0 -0
  246. data/doc/images/box_demo.png +0 -0
  247. data/doc/images/calendar_demo.png +0 -0
  248. data/doc/images/cell_demo.png +0 -0
  249. data/doc/images/chart_demo.png +0 -0
  250. data/doc/images/flex_layout.png +0 -0
  251. data/doc/images/gauge_demo.png +0 -0
  252. data/doc/images/line_gauge_demo.png +0 -0
  253. data/doc/images/list_demo.png +0 -0
  254. data/doc/images/readme_usage.png +0 -0
  255. data/doc/images/scrollbar_demo.png +0 -0
  256. data/doc/images/sparkline_demo.png +0 -0
  257. data/doc/images/table_flex.png +0 -0
  258. data/examples/all_events/app.rb +0 -169
  259. data/examples/all_events/app.rbs +0 -7
  260. data/examples/all_events/test_app.rb +0 -139
  261. data/examples/analytics/app.rb +0 -258
  262. data/examples/analytics/app.rbs +0 -7
  263. data/examples/analytics/test_app.rb +0 -132
  264. data/examples/block_padding/app.rbs +0 -7
  265. data/examples/block_padding/test_app.rb +0 -31
  266. data/examples/block_titles/app.rbs +0 -7
  267. data/examples/block_titles/test_app.rb +0 -34
  268. data/examples/box_demo/app.rbs +0 -7
  269. data/examples/box_demo/test_app.rb +0 -88
  270. data/examples/calendar_demo/app.rb +0 -101
  271. data/examples/calendar_demo/app.rbs +0 -7
  272. data/examples/calendar_demo/test_app.rb +0 -108
  273. data/examples/cell_demo/app.rb +0 -108
  274. data/examples/cell_demo/app.rbs +0 -7
  275. data/examples/cell_demo/test_app.rb +0 -36
  276. data/examples/chart_demo/app.rb +0 -203
  277. data/examples/chart_demo/app.rbs +0 -7
  278. data/examples/chart_demo/test_app.rb +0 -102
  279. data/examples/custom_widget/app.rb +0 -51
  280. data/examples/custom_widget/app.rbs +0 -7
  281. data/examples/custom_widget/test_app.rb +0 -30
  282. data/examples/flex_layout/app.rb +0 -156
  283. data/examples/flex_layout/app.rbs +0 -7
  284. data/examples/flex_layout/test_app.rb +0 -65
  285. data/examples/gauge_demo/app.rb +0 -182
  286. data/examples/gauge_demo/test_app.rb +0 -120
  287. data/examples/hit_test/app.rb +0 -175
  288. data/examples/hit_test/app.rbs +0 -7
  289. data/examples/hit_test/test_app.rb +0 -102
  290. data/examples/line_gauge_demo/app.rb +0 -190
  291. data/examples/line_gauge_demo/app.rbs +0 -7
  292. data/examples/line_gauge_demo/test_app.rb +0 -129
  293. data/examples/list_demo/app.rb +0 -253
  294. data/examples/list_demo/test_app.rb +0 -237
  295. data/examples/list_styles/app.rb +0 -140
  296. data/examples/list_styles/app.rbs +0 -7
  297. data/examples/list_styles/test_app.rb +0 -157
  298. data/examples/login_form/app.rbs +0 -7
  299. data/examples/login_form/test_app.rb +0 -51
  300. data/examples/map_demo/app.rbs +0 -7
  301. data/examples/map_demo/test_app.rb +0 -149
  302. data/examples/mouse_events/app.rb +0 -97
  303. data/examples/mouse_events/app.rbs +0 -7
  304. data/examples/mouse_events/test_app.rb +0 -53
  305. data/examples/popup_demo/app.rb +0 -103
  306. data/examples/popup_demo/app.rbs +0 -7
  307. data/examples/popup_demo/test_app.rb +0 -54
  308. data/examples/quickstart_dsl/app.rbs +0 -7
  309. data/examples/quickstart_dsl/test_app.rb +0 -29
  310. data/examples/quickstart_lifecycle/app.rbs +0 -7
  311. data/examples/quickstart_lifecycle/test_app.rb +0 -29
  312. data/examples/ratatui_logo_demo/app.rb +0 -79
  313. data/examples/ratatui_logo_demo/app.rbs +0 -7
  314. data/examples/ratatui_logo_demo/test_app.rb +0 -51
  315. data/examples/ratatui_mascot_demo/app.rb +0 -84
  316. data/examples/ratatui_mascot_demo/app.rbs +0 -7
  317. data/examples/ratatui_mascot_demo/test_app.rb +0 -47
  318. data/examples/readme_usage/app.rb +0 -29
  319. data/examples/readme_usage/app.rbs +0 -7
  320. data/examples/readme_usage/test_app.rb +0 -29
  321. data/examples/rich_text/app.rb +0 -141
  322. data/examples/rich_text/app.rbs +0 -7
  323. data/examples/rich_text/test_app.rb +0 -166
  324. data/examples/scroll_text/app.rb +0 -103
  325. data/examples/scroll_text/app.rbs +0 -7
  326. data/examples/scroll_text/test_app.rb +0 -110
  327. data/examples/scrollbar_demo/app.rb +0 -143
  328. data/examples/scrollbar_demo/app.rbs +0 -7
  329. data/examples/scrollbar_demo/test_app.rb +0 -77
  330. data/examples/sparkline_demo/app.rb +0 -240
  331. data/examples/sparkline_demo/test_app.rb +0 -107
  332. data/examples/table_flex/app.rb +0 -65
  333. data/examples/table_flex/app.rbs +0 -7
  334. data/examples/table_flex/test_app.rb +0 -36
  335. data/examples/table_select/app.rbs +0 -7
  336. data/examples/table_select/test_app.rb +0 -180
  337. data/examples/widget_style_colors/test_app.rb +0 -48
  338. /data/doc/images/{analytics.png → app_analytics.png} +0 -0
  339. /data/doc/images/{custom_widget.png → app_custom_widget.png} +0 -0
  340. /data/doc/images/{login_form.png → app_login_form.png} +0 -0
  341. /data/doc/images/{map_demo.png → app_map_demo.png} +0 -0
  342. /data/doc/images/{mouse_events.png → app_mouse_events.png} +0 -0
  343. /data/doc/images/{table_select.png → app_table_select.png} +0 -0
  344. /data/doc/images/{quickstart_dsl.png → verify_quickstart_dsl.png} +0 -0
  345. /data/doc/images/{ratatui_logo_demo.png → verify_quickstart_layout.png} +0 -0
  346. /data/doc/images/{quickstart_lifecycle.png → verify_quickstart_lifecycle.png} +0 -0
  347. /data/doc/images/{list_styles.png → widget_list_styles.png} +0 -0
  348. /data/doc/images/{popup_demo.png → widget_popup_demo.png} +0 -0
  349. /data/doc/images/{hit_test.png → widget_rect.png} +0 -0
  350. /data/doc/images/{rich_text.png → widget_rich_text.png} +0 -0
  351. /data/doc/images/{scroll_text.png → widget_scroll_text.png} +0 -0
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ require_relative "autodoc/inventory"
7
+ require_relative "autodoc/notice"
8
+ require_relative "autodoc/rbs"
9
+ require_relative "autodoc/rdoc"
10
+
11
+ namespace :autodoc do
12
+ desc "Generate all autodoc findings"
13
+ task all: [:rbs, :rdoc]
14
+
15
+ desc "Generate all RBS findings"
16
+ task rbs: ["rbs:session"]
17
+
18
+ desc "Generate all RDoc findings"
19
+ task rdoc: ["rdoc:session"]
20
+
21
+ namespace :rbs do
22
+ desc "Generate RBS for RatatuiRuby::Session"
23
+ task :session do
24
+ require_relative "../lib/ratatui_ruby"
25
+
26
+ Autodoc::Rbs.new(
27
+ path: File.expand_path("../sig/ratatui_ruby/session.rbs", __dir__),
28
+ notice: Autodoc::Notice.new("autodoc:rbs:session")
29
+ ).write(Autodoc::Inventory.new)
30
+ end
31
+ end
32
+
33
+ namespace :rdoc do
34
+ desc "Generate RDoc autodoc for RatatuiRuby::Session"
35
+ task :session do
36
+ require_relative "../lib/ratatui_ruby"
37
+
38
+ Autodoc::Rdoc.new(
39
+ path: File.expand_path("../lib/ratatui_ruby/session/autodoc.rb", __dir__),
40
+ notice: Autodoc::Notice.new("autodoc:rdoc:session")
41
+ ).write(Autodoc::Inventory.new)
42
+ end
43
+ end
44
+ end
45
+
46
+ desc "Generate all autodoc findings"
47
+ task autodoc: "autodoc:all"
@@ -8,7 +8,7 @@ class History
8
8
  # Extracts the history section from the given content, between unreleased and links.
9
9
  def self.parse(content, header_length, unreleased_length, links_text)
10
10
  start = header_length + unreleased_length
11
- text = content[start...content.index(links_text)].strip + "\n"
11
+ text = "#{content[start...(content.index(links_text))].strip}\n"
12
12
  new(text)
13
13
  end
14
14
 
@@ -19,7 +19,7 @@ class History
19
19
 
20
20
  # Adds a new versioned section to the history.
21
21
  def add(section)
22
- @content = "#{section}\n\n#{@content}".strip + "\n"
22
+ @content = "#{"#{section}\n\n#{@content}".strip}\n"
23
23
  nil
24
24
  end
25
25
 
data/tasks/doc.rake CHANGED
@@ -8,18 +8,612 @@ require "rdoc/task"
8
8
  require_relative "rdoc_config"
9
9
 
10
10
  RDoc::Task.new do |rdoc|
11
- rdoc.rdoc_dir = "tmp/rdoc"
11
+ rdoc.rdoc_dir = ENV["RDOC_OUTPUT"] || "tmp/rdoc"
12
12
  rdoc.main = RDocConfig::MAIN
13
+ rdoc.title = ENV["RDOC_TITLE"] if ENV["RDOC_TITLE"]
13
14
  rdoc.rdoc_files.include(RDocConfig::RDOC_FILES)
14
- rdoc.options << "--template-stylesheets=doc/custom.css"
15
+ custom_css = ENV["RDOC_CUSTOM_CSS"] || "doc/custom.css"
16
+ rdoc.options << "--template-stylesheets=#{custom_css}"
17
+ end
18
+
19
+ # Custom RDoc HTML generator that captures headings for TOC
20
+ class CapturingToHtml < RDoc::Markup::ToHtml
21
+ attr_reader :captured_headings
22
+
23
+ def initialize(options, markup = nil)
24
+ super
25
+ @captured_headings = []
26
+ end
27
+
28
+ def accept_heading(heading)
29
+ start_pos = @res.length
30
+ super
31
+ added = @res[start_pos..-1].join
32
+ if added =~ /id="([^"]+)"/
33
+ @captured_headings << { level: heading.level, text: heading.text, id: $1 }
34
+ end
35
+ end
15
36
  end
16
37
 
17
38
  task :copy_doc_images do
39
+ rdoc_dir = ENV["RDOC_OUTPUT"] || "tmp/rdoc"
18
40
  if Dir.exist?("doc/images")
19
- FileUtils.mkdir_p "tmp/rdoc/doc/images"
20
- FileUtils.cp_r Dir["doc/images/*.png"], "tmp/rdoc/doc/images"
41
+ FileUtils.mkdir_p "#{rdoc_dir}/doc/images"
42
+ FileUtils.cp_r Dir["doc/images/*.png"], "#{rdoc_dir}/doc/images"
43
+ FileUtils.cp_r Dir["doc/images/*.gif"], "#{rdoc_dir}/doc/images"
44
+ end
45
+ end
46
+
47
+ def build_tree(all_files, root_dir, max_depth = nil, current_depth = 1)
48
+ return {} if max_depth && current_depth > max_depth
49
+
50
+ files_by_dir = all_files.group_by { |f| File.dirname(f) }
51
+ dirs_at_level = files_by_dir.keys.select { |d| d.start_with?(root_dir) && d.count("/") == current_depth }
52
+
53
+ tree = {}
54
+
55
+ dirs_at_level.each do |dir|
56
+ dir_name = dir.split("/").last
57
+ files = files_by_dir[dir] || []
58
+ subdirs = files_by_dir.keys.select { |d| d.start_with?("#{dir}/") && d.count("/") == current_depth + 1 }
59
+
60
+ tree[dir_name] = {
61
+ path: dir.sub("examples/", ""),
62
+ files: files.map { |f|
63
+ {
64
+ name: File.basename(f),
65
+ path: "#{File.basename(f).gsub('.', '_')}.html",
66
+ full_path: f,
67
+ }
68
+ }.sort_by { |f| f[:name] },
69
+ subdirs: build_tree(all_files, dir, max_depth, current_depth + 1),
70
+ }
71
+ end
72
+
73
+ tree
74
+ end
75
+
76
+ def extract_rdoc_info(content, filename)
77
+ require "rdoc/comment"
78
+ require "rdoc/markup/to_html"
79
+
80
+ options = RDoc::Options.new
81
+ store = RDoc::Store.new(options)
82
+ top_level = store.add_file filename
83
+ stats = RDoc::Stats.new(store, 1)
84
+
85
+ parser = RDoc::Parser::Ruby.new(top_level, content, options, stats)
86
+ parser.scan
87
+
88
+ lines = content.lines
89
+
90
+ # Find the first class/module defined in the file
91
+ # We want the one that appears earliest in the file
92
+ # Filter out items with nil lines
93
+ target_class = top_level.classes_or_modules.select(&:line).min_by(&:line)
94
+
95
+ if target_class
96
+ # Use the class definition line as the anchor
97
+ # RDoc line numbers are 1-based
98
+ anchor_index = target_class.line - 1
99
+ title = target_class.name
100
+ search_snippet = target_class.respond_to?(:search_snippet) ? target_class.search_snippet : ""
101
+ else
102
+ # Fallback to first line of code if no class defined
103
+ first_code_index = lines.find_index { |l| !l.strip.empty? && !l.strip.start_with?("#") }
104
+ anchor_index = first_code_index
105
+ title = nil
106
+ search_snippet = ""
107
+ end
108
+
109
+ # Walk upwards from the line before the anchor to find immediate comments
110
+ comment_lines = []
111
+ if anchor_index && anchor_index > 0
112
+ idx = anchor_index - 1
113
+ while idx >= 0
114
+ line = lines[idx].strip
115
+
116
+ # Stop at blank lines
117
+ break if line.empty?
118
+
119
+ # Stop if we hit something that isn't a comment (shouldn't happen if we are above code, but safety check)
120
+ break unless line.start_with?("#")
121
+
122
+ comment_lines.unshift(lines[idx])
123
+ idx -= 1
124
+ end
21
125
  end
126
+
127
+ raw_comment = nil
128
+ unless comment_lines.empty?
129
+ # Create RDoc comment from extracted lines
130
+ # Strip leading # and optional space
131
+ cleaned_lines = comment_lines.map do |line|
132
+ line.strip.sub(/^#\s?/, "")
133
+ end
134
+ raw_comment = cleaned_lines.join("\n")
135
+ # Use first line of comment as snippet if RDoc didn't provide one
136
+ search_snippet = cleaned_lines.first if search_snippet.empty?
137
+ end
138
+
139
+ {
140
+ title:,
141
+ raw_comment:,
142
+ search_snippet:,
143
+ }
144
+ rescue => e
145
+ puts "Warning: Failed to extract RDoc info for #{filename}: #{e.message}"
146
+ { title: nil, raw_comment: nil, search_snippet: "" }
22
147
  end
23
148
 
24
- Rake::Task[:rdoc].enhance [:copy_doc_images]
25
- Rake::Task[:rerdoc].enhance [:copy_doc_images]
149
+ def render_tree_html(tree_data, current_path, current_file_html, depth = 0)
150
+ html_parts = []
151
+
152
+ # Calculate prefix to return to examples root from current file
153
+ current_depth = current_path.split("/").size - 1
154
+ root_prefix = "../" * current_depth
155
+
156
+ tree_data.keys.sort.each do |dir_name|
157
+ item = tree_data[dir_name]
158
+ has_children = item[:files].any? || item[:subdirs].any?
159
+
160
+ if has_children
161
+ # Check if this directory is in the path of the current file
162
+ # item[:path] is relative to examples root (e.g., "app_all_events/model")
163
+ # current_path is also relative to examples root (e.g., "app_all_events/model/event_color_cycle")
164
+ # We want to open if current_path starts with this directory's path
165
+ is_in_path = current_path == item[:path] || current_path.start_with?("#{item[:path]}/")
166
+ open_attr = is_in_path ? "open" : ""
167
+
168
+ html_parts << "<li>"
169
+ html_parts << " <details #{open_attr}>"
170
+ html_parts << " <summary>#{ERB::Util.html_escape(dir_name)}</summary>"
171
+ html_parts << " <ul class=\"link-list nav-list\">"
172
+
173
+ # Add files
174
+ item[:files].each do |file|
175
+ # Check specific file match
176
+ # file[:path] is the HTML filename (e.g., "event_color_cycle.html")
177
+ # current_file_html is the current file's HTML name
178
+ is_active = is_in_path && file[:path] == current_file_html
179
+
180
+ active_class = is_active ? ' class="active"' : ""
181
+ file_name_display = ERB::Util.html_escape(file[:name])
182
+ file_name_display = "<strong>#{file_name_display}</strong>" if is_active
183
+
184
+ # Link relative to examples root
185
+ file_path = "#{root_prefix}#{item[:path]}/#{file[:path]}"
186
+ html_parts << " <li><a href=\"#{file_path}\"#{active_class}><span class=\"file\"></span>#{file_name_display}</a></li>"
187
+ end
188
+
189
+ # Add subdirectories recursively
190
+ if item[:subdirs].any?
191
+ html_parts << render_tree_html(item[:subdirs], current_path, current_file_html, depth + 1)
192
+ end
193
+
194
+ html_parts << " </ul>"
195
+ html_parts << " </details>"
196
+ html_parts << "</li>"
197
+ end
198
+ end
199
+
200
+ html_parts.join("\n")
201
+ end
202
+
203
+ task :copy_examples do
204
+ puts "Copying examples..."
205
+ require "erb"
206
+
207
+ require "rdoc"
208
+ require "rdoc/markdown"
209
+ require "rdoc/markup/to_html"
210
+
211
+ rdoc_dir = ENV["RDOC_OUTPUT"] || "tmp/rdoc"
212
+
213
+ if Dir.exist?("examples")
214
+ FileUtils.rm_rf "#{rdoc_dir}/examples"
215
+ FileUtils.mkdir_p "#{rdoc_dir}/examples"
216
+
217
+ all_files = Dir.glob("examples/**/*.{rb,md}")
218
+
219
+ template = File.read("tasks/example_viewer.html.erb")
220
+ erb = ERB.new(template)
221
+
222
+ # Find the RDoc icons template
223
+ icons_path = Gem.find_files("rdoc/generator/template/aliki/_icons.rhtml").first
224
+ icons_svg = icons_path ? File.read(icons_path) : ""
225
+
226
+ # Group files by directory
227
+ files_by_dir = all_files.group_by { |f| File.dirname(f) }
228
+
229
+ # Create a binding context for ERB
230
+ class ExampleViewerContext
231
+ attr_reader :breadcrumb_path, :page_title, :file_content_html, :file_header_html
232
+ attr_reader :current_file_html, :tree_data, :doc_root_link, :icons_svg, :relative_path
233
+ attr_reader :toc_items
234
+ attr_accessor :render_tree_helper
235
+
236
+ def initialize(breadcrumb_path, page_title, file_content_html, file_header_html,
237
+ current_file_html, tree_data, doc_root_link, icons_svg, relative_path, toc_items
238
+ )
239
+ @breadcrumb_path = breadcrumb_path
240
+ @page_title = page_title
241
+ @file_content_html = file_content_html
242
+ @file_header_html = file_header_html
243
+
244
+ @current_file_html = current_file_html
245
+ @tree_data = tree_data
246
+ @doc_root_link = doc_root_link
247
+ @icons_svg = icons_svg
248
+ @relative_path = relative_path
249
+ @toc_items = toc_items
250
+ end
251
+
252
+ def render_tree(tree_data, current_path, current_file_html)
253
+ render_tree_helper.call(tree_data, current_path, current_file_html)
254
+ # Output directly to preserve HTML tags
255
+ end
256
+
257
+ def render_toc(items)
258
+ return "" if items.empty?
259
+
260
+ html = []
261
+ html << "<ul>"
262
+ base_level = items.first[:level]
263
+
264
+ items.each_with_index do |item, i|
265
+ level = item[:level]
266
+ text = item[:text]
267
+ id = item[:id]
268
+
269
+ html << "<li><a href=\"##{id}\">#{text}</a>"
270
+
271
+ next_item = items[i + 1]
272
+
273
+ if next_item
274
+ next_level = next_item[:level]
275
+ if next_level > level
276
+ (next_level - level).times { html << "<ul>" }
277
+ elsif next_level < level
278
+ html << "</li>"
279
+ (level - next_level).times { html << "</ul></li>" }
280
+ else # same level
281
+ html << "</li>"
282
+ end
283
+ else
284
+ # Last item. Close everything back to start.
285
+ html << "</li>"
286
+ (level - base_level).times { html << "</ul></li>" }
287
+ end
288
+ end
289
+
290
+ html << "</ul>"
291
+ html.join("\n")
292
+ end
293
+
294
+ def get_binding
295
+ binding
296
+ end
297
+ end
298
+
299
+ # Collect search index entries
300
+ search_entries = []
301
+
302
+ # Generate HTML files for each file
303
+ all_files.each do |file_path|
304
+ relative_path = file_path.sub("examples/", "")
305
+ target_dir = "#{rdoc_dir}/examples/#{File.dirname(relative_path)}"
306
+ FileUtils.mkdir_p target_dir
307
+
308
+ content = File.read(file_path)
309
+ ext = File.extname(file_path)
310
+ filename = File.basename(file_path)
311
+ toc_items = []
312
+
313
+ if ext == ".md"
314
+ # Markdown files usually have their own H1, so no header needed
315
+ page_title = filename
316
+ breadcrumb_path = relative_path
317
+ file_header_html = ""
318
+
319
+ # Parse markdown
320
+ doc = RDoc::Markdown.parse(content)
321
+
322
+ # Render and capture headings
323
+ options = RDoc::Options.new
324
+ renderer = CapturingToHtml.new(options)
325
+ file_content_html = doc.accept(renderer)
326
+ toc_items = renderer.captured_headings
327
+
328
+ # For Markdown, if we assume the first header is the title and is already captured,
329
+ # we might not need to prepend anything.
330
+ # But if file_header_html is empty, the H1 is in file_content_html.
331
+ # CapturingToHtml should have captured it.
332
+ else
333
+ info = extract_rdoc_info(content, filename)
334
+
335
+ if info[:title]
336
+ page_title = info[:title]
337
+ breadcrumb_path = relative_path
338
+ else
339
+ page_title = filename
340
+ breadcrumb_path = "#{File.dirname(relative_path)}/"
341
+ end
342
+
343
+ # Add to search index
344
+ html_path = "#{File.dirname(relative_path)}/#{File.basename(file_path).gsub('.', '_')}.html"
345
+ search_entries << {
346
+ name: page_title,
347
+ full_name: relative_path,
348
+ type: info[:title] ? "class" : "file",
349
+ path: html_path,
350
+ snippet: info[:search_snippet],
351
+ }
352
+
353
+ file_header_html = "<h1 id=\"top\">#{ERB::Util.html_escape(page_title)}</h1>"
354
+
355
+ # Concatenate comment and Source Code section
356
+ parts = []
357
+ if info[:raw_comment] && !info[:raw_comment].strip.empty?
358
+ parts << info[:raw_comment]
359
+ end
360
+
361
+ indented_code = content.gsub(/^/, " ")
362
+ parts << "= Source Code\n\n#{indented_code}"
363
+
364
+ combined_doc = parts.join("\n\n")
365
+
366
+ # Parse and render
367
+ doc = RDoc::Markup.parse(combined_doc)
368
+ options = RDoc::Options.new
369
+ renderer = CapturingToHtml.new(options)
370
+ file_content_html = doc.accept(renderer)
371
+ toc_items = renderer.captured_headings
372
+
373
+ # Add Page Title to TOC
374
+ toc_items.unshift({ level: 1, text: page_title, id: "top" })
375
+ end
376
+
377
+ # Calculate link to doc root
378
+
379
+ # Calculate link to doc root
380
+ depth = relative_path.split("/").size - 1
381
+ doc_root_link = "#{'../' * (depth + 1)}index.html"
382
+
383
+ # Build tree structure for sidebar
384
+ current_file_html = "#{File.basename(file_path).gsub('.', '_')}.html"
385
+ tree_data = build_tree(all_files, "examples", nil)
386
+
387
+ context = ExampleViewerContext.new(breadcrumb_path, page_title, file_content_html, file_header_html,
388
+ current_file_html, tree_data, doc_root_link, icons_svg, relative_path, toc_items)
389
+ context.render_tree_helper = lambda { |tree, path, file|
390
+ render_tree_html(tree, path, file)
391
+ }
392
+ html = erb.result(context.get_binding)
393
+
394
+ html_file = "#{target_dir}/#{File.basename(file_path).gsub('.', '_')}.html"
395
+ File.write(html_file, html)
396
+ end
397
+
398
+ # Write search index for examples
399
+ FileUtils.mkdir_p "#{rdoc_dir}/examples/js"
400
+ search_data = { index: search_entries }
401
+ File.write("#{rdoc_dir}/examples/js/search_data.js", "var search_data = #{JSON.generate(search_data)};")
402
+
403
+ # Copy RDoc search JS files to examples
404
+ rdoc_js_dir = Gem.find_files("rdoc/generator/template/aliki/js").first
405
+ if rdoc_js_dir && Dir.exist?(rdoc_js_dir)
406
+ %w[search_navigation.js search_ranker.js search_controller.js aliki.js].each do |js_file|
407
+ src = File.join(rdoc_js_dir, js_file)
408
+ FileUtils.cp(src, "#{rdoc_dir}/examples/js/#{js_file}") if File.exist?(src)
409
+ end
410
+ end
411
+
412
+ # Generate index.html files for each directory
413
+ files_by_dir.each do |dir, files|
414
+ target_dir = "#{rdoc_dir}/examples/#{dir}".sub("examples/", "")
415
+ FileUtils.mkdir_p target_dir
416
+
417
+ # Get parent directory
418
+ if dir == "examples"
419
+ parent_link = nil
420
+ doc_root_link = "../index.html"
421
+ else
422
+ parent_dir = File.dirname(dir).sub("examples/", "")
423
+ parent_link = (parent_dir == ".") ? "../index.html" : "../index.html"
424
+ depth = dir.sub("examples/", "").split("/").size
425
+ doc_root_link = "#{'../' * (depth + 1)}index.html"
426
+ end
427
+
428
+ # Find subdirectories
429
+ subdirs = files_by_dir.keys.select { |d| File.dirname(d) == dir && d != dir }
430
+
431
+ # Build combined list of folders and files with icons
432
+ items = []
433
+ subdirs.each { |d| items << { type: :dir, name: File.basename(d), path: "#{File.basename(d)}/index.html", icon: "📁" } }
434
+ files.each { |f| items << { type: :file, name: File.basename(f), path: "#{File.basename(f).gsub('.', '_')}.html", icon: "📄" } }
435
+
436
+ # Sort alphabetically
437
+ sorted_items = items.sort_by { |i| i[:name].downcase }
438
+
439
+ index_html = <<~HTML
440
+ <!DOCTYPE html>
441
+ <html lang="en">
442
+ <head>
443
+ <meta charset="UTF-8">
444
+ <meta name="viewport" content="width=device-width, initial-scale=1">
445
+ <title>Examples</title>
446
+ <link href="#{doc_root_link.sub('index.html', '')}css/rdoc.css" rel="stylesheet">
447
+ <link href="#{doc_root_link.sub('index.html', '')}custom.css" rel="stylesheet">
448
+ <script>
449
+ var rdoc_rel_prefix = "#{doc_root_link.sub('index.html', '')}";
450
+ </script>
451
+ </head>
452
+ <body class="file">
453
+ <header class="top-navbar">
454
+ <div class="navbar-brand">Examples</div>
455
+ <div class="navbar-search navbar-search-desktop"></div>
456
+ <button id="theme-toggle" class="theme-toggle" aria-label="Switch to dark mode" type="button" onclick="cycleColorMode()">
457
+ <span class="theme-toggle-icon" aria-hidden="true">🌙</span>
458
+ </button>
459
+ </header>
460
+
461
+ <nav id="navigation" role="navigation">
462
+ <div id="fileindex-section" class="nav-section">
463
+ <h3>Navigation</h3>
464
+ <ul class="nav-list">
465
+ <li><a href="#{doc_root_link}">← Back to Docs</a></li>
466
+ #{parent_link ? "<li><a href=\"#{parent_link}\">↑ Up to parent directory</a></li>" : ''}
467
+ </ul>
468
+ </div>
469
+ </nav>
470
+
471
+ <main role="main">
472
+ <div class="content">
473
+ <ul class="file-list">
474
+ #{sorted_items.map { |item| "<li><a href=\"#{item[:path]}\"><span class=\"icon\">#{item[:icon]}</span>#{item[:name]}#{(item[:type] == :dir) ? '/' : ''}</a></li>" }.join("\n ")}
475
+ </ul>
476
+ </div>
477
+ <div class="footer"><a href="#{doc_root_link}">← Back to docs</a></div>
478
+ </main>
479
+
480
+ <script>
481
+ const modes = ['auto', 'light', 'dark'];
482
+ const icons = { auto: '🌓', light: '☀️', dark: '🌙' };
483
+
484
+ function setColorMode(mode) {
485
+ if (mode === 'auto') {
486
+ document.documentElement.removeAttribute('data-theme');
487
+ const systemTheme = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light';
488
+ document.documentElement.setAttribute('data-theme', systemTheme);
489
+ } else {
490
+ document.documentElement.setAttribute('data-theme', mode);
491
+ }
492
+
493
+ const icon = icons[mode];
494
+ const toggle = document.getElementById('theme-toggle');
495
+ if (toggle) {
496
+ toggle.querySelector('.theme-toggle-icon').textContent = icon;
497
+ }
498
+
499
+ localStorage.setItem('rdoc-theme', mode);
500
+ }
501
+
502
+ function cycleColorMode() {
503
+ const current = localStorage.getItem('rdoc-theme') || 'auto';
504
+ const currentIndex = modes.indexOf(current);
505
+ const nextMode = modes[(currentIndex + 1) % modes.length];
506
+ setColorMode(nextMode);
507
+ }
508
+
509
+ const savedMode = localStorage.getItem('rdoc-theme') || 'auto';
510
+ setColorMode(savedMode);
511
+ </script>
512
+ </body>
513
+ </html>
514
+ HTML
515
+
516
+ File.write("#{target_dir}/index.html", index_html)
517
+ end
518
+
519
+ # Generate root index.html
520
+ root_files = all_files.select { |f| File.dirname(f) == "examples" }
521
+ root_subdirs = files_by_dir.keys.select { |d| File.dirname(d) == "examples" && d != "examples" }
522
+
523
+ # Build combined list of root folders and files with icons
524
+ root_items = []
525
+ root_subdirs.each { |d| root_items << { type: :dir, name: File.basename(d), path: "#{File.basename(d)}/index.html", icon: "📁" } }
526
+ root_files.each { |f| root_items << { type: :file, name: File.basename(f), path: "#{File.basename(f).gsub('.', '_')}.html", icon: "📄" } }
527
+
528
+ # Sort alphabetically
529
+ sorted_root_items = root_items.sort_by { |i| i[:name].downcase }
530
+
531
+ root_index_html = <<~HTML
532
+ <!DOCTYPE html>
533
+ <html lang="en">
534
+ <head>
535
+ <meta charset="UTF-8">
536
+ <meta name="viewport" content="width=device-width, initial-scale=1">
537
+ <title>Examples</title>
538
+ <link href="../css/rdoc.css" rel="stylesheet">
539
+ <link href="../custom.css" rel="stylesheet">
540
+ <script>
541
+ var rdoc_rel_prefix = "../";
542
+ </script>
543
+ </head>
544
+ <body class="file">
545
+ <header class="top-navbar">
546
+ <div class="navbar-brand">Examples</div>
547
+ <div class="navbar-search navbar-search-desktop"></div>
548
+ <button id="theme-toggle" class="theme-toggle" aria-label="Switch to dark mode" type="button" onclick="cycleColorMode()">
549
+ <span class="theme-toggle-icon" aria-hidden="true">🌙</span>
550
+ </button>
551
+ </header>
552
+
553
+ <nav id="navigation" role="navigation">
554
+ <div id="fileindex-section" class="nav-section">
555
+ <h3>Navigation</h3>
556
+ <ul class="nav-list">
557
+ <li><a href="../index.html">← Back to Docs</a></li>
558
+ </ul>
559
+ </div>
560
+ </nav>
561
+
562
+ <main role="main">
563
+ <div class="content">
564
+ <ul class="file-list">
565
+ #{sorted_root_items.map { |item| "<li><a href=\"#{item[:path]}\"><span class=\"icon\">#{item[:icon]}</span>#{item[:name]}#{(item[:type] == :dir) ? '/' : ''}</a></li>" }.join("\n ")}
566
+ </ul>
567
+ </div>
568
+ <div class="footer"><a href="../index.html">← Back to docs</a></div>
569
+ </main>
570
+
571
+ <script>
572
+ const modes = ['auto', 'light', 'dark'];
573
+ const icons = { auto: '🌓', light: '☀️', dark: '🌙' };
574
+
575
+ function setColorMode(mode) {
576
+ if (mode === 'auto') {
577
+ document.documentElement.removeAttribute('data-theme');
578
+ const systemTheme = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light';
579
+ document.documentElement.setAttribute('data-theme', systemTheme);
580
+ } else {
581
+ document.documentElement.setAttribute('data-theme', mode);
582
+ }
583
+
584
+ const icon = icons[mode];
585
+ const toggle = document.getElementById('theme-toggle');
586
+ if (toggle) {
587
+ toggle.querySelector('.theme-toggle-icon').textContent = icon;
588
+ }
589
+
590
+ localStorage.setItem('rdoc-theme', mode);
591
+ }
592
+
593
+ function cycleColorMode() {
594
+ const current = localStorage.getItem('rdoc-theme') || 'auto';
595
+ const currentIndex = modes.indexOf(current);
596
+ const nextMode = modes[(currentIndex + 1) % modes.length];
597
+ setColorMode(nextMode);
598
+ }
599
+
600
+ const savedMode = localStorage.getItem('rdoc-theme') || 'auto';
601
+ setColorMode(savedMode);
602
+ </script>
603
+ </body>
604
+ </html>
605
+ HTML
606
+
607
+ File.write("#{rdoc_dir}/examples/index.html", root_index_html)
608
+ end
609
+ end
610
+
611
+ Rake::Task[:rdoc].enhance do
612
+ Rake::Task[:copy_doc_images].invoke
613
+ Rake::Task[:copy_examples].invoke
614
+ end
615
+
616
+ Rake::Task[:rerdoc].enhance do
617
+ Rake::Task[:copy_doc_images].invoke
618
+ Rake::Task[:copy_examples].invoke
619
+ end