scriptorium 0.0.3 → 0.7.2

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 (353) 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/icons/social/reddit.png +0 -0
  7. data/assets/icons/social/x-logo.png +0 -0
  8. data/assets/icons/ui/.DS_Store +0 -0
  9. data/assets/icons/ui/back.png +0 -0
  10. data/assets/icons/ui/copy.png +0 -0
  11. data/assets/icons/ui/down.png +0 -0
  12. data/assets/icons/ui/end.png +0 -0
  13. data/assets/icons/ui/exit.png +0 -0
  14. data/assets/icons/ui/foo +10 -0
  15. data/assets/icons/ui/home.png +0 -0
  16. data/assets/icons/ui/left.png +0 -0
  17. data/assets/icons/ui/next.png +0 -0
  18. data/assets/icons/ui/right.png +0 -0
  19. data/assets/icons/ui/start.png +0 -0
  20. data/assets/icons/ui/up.png +0 -0
  21. data/assets/imagenotfound.jpg +0 -0
  22. data/assets/samples/placeholder.svg +9 -0
  23. data/assets/themes/standard/favicon.svg +6 -0
  24. data/bin/sblog +84 -5
  25. data/bin/scriptorium +1 -0
  26. data/doc/README.txt +6 -0
  27. data/doc/anti-amnesia/20250727-054000-scriptorium-overview.md +94 -0
  28. data/doc/anti-amnesia/20250727-123000-anti-amnesia-conventions.md +2 -0
  29. data/doc/anti-amnesia/20250727-172600-cursor-rbenv-ruby-version-mystery.md +45 -0
  30. data/doc/anti-amnesia/20250727-172900-ai-cognitive-assessment-capabilities.md +40 -0
  31. data/doc/anti-amnesia/20250728-124243-aaa-syntax-clarification.md +46 -0
  32. data/doc/anti-amnesia/20250729-210000-reddit-autopost-integration-complete.md +158 -0
  33. data/doc/anti-amnesia/20250804-190500-cognitive-loop-bug.md +35 -0
  34. data/doc/anti-amnesia/20250804-190700-anti-amnesia-timestamping-fix.md +27 -0
  35. data/doc/anti-amnesia/20250807-213025.md +116 -0
  36. data/doc/anti-amnesia/20250901-211714-codemirror-integration-and-web-tests.md +172 -0
  37. data/doc/anti-amnesia/20250902-002402-backup-restore-system.md +126 -0
  38. data/doc/anti-amnesia/20250907-203339-backup-metadata-implementation.md +66 -0
  39. data/doc/banner_svg_config.md +114 -0
  40. data/doc/contrib.lt3 +8 -0
  41. data/doc/dependencies.md +281 -0
  42. data/doc/hacker.lt3 +5 -0
  43. data/doc/imported/0001-elixir-conf-2014/metadata.txt +7 -0
  44. data/doc/imported/0001-elixir-conf-2014/post.html +37 -0
  45. data/doc/imported/0001-elixir-conf-2014/source.lt3 +22 -0
  46. data/doc/imported/0002-programmers-and-word-processing/metadata.txt +7 -0
  47. data/doc/imported/0002-programmers-and-word-processing/post.html +192 -0
  48. data/doc/imported/0002-programmers-and-word-processing/source.lt3 +146 -0
  49. data/doc/imported/0003-how-to-turn-your-brain-sideways/metadata.txt +7 -0
  50. data/doc/imported/0003-how-to-turn-your-brain-sideways/post.html +60 -0
  51. data/doc/imported/0003-how-to-turn-your-brain-sideways/source.lt3 +40 -0
  52. data/doc/imported/0004-upcoming-lone-star-ruby-conference/metadata.txt +7 -0
  53. data/doc/imported/0004-upcoming-lone-star-ruby-conference/post.html +42 -0
  54. data/doc/imported/0004-upcoming-lone-star-ruby-conference/source.lt3 +24 -0
  55. data/doc/imported/0005-elixir-conf-2015-announced/metadata.txt +7 -0
  56. data/doc/imported/0005-elixir-conf-2015-announced/post.html +30 -0
  57. data/doc/imported/0005-elixir-conf-2015-announced/source.lt3 +16 -0
  58. data/doc/imported/0006-ruby-for-dinosaurs/metadata.txt +7 -0
  59. data/doc/imported/0006-ruby-for-dinosaurs/post.html +43 -0
  60. data/doc/imported/0006-ruby-for-dinosaurs/source.lt3 +27 -0
  61. data/doc/imported/0007-phoenix-isnt-rails/metadata.txt +7 -0
  62. data/doc/imported/0007-phoenix-isnt-rails/post.html +116 -0
  63. data/doc/imported/0007-phoenix-isnt-rails/source.lt3 +87 -0
  64. data/doc/imported/0008-concerning-the-term-monkeypatching/metadata.txt +7 -0
  65. data/doc/imported/0008-concerning-the-term-monkeypatching/post.html +129 -0
  66. data/doc/imported/0008-concerning-the-term-monkeypatching/source.lt3 +92 -0
  67. data/doc/imported/0009-announcement-coming-soon/metadata.txt +7 -0
  68. data/doc/imported/0009-announcement-coming-soon/post.html +33 -0
  69. data/doc/imported/0009-announcement-coming-soon/source.lt3 +19 -0
  70. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/metadata.txt +7 -0
  71. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/post.html +175 -0
  72. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/source.lt3 +139 -0
  73. data/doc/imported/0011-computer-science-as-a-lost-art/metadata.txt +7 -0
  74. data/doc/imported/0011-computer-science-as-a-lost-art/post.html +139 -0
  75. data/doc/imported/0011-computer-science-as-a-lost-art/source.lt3 +104 -0
  76. data/doc/imported/0012-ruby-day-in-turin-italy/metadata.txt +7 -0
  77. data/doc/imported/0012-ruby-day-in-turin-italy/post.html +42 -0
  78. data/doc/imported/0012-ruby-day-in-turin-italy/source.lt3 +24 -0
  79. data/doc/imported/0013-rubyday-was-a-success/metadata.txt +7 -0
  80. data/doc/imported/0013-rubyday-was-a-success/post.html +44 -0
  81. data/doc/imported/0013-rubyday-was-a-success/source.lt3 +27 -0
  82. data/doc/imported/0014-working-on-the-blogging-software/metadata.txt +7 -0
  83. data/doc/imported/0014-working-on-the-blogging-software/post.html +63 -0
  84. data/doc/imported/0014-working-on-the-blogging-software/source.lt3 +41 -0
  85. data/doc/imported/0015-ok-its-not-really-a-lost-art/metadata.txt +7 -0
  86. data/doc/imported/0015-ok-its-not-really-a-lost-art/post.html +172 -0
  87. data/doc/imported/0015-ok-its-not-really-a-lost-art/source.lt3 +134 -0
  88. data/doc/imported/0016-an-in-operator-for-ruby/metadata.txt +7 -0
  89. data/doc/imported/0016-an-in-operator-for-ruby/post.html +155 -0
  90. data/doc/imported/0016-an-in-operator-for-ruby/source.lt3 +106 -0
  91. data/doc/imported/0017-the-forgotten-mathematician/metadata.txt +7 -0
  92. data/doc/imported/0017-the-forgotten-mathematician/post.html +161 -0
  93. data/doc/imported/0017-the-forgotten-mathematician/source.lt3 +119 -0
  94. data/doc/imported/0018-ruby-puns/metadata.txt +7 -0
  95. data/doc/imported/0018-ruby-puns/post.html +46 -0
  96. data/doc/imported/0018-ruby-puns/source.lt3 +28 -0
  97. data/doc/imported/0019-custom-exceptions-via-metaprogramming/metadata.txt +7 -0
  98. data/doc/imported/0019-custom-exceptions-via-metaprogramming/post.html +138 -0
  99. data/doc/imported/0019-custom-exceptions-via-metaprogramming/source.lt3 +101 -0
  100. data/doc/imported/0020-fffff/metadata.txt +7 -0
  101. data/doc/imported/0020-fffff/post.html +24 -0
  102. data/doc/imported/0020-fffff/source.lt3 +12 -0
  103. data/doc/imported/0021-trying-ror-yet-again/metadata.txt +7 -0
  104. data/doc/imported/0021-trying-ror-yet-again/post.html +26 -0
  105. data/doc/imported/0021-trying-ror-yet-again/source.lt3 +12 -0
  106. data/doc/imported/0023-doctor-sleep/metadata.txt +7 -0
  107. data/doc/imported/0023-doctor-sleep/post.html +63 -0
  108. data/doc/imported/0023-doctor-sleep/source.lt3 +44 -0
  109. data/doc/imported/0024-just-a-test/metadata.txt +7 -0
  110. data/doc/imported/0024-just-a-test/post.html +24 -0
  111. data/doc/imported/0024-just-a-test/source.lt3 +12 -0
  112. data/doc/imported/import_summary.txt +98 -0
  113. data/doc/livetext-informal-spec.txt +65 -0
  114. data/doc/myuserdoc/ch-0.lt3 +31 -0
  115. data/doc/myuserdoc/ch-1.lt3 +37 -0
  116. data/doc/myuserdoc/ch-10.lt3 +22 -0
  117. data/doc/myuserdoc/ch-2.lt3 +37 -0
  118. data/doc/myuserdoc/ch-3.lt3 +19 -0
  119. data/doc/myuserdoc/ch-4.lt3 +43 -0
  120. data/doc/myuserdoc/ch-5.lt3 +22 -0
  121. data/doc/myuserdoc/ch-6.lt3 +19 -0
  122. data/doc/myuserdoc/ch-7.lt3 +16 -0
  123. data/doc/myuserdoc/ch-8.lt3 +13 -0
  124. data/doc/myuserdoc/ch-9.lt3 +19 -0
  125. data/doc/myuserdoc/tweak.rb +18 -0
  126. data/doc/myuserdoc/userdoc-toc.txt +88 -0
  127. data/doc/old-posts/0001-elixir-conf-2014.lt3 +24 -0
  128. data/doc/old-posts/0002-programmers-and-word-processing.lt3 +150 -0
  129. data/doc/old-posts/0003-how-to-turn-your-brain-sideways.lt3 +43 -0
  130. data/doc/old-posts/0004-upcoming-lone-star-ruby-conference.lt3 +26 -0
  131. data/doc/old-posts/0005-elixir-conf-2015-announced.lt3 +17 -0
  132. data/doc/old-posts/0006-ruby-for-dinosaurs.lt3 +30 -0
  133. data/doc/old-posts/0007-phoenix-isnt-rails.lt3 +90 -0
  134. data/doc/old-posts/0008-concerning-the-term-monkeypatching.lt3 +105 -0
  135. data/doc/old-posts/0009-announcement-coming-soon.lt3 +20 -0
  136. data/doc/old-posts/0010-immutable-data-ditching-the-wax-tablet.lt3 +142 -0
  137. data/doc/old-posts/0011-computer-science-as-a-lost-art.lt3 +117 -0
  138. data/doc/old-posts/0012-ruby-day-in-turin-italy.lt3 +26 -0
  139. data/doc/old-posts/0013-rubyday-was-a-success.lt3 +28 -0
  140. data/doc/old-posts/0014-working-on-the-blogging-software.lt3 +42 -0
  141. data/doc/old-posts/0015-ok-its-not-really-a-lost-art.lt3 +137 -0
  142. data/doc/old-posts/0016-an-in-operator-for-ruby.lt3 +142 -0
  143. data/doc/old-posts/0017-the-forgotten-mathematician.lt3 +129 -0
  144. data/doc/old-posts/0018-ruby-puns.lt3 +31 -0
  145. data/doc/old-posts/0019-custom-exceptions-via-metaprogramming.lt3 +116 -0
  146. data/doc/old-posts/0021-trying-ror-yet-again.lt3 +35 -0
  147. data/doc/old-posts/0023-doctor-sleep.lt3 +43 -0
  148. data/doc/old-posts/0024-just-a-test.lt3 +12 -0
  149. data/doc/old-posts/0025-trying-another-post.lt3 +12 -0
  150. data/doc/old-repo +1 -0
  151. data/doc/reddit_credentials_template.json +8 -0
  152. data/doc/reddit_integration.md +207 -0
  153. data/doc/user.lt3 +35 -0
  154. data/doc/user_guide_section_1.md +137 -0
  155. data/doc/user_guide_section_10.md +515 -0
  156. data/doc/user_guide_section_11.md +708 -0
  157. data/doc/user_guide_section_2.md +233 -0
  158. data/doc/user_guide_section_3.md +5 -0
  159. data/doc/user_guide_section_4.md +221 -0
  160. data/doc/user_guide_section_5.md +243 -0
  161. data/doc/user_guide_section_6.md +147 -0
  162. data/doc/user_guide_section_7.md +311 -0
  163. data/doc/user_guide_section_8.md +224 -0
  164. data/doc/user_guide_section_9.md +375 -0
  165. data/lib/rouge/lexers/livetext.rb +74 -0
  166. data/lib/scriptorium/api.rb +2373 -0
  167. data/lib/scriptorium/banner_svg.rb +729 -0
  168. data/lib/scriptorium/contract.rb +34 -0
  169. data/lib/scriptorium/exceptions.rb +201 -1
  170. data/lib/scriptorium/helpers.rb +675 -0
  171. data/lib/scriptorium/post.rb +259 -0
  172. data/lib/scriptorium/reddit.rb +83 -0
  173. data/lib/scriptorium/repo.rb +938 -0
  174. data/lib/scriptorium/standard_files.rb +149 -0
  175. data/lib/scriptorium/support/bootstrap/css.txt +5 -0
  176. data/lib/scriptorium/support/bootstrap/js.txt +4 -0
  177. data/lib/scriptorium/support/common_js/clipboard.js +35 -0
  178. data/lib/scriptorium/support/common_js/content-loader.js +187 -0
  179. data/lib/scriptorium/support/common_js/navigation.js +52 -0
  180. data/lib/scriptorium/support/common_js/syntax-highlighting.js +27 -0
  181. data/lib/scriptorium/support/config/reddit.txt +10 -0
  182. data/lib/scriptorium/support/config/reddit_template.txt +17 -0
  183. data/lib/scriptorium/support/config/social.txt +8 -0
  184. data/lib/scriptorium/support/highlight/css.txt +2 -0
  185. data/lib/scriptorium/support/highlight/custom.css +119 -0
  186. data/lib/scriptorium/support/highlight/js.txt +1 -0
  187. data/lib/scriptorium/support/post_index/config.txt +15 -0
  188. data/lib/scriptorium/support/post_index/style.css +55 -0
  189. data/lib/scriptorium/support/templates/index_entry.lt3 +16 -0
  190. data/lib/scriptorium/support/templates/initial_post.lt3 +12 -0
  191. data/lib/scriptorium/support/templates/layout.txt +5 -0
  192. data/lib/scriptorium/support/templates/post.lt3 +104 -0
  193. data/lib/scriptorium/support/theme/footer.lt3 +2 -0
  194. data/lib/scriptorium/support/theme/header.lt3 +4 -0
  195. data/lib/scriptorium/support/theme/left.lt3 +3 -0
  196. data/lib/scriptorium/support/theme/main.lt3 +5 -0
  197. data/lib/scriptorium/support/theme/right.lt3 +3 -0
  198. data/lib/scriptorium/theme.rb +192 -0
  199. data/lib/scriptorium/version.rb +1 -1
  200. data/lib/scriptorium/view.rb +1021 -0
  201. data/lib/scriptorium/widgets/featured_posts.rb +149 -0
  202. data/lib/scriptorium/widgets/links.rb +112 -0
  203. data/lib/scriptorium/widgets/pages.rb +133 -0
  204. data/lib/scriptorium/widgets/widget.rb +133 -0
  205. data/lib/scriptorium.rb +38 -34
  206. data/lib/skeleton.rb +10 -1
  207. data/scriptorium.gemspec +17 -5
  208. data/test/README.md +69 -0
  209. data/test/WEB_INTEGRATION_README.md +196 -0
  210. data/test/all +83 -0
  211. data/test/api_demo.rb +99 -0
  212. data/test/assets/imagenotfound.jpg +0 -0
  213. data/test/assets/images/.DS_Store +0 -0
  214. data/test/assets/images/README.md +27 -0
  215. data/test/assets/images/odd_aspect.png +0 -0
  216. data/test/assets/images/perfect.png +0 -0
  217. data/test/assets/images/small.png +0 -0
  218. data/test/assets/images/tall.png +0 -0
  219. data/test/assets/images/very_tall.png +0 -0
  220. data/test/assets/images/very_wide.png +0 -0
  221. data/test/assets/images/wide.png +0 -0
  222. data/test/assets/testbanner.jpg +0 -0
  223. data/test/banner_svg/simple_helpers.rb +13 -0
  224. data/test/banner_svg/unit.rb +1000 -0
  225. data/test/config/deployment.txt +5 -0
  226. data/test/ed_test.rb +204 -0
  227. data/test/integration/cursor_banner_combinations.rb +193 -0
  228. data/test/integration/cursor_banner_features.rb +374 -0
  229. data/test/integration/integration_test.rb +326 -0
  230. data/test/integration/preview_flow_test.rb +94 -0
  231. data/test/livetext_plugin_test.rb +500 -0
  232. data/test/manual/asset_mgmt.rb +67 -0
  233. data/test/manual/banner-tests/index.html +45 -0
  234. data/test/manual/banner-tests/svg.txt +3 -0
  235. data/test/manual/banner-tests/test01.html +122 -0
  236. data/test/manual/banner-tests/test02.html +122 -0
  237. data/test/manual/banner-tests/test03.html +122 -0
  238. data/test/manual/banner-tests/test04.html +129 -0
  239. data/test/manual/banner-tests/test05.html +129 -0
  240. data/test/manual/banner-tests/test06.html +129 -0
  241. data/test/manual/banner-tests/test07.html +129 -0
  242. data/test/manual/banner-tests/test08.html +123 -0
  243. data/test/manual/banner-tests/test09.html +123 -0
  244. data/test/manual/banner-tests/test10.html +123 -0
  245. data/test/manual/banner-tests/test11.html +123 -0
  246. data/test/manual/banner-tests/test12.html +123 -0
  247. data/test/manual/banner-tests/test13.html +123 -0
  248. data/test/manual/banner-tests/test14.html +123 -0
  249. data/test/manual/banner-tests/test15.html +122 -0
  250. data/test/manual/banner-tests/test16.html +122 -0
  251. data/test/manual/banner-tests/test17.html +122 -0
  252. data/test/manual/banner-tests/test18.html +132 -0
  253. data/test/manual/banner-tests/test19.html +132 -0
  254. data/test/manual/banner-tests/test20.html +132 -0
  255. data/test/manual/banner-tests/test21.html +132 -0
  256. data/test/manual/banner-tests/test22.html +132 -0
  257. data/test/manual/banner-tests/test23.html +132 -0
  258. data/test/manual/banner-tests/test24.html +132 -0
  259. data/test/manual/banner-tests/test25.html +131 -0
  260. data/test/manual/banner_environment.rb +205 -0
  261. data/test/manual/codemirror_demo.html +773 -0
  262. data/test/manual/create_posts_for_web.rb +114 -0
  263. data/test/manual/environment.rb +67 -0
  264. data/test/manual/make_banner.rb +153 -0
  265. data/test/manual/preview_manual_test.rb +129 -0
  266. data/test/manual/sample_banner_config.txt +12 -0
  267. data/test/manual/test_advanced_widgets.rb +73 -0
  268. data/test/manual/test_banner_combinations.rb +120 -0
  269. data/test/manual/test_banner_features.rb +306 -0
  270. data/test/manual/test_banner_integration.rb +115 -0
  271. data/test/manual/test_banner_radial.rb +87 -0
  272. data/test/manual/test_basic_posts.rb +47 -0
  273. data/test/manual/test_layout_widgets.rb +40 -0
  274. data/test/manual/test_pagination.rb +24 -0
  275. data/test/manual/test_random_posts.rb +38 -0
  276. data/test/manual/test_syntax_highlighting.rb +167 -0
  277. data/test/rubytext/rubytext_comprehensive_test.rb +307 -0
  278. data/test/rubytext/rubytext_demo_test.rb +42 -0
  279. data/test/rubytext/rubytext_testing_guide.md +277 -0
  280. data/test/run_automated_tests.rb +45 -0
  281. data/test/staging/.DS_Store +0 -0
  282. data/test/support/preview_utils.rb +88 -0
  283. data/test/syntax_highlighting_test.lt3 +124 -0
  284. data/test/test_gem_assets.rb +48 -0
  285. data/test/test_helpers.rb +240 -0
  286. data/test/tui_editor_integration_test.rb +296 -0
  287. data/test/tui_integration_test.rb +883 -0
  288. data/test/unit/api.rb +1776 -0
  289. data/test/unit/asset_management.rb +219 -0
  290. data/test/unit/backup_test.rb +451 -0
  291. data/test/unit/clipboard_test.rb +60 -0
  292. data/test/unit/contract_test.rb +69 -0
  293. data/test/unit/core.rb +1211 -0
  294. data/test/unit/deploy_config_test.rb +248 -0
  295. data/test/unit/deploy_test.rb +478 -0
  296. data/test/unit/edit_post_test.rb +168 -0
  297. data/test/unit/gem_asset_management.rb +183 -0
  298. data/test/unit/livetext_basic.rb +57 -0
  299. data/test/unit/livetext_compatibility.rb +82 -0
  300. data/test/unit/parse_cmd_test.rb +260 -0
  301. data/test/unit/permalink_copy_test.rb +211 -0
  302. data/test/unit/post.rb +309 -0
  303. data/test/unit/post_index_config_test.rb +258 -0
  304. data/test/unit/post_state_helpers_test.rb +137 -0
  305. data/test/unit/read_commented_file_test.rb +278 -0
  306. data/test/unit/reddit_test.rb +235 -0
  307. data/test/unit/repo.rb +569 -0
  308. data/test/unit/social_test.rb +366 -0
  309. data/test/unit/syntax_highlighting.rb +70 -0
  310. data/test/unit/theme_management_test.rb +91 -0
  311. data/test/unit/view.rb +498 -0
  312. data/test/unit/widgets.rb +669 -0
  313. data/test/web_integration_test.rb +231 -0
  314. data/test/web_test_helper.rb +218 -0
  315. data/test/web_workflow_test.rb +527 -0
  316. data/test/wizard_test.rb +123 -0
  317. data/ui/README.md +67 -0
  318. data/ui/common/lib/ui_common.rb +8 -0
  319. data/ui/rubytext/README.md +191 -0
  320. data/ui/rubytext/bin/scriptorium-rubytext +402 -0
  321. data/ui/rubytext/lib/rubytext_ui.rb +300 -0
  322. data/ui/tui/bin/scriptorium +1890 -0
  323. data/ui/tui/test/tui_test.rb +23 -0
  324. data/ui/web/app/app.rb +2600 -0
  325. data/ui/web/app/assets/livetext_mode.js +244 -0
  326. data/ui/web/app/error_helpers.rb +150 -0
  327. data/ui/web/app/views/advanced_config.erb +196 -0
  328. data/ui/web/app/views/asset_management.erb +645 -0
  329. data/ui/web/app/views/backup_management.erb +238 -0
  330. data/ui/web/app/views/banner_config.erb +200 -0
  331. data/ui/web/app/views/config_widget.erb +232 -0
  332. data/ui/web/app/views/configure_view.erb +401 -0
  333. data/ui/web/app/views/dashboard.erb +154 -0
  334. data/ui/web/app/views/deploy_config.erb +149 -0
  335. data/ui/web/app/views/edit_pages.erb +363 -0
  336. data/ui/web/app/views/edit_post.erb +175 -0
  337. data/ui/web/app/views/edit_theme.erb +73 -0
  338. data/ui/web/app/views/edit_theme_file.erb +74 -0
  339. data/ui/web/app/views/error_page.erb +29 -0
  340. data/ui/web/app/views/header_config.erb +155 -0
  341. data/ui/web/app/views/layout_config.erb +147 -0
  342. data/ui/web/app/views/navbar_config.erb +411 -0
  343. data/ui/web/app/views/theme_management.erb +130 -0
  344. data/ui/web/app/views/view_dashboard.erb +779 -0
  345. data/ui/web/app/views/widgets.erb +249 -0
  346. data/ui/web/bin/scriptorium-web +164 -0
  347. data/ui/web/test/web_basic_test.rb +38 -0
  348. data/ui/web/test_navbar.txt +7 -0
  349. data/ui/web/tmp/timing.log +17 -0
  350. data/ui/web/tmp/web_server.log +0 -0
  351. metadata +434 -8
  352. data/lib/scriptorium/engine.rb +0 -22
  353. data/test/engine/unit.rb +0 -44
@@ -0,0 +1,240 @@
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
+ # Assert that a string includes text, with concise error message
31
+ def assert_includes_concise_string(content, text, description)
32
+ unless content.include?(text)
33
+ # Show content size and a snippet for debugging
34
+ size = content.size
35
+ snippet = content[0, 300].gsub(/\s+/, ' ').strip
36
+ flunk "#{description} - Expected to find '#{text}' in content (#{size} bytes). Content snippet: #{snippet}..."
37
+ end
38
+ end
39
+
40
+ def assert_file_newer?(f1, f2)
41
+ assert File.mtime(f1) > File.mtime(f2), "File #{f1} is not newer than #{f2}"
42
+ end
43
+
44
+ def assert_file_older?(f1, f2)
45
+ assert File.mtime(f1) < File.mtime(f2), "File #{f1} is not older than #{f2}"
46
+ end
47
+
48
+ def assert_class(obj, klass)
49
+ assert obj.is_a?(klass), "Expected a #{klass}, got #{obj.class}"
50
+ end
51
+
52
+ def see_file(file)
53
+ puts "----- File: #{file}"
54
+ system("cat #{file}")
55
+ puts "-----"
56
+ end
57
+
58
+ def see(label, var)
59
+ puts "#{label} = <<<\n#{var}"
60
+ end
61
+
62
+ def assert_wildcard_exist?(pattern)
63
+ glob = Dir.glob(pattern)
64
+ assert glob.size == 1, "Wildcard '#{pattern}' - expected 1 entry"
65
+ end
66
+
67
+ def assert_generated_post_found?(repo, num, views)
68
+ id4 = d4(num)
69
+ views = [views] if views.is_a?(String)
70
+ assert_file_exist?(repo.root/:posts/id4/"body.html")
71
+ assert_file_exist?(repo.root/:posts/id4/"meta.txt")
72
+ views.each do |view|
73
+ assert_wildcard_exist?(repo.root/:views/view/:output/:posts/"#{id4}-*.html")
74
+ end
75
+ end
76
+
77
+ def random_post(repo, views: nil)
78
+ views ||= []
79
+ views = [views] if views.is_a?(String)
80
+ rnum = rand(10000).to_i
81
+ name = repo.create_draft(title: "Random Post #{rnum}",
82
+ body: "Just a (#{rand(10000).to_i}) random post",
83
+ views: views)
84
+ num = repo.finish_draft(name)
85
+ num
86
+ end
87
+
88
+ def try_post_with_views(repo, views)
89
+ num = random_post(repo, views: views)
90
+ repo.generate_post(num)
91
+ assert_generated_post_found?(repo, num, views)
92
+ end
93
+
94
+ def post_with_views(repo, views) # No assert - for manual tests
95
+ num = random_post(repo, views: views)
96
+ repo.generate_post(num)
97
+ end
98
+
99
+ def num_posts_per_view(repo, view, exp)
100
+ posts = repo.all_posts(view)
101
+ assert posts.size == exp, "Expected #{exp} #{view} posts, found #{posts.size}"
102
+ end
103
+
104
+ def create_post(repo, title: "Test Post", views: ["sample"], body: "Hello")
105
+ draft = repo.create_draft(title: title, views: views, body: body)
106
+ num = repo.finish_draft(draft)
107
+ repo.generate_post(num)
108
+ Scriptorium::Post.new(repo, num)
109
+ end
110
+
111
+ def assert_ordered(str, *targets)
112
+ located = []
113
+ targets.each do |t|
114
+ located << str.index(t)
115
+ end
116
+ assert located == located.sort, "Targets out of order (#{located})"
117
+ end
118
+
119
+ def assert_present(str, *targets)
120
+ result = true
121
+ missing = []
122
+ targets.each do |t|
123
+ if ! str.include?(t)
124
+ result = false
125
+ missing << t
126
+ end
127
+ end
128
+ assert result, "Targets missing: #{missing.join(", ").inspect}"
129
+ end
130
+
131
+ def create_3_views # For test_posts_generated_and_indexed_across_multiple_views
132
+ @repo.create_view("blog1", "Blog 1", "nothing (1)")
133
+ @repo.create_view("blog2", "Blog 2", "nothing (2)")
134
+ @repo.create_view("blog3", "Blog 3", "nothing (3)")
135
+ end
136
+
137
+ def create_13_posts_manual
138
+ post_with_views(@repo, "blog1")
139
+ post_with_views(@repo, "blog2")
140
+ post_with_views(@repo, "blog3")
141
+ post_with_views(@repo, %w[blog1 blog2])
142
+ post_with_views(@repo, %w[blog2 blog3])
143
+ post_with_views(@repo, %w[blog1 blog3])
144
+ post_with_views(@repo, %w[blog1 blog2 blog3])
145
+ post_with_views(@repo, "blog1")
146
+ post_with_views(@repo, "blog1")
147
+ post_with_views(@repo, "blog1")
148
+ post_with_views(@repo, "blog2")
149
+ post_with_views(@repo, "blog2")
150
+ post_with_views(@repo, "blog3")
151
+ end
152
+
153
+ def create_13_posts # For test_posts_generated_and_indexed_across_multiple_views
154
+ try_post_with_views(@repo, "blog1")
155
+ try_post_with_views(@repo, "blog2")
156
+ try_post_with_views(@repo, "blog3")
157
+ try_post_with_views(@repo, %w[blog1 blog2])
158
+ try_post_with_views(@repo, %w[blog2 blog3])
159
+ try_post_with_views(@repo, %w[blog1 blog3])
160
+ try_post_with_views(@repo, %w[blog1 blog2 blog3])
161
+ try_post_with_views(@repo, "blog1")
162
+ try_post_with_views(@repo, "blog1")
163
+ try_post_with_views(@repo, "blog1")
164
+ try_post_with_views(@repo, "blog2")
165
+ try_post_with_views(@repo, "blog2")
166
+ try_post_with_views(@repo, "blog3")
167
+ # blog1 1 4 6 7 8 9 10
168
+ # blog2 2 4 5 11 12
169
+ # blog3 3 5 6 7 13
170
+ end
171
+
172
+ def alter_pubdates # For test_posts_generated_and_indexed_across_multiple_views
173
+ @repo.post(1).set_pubdate("2025-07-01")
174
+ @repo.post(2).set_pubdate("2025-07-02")
175
+ @repo.post(3).set_pubdate("2025-07-03")
176
+ @repo.post(4).set_pubdate("2025-07-04")
177
+ @repo.post(5).set_pubdate("2025-07-05")
178
+ @repo.post(6).set_pubdate("2025-07-06")
179
+ @repo.post(7).set_pubdate("2025-07-07")
180
+ @repo.post(8).set_pubdate("2025-07-08")
181
+ @repo.post(9).set_pubdate("2025-07-09")
182
+ @repo.post(10).set_pubdate("2025-07-10")
183
+ @repo.post(11).set_pubdate("2025-07-11")
184
+ @repo.post(12).set_pubdate("2025-07-12")
185
+ @repo.post(13).set_pubdate("2025-07-13")
186
+ end
187
+
188
+ def try_blog1_index # For test_posts_generated_and_indexed_across_multiple_views
189
+ @repo.generate_post_index("blog1")
190
+ %w[header main right footer].each do |section|
191
+ file = @repo.root/:views/"blog1"/:output/:panes/"#{section}.html"
192
+ assert File.exist?(file), "Expected section file #{file} to exist"
193
+ end
194
+ @repo.tree("/tmp/blog1.txt")
195
+ post_index = @repo.root/:views/"blog1"/:output/"post_index.html"
196
+ assert File.exist?(post_index), "Expected blog1 post_index.html to be generated"
197
+ content = File.read(post_index)
198
+
199
+ assert content.include?("July 1</div>"), "Expected July 1 in post_index"
200
+ assert content.include?("July 4</div>"), "Expected July 4 in post_index"
201
+ assert content.include?("July 6</div>"), "Expected July 6 in post_index"
202
+ assert content.include?("July 7</div>"), "Expected July 7 in post_index"
203
+ assert content.include?("July 8</div>"), "Expected July 8 in post_index"
204
+ assert content.include?("July 9</div>"), "Expected July 9 in post_index"
205
+ assert content.include?("July 10</div>"), "Expected July 10 in post_index"
206
+ refute content.include?("July 2</div>"), "Expected July 2 not in post_index"
207
+ refute content.include?("July 3</div>"), "Expected July 3 not in post_index"
208
+ refute content.include?("July 5"), "Expected July 5 not in post_index"
209
+ refute content.include?("July 11"), "Expected July 11 not in post_index"
210
+ refute content.include?("July 12"), "Expected July 12 not in post_index"
211
+ refute content.include?("July 13"), "Expected July 13 not in post_index"
212
+ end
213
+
214
+ def pseudoword
215
+ syllables = %w[tel mas re ko tem sil cro nim tes ran vel san tos le tor
216
+ de del mac dor mor ma ril odo tre kon lan sa te ti do mu]
217
+ n = syllables.size
218
+ num = rand(1..3)
219
+ str = ""
220
+ num.times { str << syllables[rand(n)] }
221
+ str
222
+ end
223
+
224
+ def pseudowords(n, prefix = "")
225
+ arr = [prefix]
226
+ n.times { arr << pseudoword }
227
+ arr.join(" ")
228
+ end
229
+
230
+ def pseudoline
231
+ pseudowords(10) + "\n"
232
+ end
233
+
234
+ def pseudolines(n)
235
+ str = ""
236
+ n.times { str << pseudoline }
237
+ str
238
+ end
239
+
240
+ end
@@ -0,0 +1,296 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pty'
4
+ require 'expect'
5
+ require 'fileutils'
6
+ require 'minitest/autorun'
7
+ require_relative '../lib/scriptorium'
8
+
9
+ class TUIEditorIntegrationTest < Minitest::Test
10
+ # Test repository path
11
+ TEST_REPO_PATH = "scriptorium-TEST"
12
+
13
+ def setup
14
+ cleanup_test_repo
15
+ end
16
+
17
+ def teardown
18
+ cleanup_test_repo
19
+ end
20
+
21
+ def test_001_links_widget_editing_workflow
22
+ # Test the complete links widget editing workflow using PTY
23
+ # Start with no repo, let TUI create it, trigger wizard, configure links widget, edit content
24
+
25
+ # Ensure clean start
26
+ cleanup_test_repo
27
+
28
+ ENV['NOREADLINE'] = '1'
29
+
30
+ PTY.spawn({'NOREADLINE' => '1'}, 'ruby bin/scriptorium --test') do |read, write, pid|
31
+ begin
32
+ run_links_widget_workflow(read, write)
33
+ ensure
34
+ # Clean up the process
35
+ Process.kill('TERM', pid) rescue nil
36
+ Process.wait(pid) rescue nil
37
+ end
38
+ end
39
+ ensure
40
+ ENV.delete('NOREADLINE')
41
+ end
42
+
43
+ def test_002_basic_tui_flow
44
+ # Test basic TUI flow without complex wizard
45
+ ENV['NOREADLINE'] = '1'
46
+
47
+ PTY.spawn({'NOREADLINE' => '1'}, 'ruby bin/scriptorium --test') do |read, write, pid|
48
+ begin
49
+ run_basic_tui_workflow(read, write)
50
+
51
+ ensure
52
+ # Clean up the process
53
+ Process.kill('TERM', pid) rescue nil
54
+ Process.wait(pid) rescue nil
55
+ end
56
+ end
57
+ ensure
58
+ ENV.delete('NOREADLINE')
59
+ end
60
+
61
+ def test_003_create_post_workflow
62
+ # Test creating a post through the TUI
63
+ ENV['NOREADLINE'] = '1'
64
+
65
+ PTY.spawn({'NOREADLINE' => '1'}, 'ruby bin/scriptorium --test') do |read, write, pid|
66
+ begin
67
+ run_create_post_workflow(read, write)
68
+ ensure
69
+ # Clean up the process
70
+ Process.kill('TERM', pid) rescue nil
71
+ Process.wait(pid) rescue nil
72
+ end
73
+ end
74
+ ensure
75
+ ENV.delete('NOREADLINE')
76
+ end
77
+
78
+ private
79
+
80
+ def get_string(read, pattern, message, timeout = 5)
81
+ # Convenience method to get a string matching a pattern with timeout
82
+ # Set VERBOSE=1 to see detailed output, defaults to silent
83
+ output = read.expect(pattern, timeout)
84
+ if output
85
+ puts "Found: #{output[0].strip}" if ENV['VERBOSE']
86
+ else
87
+ # Pattern not found
88
+ # Read what we actually got
89
+ available = read.read_nonblock(1000) rescue ""
90
+ # Available output logged above
91
+ end
92
+ assert output, message
93
+ output[0] # Return the matched string
94
+ end
95
+
96
+ def cleanup_test_repo
97
+ # More aggressive cleanup - remove any test repositories
98
+ FileUtils.rm_rf(TEST_REPO_PATH) if Dir.exist?(TEST_REPO_PATH)
99
+ FileUtils.rm_rf("scriptorium-TEST") if Dir.exist?("scriptorium-TEST")
100
+ # Also clean up any other potential test repos
101
+ Dir.glob("scriptorium-*TEST*").each do |dir|
102
+ FileUtils.rm_rf(dir) if Dir.exist?(dir)
103
+ end
104
+ end
105
+
106
+ def send_and_expect(read, write, input, expected_pattern, description)
107
+ write.puts input
108
+ sleep 0.1 # Small delay to ensure input is processed
109
+ result = get_string(read, expected_pattern, description)
110
+ # Add explicit assertion for the expected pattern
111
+ assert result.match?(expected_pattern), "#{description}: Expected pattern '#{expected_pattern}' not found in output"
112
+ result
113
+ rescue Errno::EIO => e
114
+ # Handle case where TUI terminates immediately after output
115
+ if expected_pattern.to_s.include?("Goodbye!")
116
+ # If we're expecting Goodbye! and get an I/O error, that's probably OK
117
+ # The TUI terminated after outputting Goodbye!
118
+ return
119
+ else
120
+ raise e
121
+ end
122
+ end
123
+
124
+ def run_links_widget_workflow(read, write)
125
+ # Wait for initial output
126
+ get_string(read, /No repository found\./, "Should show 'No repository found'")
127
+
128
+ # Send 'y' to create new repository
129
+ send_and_expect(read, write, "y", "Created test repository successfully.",
130
+ "Should show repository created")
131
+
132
+ # Wait for editor setup
133
+ get_string(read, "No editor configured", "Should show editor setup")
134
+
135
+ # Wait for editor list
136
+ get_string(read, "Available editors", "Should show available editors")
137
+
138
+ # Wait for editor choice prompt
139
+ get_string(read, "Choose editor", "Should prompt for editor selection")
140
+ send_and_expect(read, write, "4", "Selected editor: ed",
141
+ "Should show editor selection")
142
+
143
+ # Wait for setup completion
144
+ get_string(read, "Setup complete", "Should show setup completion")
145
+
146
+ # Wait for assistance question
147
+ get_string(read, /Do you want assistance/, "Should ask about assistance")
148
+
149
+ # Send 'y' for assistance
150
+ send_and_expect(read, write, "y", /Enter view name/,
151
+ "Should prompt for view name")
152
+
153
+ # Send view name
154
+ send_and_expect(read, write, "testview", /Enter view title/,
155
+ "Should prompt for view title")
156
+
157
+ # Send view title
158
+ send_and_expect(read, write, "Test View", /Enter subtitle/,
159
+ "Should prompt for subtitle")
160
+
161
+ # Send subtitle
162
+ send_and_expect(read, write, "Test Subtitle", "Would you like to edit the layout",
163
+ "Should ask about editing layout")
164
+
165
+ # Send 'n' to skip layout editing (avoid ed interaction issues)
166
+ send_and_expect(read, write, "n", "Would you like to configure header",
167
+ "Should ask about header configuration")
168
+
169
+ # Wait for container configuration questions (in wizard order: header, main, left, right, footer)
170
+ send_and_expect(read, write, "n", "Would you like to configure main",
171
+ "Should ask about main configuration")
172
+ send_and_expect(read, write, "n", "Would you like to configure left",
173
+ "Should ask about left configuration")
174
+
175
+ # Say yes to configure left sidebar
176
+ send_and_expect(read, write, "y", "Add widgets to left",
177
+ "Should ask about adding widgets to left")
178
+
179
+ # Say yes to add widgets
180
+ send_and_expect(read, write, "y", "Available widgets: links, pages, featuredposts",
181
+ "Should show available widgets")
182
+
183
+ send_and_expect(read, write, "y", /Add links widget\?/, "Should ask about adding links widget")
184
+
185
+ send_and_expect(read, write, "n", /Add pages widget\?/, "Should ask about adding pages widget")
186
+
187
+ send_and_expect(read, write, "n", /Add featuredposts widget\?/, "Should ask about adding featuredposts widget")
188
+
189
+ send_and_expect(read, write, "n", /Configure links widget\?/, "Should ask about configuring links widget")
190
+
191
+ # Say no to configure links widget - now should proceed to next container
192
+ send_and_expect(read, write, "n", "Would you like to configure right",
193
+ "Should ask about right configuration")
194
+
195
+
196
+ send_and_expect(read, write, "n", "Would you like to configure footer",
197
+ "Should ask about footer configuration")
198
+ send_and_expect(read, write, "n", "View setup complete",
199
+ "Should show setup completion")
200
+
201
+ # Wait for main prompt
202
+ get_string(read, "[testview]", "Should show main prompt")
203
+
204
+ # Send 'quit' to exit
205
+ send_and_expect(read, write, "quit", "Goodbye!",
206
+ "Should show goodbye message")
207
+ end
208
+
209
+ def run_basic_tui_workflow(read, write)
210
+ # Wait for initial output
211
+ get_string(read, "No repository found.", "Should show 'No repository found'")
212
+
213
+ # Send 'y' to create new repository
214
+ send_and_expect(read, write, "y", "Created test repository successfully.",
215
+ "Should show repository created")
216
+
217
+ # Wait for editor setup
218
+ get_string(read, "No editor configured", "Should show editor setup")
219
+
220
+ # Wait for editor list
221
+ get_string(read, "Available editors", "Should show available editors")
222
+
223
+ # Wait for editor choice prompt
224
+ get_string(read, "Choose editor", "Should prompt for editor selection")
225
+ send_and_expect(read, write, "4", "Selected editor: ed",
226
+ "Should show editor selection")
227
+
228
+ # Wait for setup completion
229
+ get_string(read, "Setup complete", "Should show setup completion")
230
+
231
+ # Wait for assistance question
232
+ get_string(read, /Do you want assistance/, "Should ask about assistance")
233
+
234
+ # Send 'n' to skip assistance
235
+ send_and_expect(read, write, "n", "[sample]", "Should show main prompt")
236
+
237
+ # Send 'quit' to exit
238
+ send_and_expect(read, write, "quit", "Goodbye!",
239
+ "Should show goodbye message")
240
+ end
241
+
242
+ def run_create_post_workflow(read, write)
243
+ # Wait for initial output
244
+ get_string(read, "No repository found.", "Should show 'No repository found'")
245
+
246
+ # Send 'y' to create new repository
247
+ send_and_expect(read, write, "y", "Created test repository successfully.",
248
+ "Should show repository created")
249
+
250
+ # Wait for editor setup
251
+ get_string(read, "No editor configured", "Should show editor setup")
252
+
253
+ # Wait for editor list
254
+ get_string(read, "Available editors", "Should show available editors")
255
+
256
+ # Wait for editor choice prompt
257
+ get_string(read, "Choose editor", "Should prompt for editor selection")
258
+ send_and_expect(read, write, "4", "Selected editor: ed",
259
+ "Should show editor selection")
260
+
261
+ # Wait for setup completion
262
+ get_string(read, "Setup complete", "Should show setup completion")
263
+
264
+ # Wait for assistance question
265
+ get_string(read, /Do you want assistance/, "Should ask about assistance")
266
+
267
+ # Send 'n' to skip assistance
268
+ send_and_expect(read, write, "n", "[sample]", "Should show main prompt")
269
+
270
+ # Create a post
271
+ send_and_expect(read, write, "new post Test Post", /Created draft:/,
272
+ "Should show draft created")
273
+
274
+ # Wait for editor opening
275
+ get_string(read, "Opening in ed", "Should show editor opening")
276
+
277
+ # Simulate ed interaction
278
+ sleep 1
279
+ write.puts "a"
280
+ write.puts "Test content"
281
+ write.puts "."
282
+ write.puts "w"
283
+ write.puts "q"
284
+ sleep 1
285
+
286
+ # Wait for post creation
287
+ get_string(read, "Post created", "Should show post created")
288
+
289
+ # Wait for main prompt
290
+ get_string(read, "[sample]", "Should show main prompt")
291
+
292
+ # Send 'quit' to exit
293
+ send_and_expect(read, write, "quit", "Goodbye!",
294
+ "Should show goodbye message")
295
+ end
296
+ end