scriptorium 0.0.2 → 0.6.1

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 (290) hide show
  1. checksums.yaml +4 -4
  2. data/README.lt3 +324 -0
  3. data/README.md +3155 -1
  4. data/assets/.DS_Store +0 -0
  5. data/assets/README.md +44 -0
  6. data/assets/back-icon.png +0 -0
  7. data/assets/icons/facebook.svg +1 -0
  8. data/assets/icons/github.svg +1 -0
  9. data/assets/icons/instagram.svg +1 -0
  10. data/assets/icons/reddit.svg +1 -0
  11. data/assets/icons/ui/.DS_Store +0 -0
  12. data/assets/icons/ui/back.png +0 -0
  13. data/assets/icons/ui/copy.png +0 -0
  14. data/assets/icons/ui/down.png +0 -0
  15. data/assets/icons/ui/end.png +0 -0
  16. data/assets/icons/ui/exit.png +0 -0
  17. data/assets/icons/ui/foo +10 -0
  18. data/assets/icons/ui/home.png +0 -0
  19. data/assets/icons/ui/left.png +0 -0
  20. data/assets/icons/ui/next.png +0 -0
  21. data/assets/icons/ui/right.png +0 -0
  22. data/assets/icons/ui/start.png +0 -0
  23. data/assets/icons/ui/up.png +0 -0
  24. data/assets/icons/x.svg +1 -0
  25. data/assets/icons/youtube.svg +1 -0
  26. data/assets/samples/placeholder.svg +9 -0
  27. data/assets/themes/standard/favicon.svg +6 -0
  28. data/bin/scriptorium +1511 -0
  29. data/doc/README.txt +6 -0
  30. data/doc/anti-amnesia/20250727-054000-scriptorium-overview.md +95 -0
  31. data/doc/anti-amnesia/20250727-060000-api-design-tui-planning.md +34 -0
  32. data/doc/anti-amnesia/20250727-061000-runeblog-tui-analysis.md +50 -0
  33. data/doc/anti-amnesia/20250727-123000-anti-amnesia-conventions.md +31 -0
  34. data/doc/anti-amnesia/20250727-154000-livetext-plugin-file-stats.md +73 -0
  35. data/doc/anti-amnesia/20250727-172600-cursor-rbenv-ruby-version-mystery.md +64 -0
  36. data/doc/anti-amnesia/20250727-172600-unified-minitest-framework.md +70 -0
  37. data/doc/anti-amnesia/20250727-172900-ai-cognitive-assessment-capabilities.md +40 -0
  38. data/doc/anti-amnesia/20250727-173000-widget-testing-achievement.md +110 -0
  39. data/doc/anti-amnesia/20250727-180000-post-id-num-refactoring.md +73 -0
  40. data/doc/anti-amnesia/20250728-124243-aaa-syntax-clarification.md +46 -0
  41. data/doc/anti-amnesia/20250728-124421-conversation-summary-concise.md +124 -0
  42. data/doc/anti-amnesia/20250729-190000-scriptorium-tui-testing-complete.md +46 -0
  43. data/doc/anti-amnesia/20250729-200000-scriptorium-tui-testing-edit-file-workflow.md +97 -0
  44. data/doc/anti-amnesia/20250729-210000-reddit-autopost-integration-complete.md +158 -0
  45. data/doc/anti-amnesia/20250729-211500-dependency-management-system.md +211 -0
  46. data/doc/anti-amnesia/20250729-213000-python-virtual-environment-setup.md +141 -0
  47. data/doc/anti-amnesia/20250729-214500-theme-management-commands.md +211 -0
  48. data/doc/anti-amnesia/20250729-215000-version-update-to-0.6.0.md +134 -0
  49. data/doc/anti-amnesia/20250729-220000-user-guide-complete.md +41 -0
  50. data/doc/anti-amnesia/20250804-190500-cognitive-loop-bug.md +45 -0
  51. data/doc/anti-amnesia/20250804-190700-anti-amnesia-timestamping-fix.md +30 -0
  52. data/doc/anti-amnesia/20250804-213700-publishing-test-fix.md +49 -0
  53. data/doc/anti-amnesia/20250804-214400-additional-test-fixes.md +46 -0
  54. data/doc/anti-amnesia/20250804-220000-asset-function-logic-clarification.md +41 -0
  55. data/doc/anti-amnesia/20250806-202032-asset-function-logic-clarification.md +41 -0
  56. data/doc/anti-amnesia/20250807-213025.md +116 -0
  57. data/doc/anti-amnesia/20250813-082428-syntax-highlighting-and-navigation-improvements.md +256 -0
  58. data/doc/banner_svg_config.md +114 -0
  59. data/doc/contrib.lt3 +8 -0
  60. data/doc/dependencies.md +281 -0
  61. data/doc/hacker.lt3 +5 -0
  62. data/doc/reddit_credentials_template.json +8 -0
  63. data/doc/reddit_integration.md +207 -0
  64. data/doc/user.lt3 +38 -0
  65. data/doc/user_guide_section_1.md +137 -0
  66. data/doc/user_guide_section_10.md +515 -0
  67. data/doc/user_guide_section_11.md +708 -0
  68. data/doc/user_guide_section_2.md +233 -0
  69. data/doc/user_guide_section_3.md +5 -0
  70. data/doc/user_guide_section_4.md +221 -0
  71. data/doc/user_guide_section_5.md +243 -0
  72. data/doc/user_guide_section_6.md +147 -0
  73. data/doc/user_guide_section_7.md +311 -0
  74. data/doc/user_guide_section_8.md +224 -0
  75. data/doc/user_guide_section_9.md +375 -0
  76. data/doc/userdoc-toc.txt +88 -0
  77. data/lib/rouge/lexers/livetext.rb +74 -0
  78. data/lib/scriptorium/api.rb +640 -0
  79. data/lib/scriptorium/banner_svg.rb +742 -0
  80. data/lib/scriptorium/contract.rb +33 -0
  81. data/lib/scriptorium/exceptions.rb +174 -0
  82. data/lib/scriptorium/helpers.rb +475 -0
  83. data/lib/scriptorium/post.rb +195 -0
  84. data/lib/scriptorium/reddit.rb +83 -0
  85. data/lib/scriptorium/repo.rb +624 -0
  86. data/lib/scriptorium/standard_files.rb +515 -0
  87. data/lib/scriptorium/syntax_highlighter.rb +234 -0
  88. data/lib/scriptorium/theme.rb +179 -0
  89. data/lib/scriptorium/version.rb +2 -2
  90. data/lib/scriptorium/view.rb +976 -0
  91. data/lib/scriptorium/widgets/featured_posts.rb +149 -0
  92. data/lib/scriptorium/widgets/links.rb +112 -0
  93. data/lib/scriptorium/widgets/pages.rb +133 -0
  94. data/lib/scriptorium/widgets/widget.rb +133 -0
  95. data/lib/scriptorium.rb +22 -9
  96. data/lib/skeleton.rb +11 -2
  97. data/scriptorium.gemspec +15 -4
  98. data/test/README.md +69 -0
  99. data/test/all +43 -0
  100. data/test/api_demo.rb +99 -0
  101. data/test/assets/imagenotfound.jpg +0 -0
  102. data/test/assets/images/.DS_Store +0 -0
  103. data/test/assets/images/README.md +27 -0
  104. data/test/assets/images/odd_aspect.png +0 -0
  105. data/test/assets/images/perfect.png +0 -0
  106. data/test/assets/images/small.png +0 -0
  107. data/test/assets/images/tall.png +0 -0
  108. data/test/assets/images/very_tall.png +0 -0
  109. data/test/assets/images/very_wide.png +0 -0
  110. data/test/assets/images/wide.png +0 -0
  111. data/test/assets/testbanner.jpg +0 -0
  112. data/test/banner_svg/simple_helpers.rb +13 -0
  113. data/test/banner_svg/unit.rb +768 -0
  114. data/test/ed_test.rb +204 -0
  115. data/test/integration/cursor_banner_combinations.rb +193 -0
  116. data/test/integration/cursor_banner_features.rb +374 -0
  117. data/test/integration/integration_test.rb +326 -0
  118. data/test/livetext_plugin_test.rb +229 -0
  119. data/test/manual/asset_mgmt.rb +67 -0
  120. data/test/manual/banner-tests/config.txt +3 -0
  121. data/test/manual/banner-tests/index.html +45 -0
  122. data/test/manual/banner-tests/test01.html +58 -0
  123. data/test/manual/banner-tests/test02.html +58 -0
  124. data/test/manual/banner-tests/test03.html +58 -0
  125. data/test/manual/banner-tests/test04.html +65 -0
  126. data/test/manual/banner-tests/test05.html +65 -0
  127. data/test/manual/banner-tests/test06.html +65 -0
  128. data/test/manual/banner-tests/test07.html +65 -0
  129. data/test/manual/banner-tests/test08.html +59 -0
  130. data/test/manual/banner-tests/test09.html +59 -0
  131. data/test/manual/banner-tests/test10.html +59 -0
  132. data/test/manual/banner-tests/test11.html +59 -0
  133. data/test/manual/banner-tests/test12.html +59 -0
  134. data/test/manual/banner-tests/test13.html +59 -0
  135. data/test/manual/banner-tests/test14.html +59 -0
  136. data/test/manual/banner-tests/test15.html +58 -0
  137. data/test/manual/banner-tests/test16.html +58 -0
  138. data/test/manual/banner-tests/test17.html +58 -0
  139. data/test/manual/banner-tests/test18.html +68 -0
  140. data/test/manual/banner-tests/test19.html +68 -0
  141. data/test/manual/banner-tests/test20.html +68 -0
  142. data/test/manual/banner-tests/test21.html +68 -0
  143. data/test/manual/banner-tests/test22.html +68 -0
  144. data/test/manual/banner-tests/test23.html +68 -0
  145. data/test/manual/banner-tests/test24.html +68 -0
  146. data/test/manual/banner-tests/test25.html +67 -0
  147. data/test/manual/banner_environment.rb +192 -0
  148. data/test/manual/deploy_symlink_demo.rb +142 -0
  149. data/test/manual/environment.rb +67 -0
  150. data/test/manual/make_banner.rb +153 -0
  151. data/test/manual/sample_banner_config.txt +12 -0
  152. data/test/manual/symlink_demo.rb +117 -0
  153. data/test/manual/test1.rb +47 -0
  154. data/test/manual/test2.rb +12 -0
  155. data/test/manual/test3.rb +38 -0
  156. data/test/manual/test4.rb +40 -0
  157. data/test/manual/test5.rb +24 -0
  158. data/test/manual/test6.rb +73 -0
  159. data/test/manual/test_banner_combinations.rb +120 -0
  160. data/test/manual/test_banner_features.rb +306 -0
  161. data/test/manual/test_banner_from_file.rb +150 -0
  162. data/test/manual/test_banner_in_header.rb +35 -0
  163. data/test/manual/test_code_highlighting.rb +68 -0
  164. data/test/manual/test_complex_header.rb +74 -0
  165. data/test/manual/test_empty_header.rb +32 -0
  166. data/test/manual/test_radial_custom.rb +58 -0
  167. data/test/manual/test_radial_large_radius.rb +52 -0
  168. data/test/manual/test_svg_debug.rb +47 -0
  169. data/test/manual/test_syntax_highlighting.rb +147 -0
  170. data/test/pages-demo/config/currentview.txt +1 -0
  171. data/test/pages-demo/views/demo/config/bootstrap_css.txt +5 -0
  172. data/test/pages-demo/views/demo/config/bootstrap_js.txt +4 -0
  173. data/test/pages-demo/views/demo/config/common.js +57 -0
  174. data/test/pages-demo/views/demo/config/footer.txt +1 -0
  175. data/test/pages-demo/views/demo/config/global-head.txt +8 -0
  176. data/test/pages-demo/views/demo/config/header.txt +1 -0
  177. data/test/pages-demo/views/demo/config/layout.txt +1 -0
  178. data/test/pages-demo/views/demo/config/left.txt +1 -0
  179. data/test/pages-demo/views/demo/config/main.txt +1 -0
  180. data/test/pages-demo/views/demo/config/right.txt +1 -0
  181. data/test/pages-demo/views/demo/config.txt +3 -0
  182. data/test/pages-demo/views/demo/output/panes/footer.html +1 -0
  183. data/test/pages-demo/views/demo/output/panes/header.html +1 -0
  184. data/test/pages-demo/views/demo/output/panes/left.html +1 -0
  185. data/test/pages-demo/views/demo/output/panes/main.html +1 -0
  186. data/test/pages-demo/views/demo/output/panes/right.html +1 -0
  187. data/test/rubytext/rubytext_comprehensive_test.rb +307 -0
  188. data/test/rubytext/rubytext_demo_test.rb +42 -0
  189. data/test/rubytext/rubytext_testing_guide.md +277 -0
  190. data/test/run_automated_tests.rb +45 -0
  191. data/test/scriptorium-TEST-1754622690-146/config/bootstrap_css.txt +5 -0
  192. data/test/scriptorium-TEST-1754622690-146/config/bootstrap_js.txt +4 -0
  193. data/test/scriptorium-TEST-1754622690-146/config/common.js +57 -0
  194. data/test/scriptorium-TEST-1754622690-146/config/currentview.txt +1 -0
  195. data/test/scriptorium-TEST-1754622690-146/config/global-head.txt +9 -0
  196. data/test/scriptorium-TEST-1754622690-146/config/last_post_num.txt +1 -0
  197. data/test/scriptorium-TEST-1754622690-146/config/os_helpers.rb +4 -0
  198. data/test/scriptorium-TEST-1754622690-146/config/widgets.txt +3 -0
  199. data/test/scriptorium-TEST-1754622690-146/posts/0001/meta.txt +8 -0
  200. data/test/scriptorium-TEST-1754622690-146/posts/0001/source.lt3 +6 -0
  201. data/test/scriptorium-TEST-1754622690-146/themes/standard/README.txt +1 -0
  202. data/test/scriptorium-TEST-1754622690-146/themes/standard/config.txt +1 -0
  203. data/test/scriptorium-TEST-1754622690-146/themes/standard/initial/post.lt3 +12 -0
  204. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/footer.txt +2 -0
  205. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/header.txt +4 -0
  206. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/left.txt +3 -0
  207. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/main.txt +5 -0
  208. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/right.txt +3 -0
  209. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/gen/text.css +1 -0
  210. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/layout.txt +5 -0
  211. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index.lt3 +1 -0
  212. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index_entry.lt3 +14 -0
  213. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/post.lt3 +13 -0
  214. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/widget.lt3 +1 -0
  215. data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_css.txt +5 -0
  216. data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_js.txt +4 -0
  217. data/test/scriptorium-TEST-1754622690-146/views/sample/config/common.js +57 -0
  218. data/test/scriptorium-TEST-1754622690-146/views/sample/config/deploy.txt +5 -0
  219. data/test/scriptorium-TEST-1754622690-146/views/sample/config/footer.txt +2 -0
  220. data/test/scriptorium-TEST-1754622690-146/views/sample/config/global-head.txt +9 -0
  221. data/test/scriptorium-TEST-1754622690-146/views/sample/config/header.txt +4 -0
  222. data/test/scriptorium-TEST-1754622690-146/views/sample/config/layout.txt +5 -0
  223. data/test/scriptorium-TEST-1754622690-146/views/sample/config/left.txt +3 -0
  224. data/test/scriptorium-TEST-1754622690-146/views/sample/config/main.txt +5 -0
  225. data/test/scriptorium-TEST-1754622690-146/views/sample/config/reddit.txt +10 -0
  226. data/test/scriptorium-TEST-1754622690-146/views/sample/config/right.txt +3 -0
  227. data/test/scriptorium-TEST-1754622690-146/views/sample/config/social.txt +7 -0
  228. data/test/scriptorium-TEST-1754622690-146/views/sample/config/status.txt +7 -0
  229. data/test/scriptorium-TEST-1754622690-146/views/sample/config.txt +3 -0
  230. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/footer.html +3 -0
  231. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/header.html +3 -0
  232. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/left.html +3 -0
  233. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/main.html +3 -0
  234. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/right.html +3 -0
  235. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/footer.html +1 -0
  236. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/header.html +1 -0
  237. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/left.html +1 -0
  238. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/main.html +1 -0
  239. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/right.html +1 -0
  240. data/test/staging/.DS_Store +0 -0
  241. data/test/syntax_highlighting_test.lt3 +124 -0
  242. data/test/test_helpers.rb +230 -0
  243. data/test/tui_editor_integration_test.rb +296 -0
  244. data/test/tui_integration_test.rb +637 -0
  245. data/test/unit/api.rb +1056 -0
  246. data/test/unit/asset_management.rb +245 -0
  247. data/test/unit/clipboard_test.rb +60 -0
  248. data/test/unit/contract_test.rb +91 -0
  249. data/test/unit/core.rb +857 -0
  250. data/test/unit/deploy_test.rb +187 -0
  251. data/test/unit/gem_asset_management.rb +189 -0
  252. data/test/unit/livetext_basic.rb +69 -0
  253. data/test/unit/livetext_compatibility.rb +89 -0
  254. data/test/unit/post.rb +244 -0
  255. data/test/unit/read_commented_file_test.rb +276 -0
  256. data/test/unit/reddit_test.rb +235 -0
  257. data/test/unit/repo.rb +548 -0
  258. data/test/unit/social_test.rb +369 -0
  259. data/test/unit/symlink_test.rb +213 -0
  260. data/test/unit/view.rb +431 -0
  261. data/test/unit/widgets.rb +669 -0
  262. data/test/wizard_test.rb +123 -0
  263. data/ui/README.md +67 -0
  264. data/ui/common/lib/ui_common.rb +8 -0
  265. data/ui/rubytext/README.md +191 -0
  266. data/ui/rubytext/bin/scriptorium-rubytext +402 -0
  267. data/ui/rubytext/lib/rubytext_ui.rb +300 -0
  268. data/ui/tui/bin/scriptorium +1420 -0
  269. data/ui/tui/test/tui_test.rb +23 -0
  270. data/ui/web/app/app.rb +1378 -0
  271. data/ui/web/app/error_helpers.rb +150 -0
  272. data/ui/web/app/views/advanced_config.erb +190 -0
  273. data/ui/web/app/views/asset_management.erb +589 -0
  274. data/ui/web/app/views/banner_config.erb +200 -0
  275. data/ui/web/app/views/configure_view.erb +401 -0
  276. data/ui/web/app/views/dashboard.erb +162 -0
  277. data/ui/web/app/views/deploy_config.erb +146 -0
  278. data/ui/web/app/views/edit_pages.erb +195 -0
  279. data/ui/web/app/views/edit_post.erb +54 -0
  280. data/ui/web/app/views/error_page.erb +29 -0
  281. data/ui/web/app/views/header_config.erb +155 -0
  282. data/ui/web/app/views/layout_config.erb +147 -0
  283. data/ui/web/app/views/navbar_config.erb +411 -0
  284. data/ui/web/app/views/view_dashboard.erb +138 -0
  285. data/ui/web/bin/scriptorium-web +153 -0
  286. data/ui/web/test/web_basic_test.rb +38 -0
  287. data/ui/web/test_navbar.txt +7 -0
  288. data/ui/web/tmp/web_server.log +5 -0
  289. data/ui/web/tmp/web_server.pid +1 -0
  290. metadata +360 -5
@@ -0,0 +1,7 @@
1
+ # Available platforms: facebook twitter linkedin reddit
2
+ # List one platform per line to enable social meta tags
3
+ facebook
4
+ twitter
5
+
6
+ # Note: No Facebook App ID or Twitter username required for basic meta tags
7
+ # These are only needed if you want to add social sharing buttons later
@@ -0,0 +1,7 @@
1
+ header n
2
+ banner n
3
+ navbar n
4
+ left n
5
+ right n
6
+ pages n
7
+ deploy n
@@ -0,0 +1,3 @@
1
+ title My first view
2
+ subtitle This is just a sample
3
+ theme standard
@@ -0,0 +1,3 @@
1
+ <footer class="footer" style="background: lightgray; padding: 10px;">
2
+ <!-- Section: footer -->
3
+ </footer>
@@ -0,0 +1,3 @@
1
+ <header id="header" class="header" style="padding: 10px;">
2
+ <!-- Section: header -->
3
+ </header>
@@ -0,0 +1,3 @@
1
+ <aside class="left" style="width: 15%; background: #f0f0f0; padding: 10px; flex-grow: 0; flex-shrink: 0;">
2
+ <!-- Section: left -->
3
+ </aside>
@@ -0,0 +1,3 @@
1
+ <main class="main" style="flex-grow: 1; padding: 10px;">
2
+ <!-- Section: main -->
3
+ </main>
@@ -0,0 +1,3 @@
1
+ <aside class="right" style="width: 15%; background: #f0f0f0; padding: 10px; flex-grow: 0; flex-shrink: 0;">
2
+ <!-- Section: right -->
3
+ </aside>
@@ -0,0 +1 @@
1
+ Empty file generated at 2025-08-07 22:11:30 -0500
@@ -0,0 +1 @@
1
+ Empty file generated at 2025-08-07 22:11:30 -0500
@@ -0,0 +1 @@
1
+ Empty file generated at 2025-08-07 22:11:30 -0500
@@ -0,0 +1 @@
1
+ Empty file generated at 2025-08-07 22:11:30 -0500
@@ -0,0 +1 @@
1
+ Empty file generated at 2025-08-07 22:11:30 -0500
Binary file
@@ -0,0 +1,124 @@
1
+ .title Syntax Highlighting Test - Ruby, Elixir, JavaScript
2
+ .blurb Testing Rouge syntax highlighting for multiple languages
3
+ .views testview
4
+ .tags syntax,rouge,highlighting,manual-test
5
+
6
+ .syntax_css
7
+
8
+ This post tests the integrated Rouge syntax highlighter with three different programming languages.
9
+
10
+ ## Ruby Code Example
11
+
12
+ .code ruby
13
+ class User
14
+ attr_accessor :name, :email
15
+
16
+ def initialize(name, email)
17
+ @name = name
18
+ @email = email
19
+ end
20
+
21
+ def greet
22
+ puts "Hello, #{@name}!"
23
+ end
24
+
25
+ def valid_email?
26
+ email.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
27
+ end
28
+ end
29
+
30
+ user = User.new("Alice", "alice@example.com")
31
+ user.greet
32
+ .end
33
+
34
+ ## Elixir Code Example
35
+
36
+ .code elixir
37
+ defmodule Calculator do
38
+ def add(a, b), do: a + b
39
+ def subtract(a, b), do: a - b
40
+ def multiply(a, b), do: a * b
41
+ def divide(a, b) when b != 0, do: a / b
42
+ def divide(_, 0), do: {:error, "Division by zero"}
43
+
44
+ def calculate(operation, a, b) do
45
+ case operation do
46
+ :add -> add(a, b)
47
+ :subtract -> subtract(a, b)
48
+ :multiply -> multiply(a, b)
49
+ :divide -> divide(a, b)
50
+ _ -> {:error, "Unknown operation"}
51
+ end
52
+ end
53
+ end
54
+
55
+ # Test the calculator
56
+ result = Calculator.calculate(:add, 5, 3)
57
+ IO.puts("5 + 3 = #{result}")
58
+ .end
59
+
60
+ ## JavaScript Code Example
61
+
62
+ .code javascript
63
+ class TodoList {
64
+ constructor() {
65
+ this.todos = [];
66
+ this.nextId = 1;
67
+ }
68
+
69
+ addTodo(text, completed = false) {
70
+ const todo = {
71
+ id: this.nextId++,
72
+ text: text,
73
+ completed: completed,
74
+ createdAt: new Date()
75
+ };
76
+
77
+ this.todos.push(todo);
78
+ return todo;
79
+ }
80
+
81
+ toggleTodo(id) {
82
+ const todo = this.todos.find(t => t.id === id);
83
+ if (todo) {
84
+ todo.completed = !todo.completed;
85
+ }
86
+ return todo;
87
+ }
88
+
89
+ removeTodo(id) {
90
+ const index = this.todos.findIndex(t => t.id === id);
91
+ if (index > -1) {
92
+ return this.todos.splice(index, 1)[0];
93
+ }
94
+ return null;
95
+ }
96
+
97
+ getCompletedTodos() {
98
+ return this.todos.filter(t => t.completed);
99
+ }
100
+
101
+ getPendingTodos() {
102
+ return this.todos.filter(t => !t.completed);
103
+ }
104
+ }
105
+
106
+ // Usage example
107
+ const todoList = new TodoList();
108
+ todoList.addTodo("Learn Rouge syntax highlighting");
109
+ todoList.addTodo("Test multiple languages");
110
+ todoList.addTodo("Write documentation", true);
111
+
112
+ console.log("Pending todos:", todoList.getPendingTodos().length);
113
+ console.log("Completed todos:", todoList.getCompletedTodos().length);
114
+ .end
115
+
116
+ ## Test Results
117
+
118
+ Each code block above should display with proper syntax highlighting:
119
+
120
+ - **Ruby**: Keywords in red, strings in blue, variables in orange
121
+ - **Elixir**: Functions in purple, atoms in green, strings in blue
122
+ - **JavaScript**: Keywords in red, functions in purple, strings in blue
123
+
124
+ The highlighting should work automatically thanks to our Rouge integration!
@@ -0,0 +1,230 @@
1
+ module TestHelpers
2
+
3
+ include Scriptorium::Helpers
4
+
5
+ # Disable Design by Contract in tests by default
6
+ ENV['DBC_DISABLED'] = 'true'
7
+
8
+ def assert_dir_exist?(dir)
9
+ assert Dir.exist?(dir), "Directory '#{dir}' was not found"
10
+ end
11
+
12
+ def assert_file_exist?(file)
13
+ assert File.exist?(file), "File '#{file}' was not found"
14
+ end
15
+
16
+ def create_test_repo
17
+ Scriptorium::Repo.create("test/scriptorium-TEST", testmode: true) # testing
18
+ end
19
+
20
+ def assert_file_contains?(file, str)
21
+ text = File.read(file)
22
+ text.include?(str)
23
+ end
24
+
25
+ def assert_file_lines(file, num)
26
+ lines = File.readlines(file)
27
+ assert lines.size == num, "Expected #{num} lines in #{file}; found #{lines.size}"
28
+ end
29
+
30
+ def assert_file_newer?(f1, f2)
31
+ assert File.mtime(f1) > File.mtime(f2), "File #{f1} is not newer than #{f2}"
32
+ end
33
+
34
+ def assert_file_older?(f1, f2)
35
+ assert File.mtime(f1) < File.mtime(f2), "File #{f1} is not older than #{f2}"
36
+ end
37
+
38
+ def assert_class(obj, klass)
39
+ assert obj.is_a?(klass), "Expected a #{klass}, got #{obj.class}"
40
+ end
41
+
42
+ def see_file(file)
43
+ puts "----- File: #{file}"
44
+ system("cat #{file}")
45
+ puts "-----"
46
+ end
47
+
48
+ def see(label, var)
49
+ puts "#{label} = <<<\n#{var}"
50
+ end
51
+
52
+ def assert_wildcard_exist?(pattern)
53
+ glob = Dir.glob(pattern)
54
+ assert glob.size == 1, "Wildcard '#{pattern}' - expected 1 entry"
55
+ end
56
+
57
+ def assert_generated_post_found?(repo, num, views)
58
+ id4 = d4(num)
59
+ views = [views] if views.is_a?(String)
60
+ assert_file_exist?(repo.root/:posts/id4/"body.html")
61
+ assert_file_exist?(repo.root/:posts/id4/"meta.txt")
62
+ views.each do |view|
63
+ assert_wildcard_exist?(repo.root/:views/view/:output/:posts/"#{id4}-*.html")
64
+ end
65
+ end
66
+
67
+ def random_post(repo, views: nil)
68
+ views ||= []
69
+ views = [views] if views.is_a?(String)
70
+ rnum = rand(10000).to_i
71
+ name = repo.create_draft(title: "Random Post #{rnum}",
72
+ body: "Just a (#{rand(10000).to_i}) random post",
73
+ views: views)
74
+ num = repo.finish_draft(name)
75
+ num
76
+ end
77
+
78
+ def try_post_with_views(repo, views)
79
+ num = random_post(repo, views: views)
80
+ repo.generate_post(num)
81
+ assert_generated_post_found?(repo, num, views)
82
+ end
83
+
84
+ def post_with_views(repo, views) # No assert - for manual tests
85
+ num = random_post(repo, views: views)
86
+ repo.generate_post(num)
87
+ end
88
+
89
+ def num_posts_per_view(repo, view, exp)
90
+ posts = repo.all_posts(view)
91
+ assert posts.size == exp, "Expected #{exp} #{view} posts, found #{posts.size}"
92
+ end
93
+
94
+ def create_post(repo, title: "Test Post", views: ["sample"], body: "Hello")
95
+ draft = repo.create_draft(title: title, views: views, body: body)
96
+ num = repo.finish_draft(draft)
97
+ repo.generate_post(num)
98
+ Scriptorium::Post.new(repo, num)
99
+ end
100
+
101
+ def assert_ordered(str, *targets)
102
+ located = []
103
+ targets.each do |t|
104
+ located << str.index(t)
105
+ end
106
+ assert located == located.sort, "Targets out of order (#{located})"
107
+ end
108
+
109
+ def assert_present(str, *targets)
110
+ result = true
111
+ missing = []
112
+ targets.each do |t|
113
+ if ! str.include?(t)
114
+ result = false
115
+ missing << t
116
+ end
117
+ end
118
+ assert result, "Targets missing: #{missing.join(", ").inspect}"
119
+ end
120
+
121
+ def create_3_views # For test_posts_generated_and_indexed_across_multiple_views
122
+ @repo.create_view("blog1", "Blog 1", "nothing (1)")
123
+ @repo.create_view("blog2", "Blog 2", "nothing (2)")
124
+ @repo.create_view("blog3", "Blog 3", "nothing (3)")
125
+ end
126
+
127
+ def create_13_posts_manual
128
+ post_with_views(@repo, "blog1")
129
+ post_with_views(@repo, "blog2")
130
+ post_with_views(@repo, "blog3")
131
+ post_with_views(@repo, %w[blog1 blog2])
132
+ post_with_views(@repo, %w[blog2 blog3])
133
+ post_with_views(@repo, %w[blog1 blog3])
134
+ post_with_views(@repo, %w[blog1 blog2 blog3])
135
+ post_with_views(@repo, "blog1")
136
+ post_with_views(@repo, "blog1")
137
+ post_with_views(@repo, "blog1")
138
+ post_with_views(@repo, "blog2")
139
+ post_with_views(@repo, "blog2")
140
+ post_with_views(@repo, "blog3")
141
+ end
142
+
143
+ def create_13_posts # For test_posts_generated_and_indexed_across_multiple_views
144
+ try_post_with_views(@repo, "blog1")
145
+ try_post_with_views(@repo, "blog2")
146
+ try_post_with_views(@repo, "blog3")
147
+ try_post_with_views(@repo, %w[blog1 blog2])
148
+ try_post_with_views(@repo, %w[blog2 blog3])
149
+ try_post_with_views(@repo, %w[blog1 blog3])
150
+ try_post_with_views(@repo, %w[blog1 blog2 blog3])
151
+ try_post_with_views(@repo, "blog1")
152
+ try_post_with_views(@repo, "blog1")
153
+ try_post_with_views(@repo, "blog1")
154
+ try_post_with_views(@repo, "blog2")
155
+ try_post_with_views(@repo, "blog2")
156
+ try_post_with_views(@repo, "blog3")
157
+ # blog1 1 4 6 7 8 9 10
158
+ # blog2 2 4 5 11 12
159
+ # blog3 3 5 6 7 13
160
+ end
161
+
162
+ def alter_pubdates # For test_posts_generated_and_indexed_across_multiple_views
163
+ @repo.post(1).set_pubdate("2025-07-01")
164
+ @repo.post(2).set_pubdate("2025-07-02")
165
+ @repo.post(3).set_pubdate("2025-07-03")
166
+ @repo.post(4).set_pubdate("2025-07-04")
167
+ @repo.post(5).set_pubdate("2025-07-05")
168
+ @repo.post(6).set_pubdate("2025-07-06")
169
+ @repo.post(7).set_pubdate("2025-07-07")
170
+ @repo.post(8).set_pubdate("2025-07-08")
171
+ @repo.post(9).set_pubdate("2025-07-09")
172
+ @repo.post(10).set_pubdate("2025-07-10")
173
+ @repo.post(11).set_pubdate("2025-07-11")
174
+ @repo.post(12).set_pubdate("2025-07-12")
175
+ @repo.post(13).set_pubdate("2025-07-13")
176
+ end
177
+
178
+ def try_blog1_index # For test_posts_generated_and_indexed_across_multiple_views
179
+ @repo.generate_post_index("blog1")
180
+ %w[header main right footer].each do |section|
181
+ file = @repo.root/:views/"blog1"/:output/:panes/"#{section}.html"
182
+ assert File.exist?(file), "Expected section file #{file} to exist"
183
+ end
184
+ @repo.tree("/tmp/blog1.txt")
185
+ post_index = @repo.root/:views/"blog1"/:output/"post_index.html"
186
+ assert File.exist?(post_index), "Expected blog1 post_index.html to be generated"
187
+ content = File.read(post_index)
188
+
189
+ assert content.include?("July 1</div>"), "Expected July 1 in post_index"
190
+ assert content.include?("July 4</div>"), "Expected July 4 in post_index"
191
+ assert content.include?("July 6</div>"), "Expected July 6 in post_index"
192
+ assert content.include?("July 7</div>"), "Expected July 7 in post_index"
193
+ assert content.include?("July 8</div>"), "Expected July 8 in post_index"
194
+ assert content.include?("July 9</div>"), "Expected July 9 in post_index"
195
+ assert content.include?("July 10</div>"), "Expected July 10 in post_index"
196
+ refute content.include?("July 2</div>"), "Expected July 2 not in post_index"
197
+ refute content.include?("July 3</div>"), "Expected July 3 not in post_index"
198
+ refute content.include?("July 5"), "Expected July 5 not in post_index"
199
+ refute content.include?("July 11"), "Expected July 11 not in post_index"
200
+ refute content.include?("July 12"), "Expected July 12 not in post_index"
201
+ refute content.include?("July 13"), "Expected July 13 not in post_index"
202
+ end
203
+
204
+ def pseudoword
205
+ syllables = %w[tel mas re ko tem sil cro nim tes ran vel san tos le tor
206
+ de del mac dor mor ma ril odo tre kon lan sa te ti do mu]
207
+ n = syllables.size
208
+ num = rand(1..3)
209
+ str = ""
210
+ num.times { str << syllables[rand(n)] }
211
+ str
212
+ end
213
+
214
+ def pseudowords(n, prefix = "")
215
+ arr = [prefix]
216
+ n.times { arr << pseudoword }
217
+ arr.join(" ")
218
+ end
219
+
220
+ def pseudoline
221
+ pseudowords(10) + "\n"
222
+ end
223
+
224
+ def pseudolines(n)
225
+ str = ""
226
+ n.times { str << pseudoline }
227
+ str
228
+ end
229
+
230
+ end