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
data/test/unit/repo.rb ADDED
@@ -0,0 +1,569 @@
1
+ require 'minitest/autorun'
2
+
3
+ require_relative '../../lib/scriptorium'
4
+ require_relative '../test_helpers'
5
+
6
+ class TestScriptoriumRepo < Minitest::Test
7
+
8
+ include Scriptorium::Exceptions
9
+ include Scriptorium::Helpers
10
+ include TestHelpers
11
+
12
+ Repo = Scriptorium::Repo
13
+
14
+ def setup
15
+ end
16
+
17
+ def teardown
18
+ system("rm -rf test/scriptorium-TEST")
19
+ end
20
+
21
+ def test_001_version
22
+ ver = Scriptorium::VERSION
23
+ pieces = ver.split(".")
24
+ pieces.each do |num|
25
+ assert num =~ /^\d+$/, "Invalid version '#{ver}'"
26
+ end
27
+ end
28
+
29
+ def test_002_repo_create_destroy
30
+ t0 = Repo.exist?
31
+ refute t0, "Repo should not exist yet"
32
+
33
+ create_test_repo
34
+
35
+ t1 = Repo.exist?
36
+ assert t1, "Repo should exist"
37
+
38
+ Repo.destroy
39
+
40
+ t2 = Repo.exist?
41
+ refute t2, "Repo should have been destroyed"
42
+ end
43
+
44
+ def test_003_illegal_destroy
45
+ Repo.testing = false
46
+ assert_raises(TestModeOnly) { Repo.destroy }
47
+ end
48
+
49
+ def test_004_repo_structure
50
+ create_test_repo
51
+ root = Repo.root
52
+ assert_dir_exist?(root/"config")
53
+ assert_dir_exist?(root/"views")
54
+ assert_dir_exist?(root/"views/sample")
55
+ assert_dir_exist?(root/"posts")
56
+ assert_dir_exist?(root/"drafts")
57
+ assert_dir_exist?(root/"themes")
58
+ assert_dir_exist?(root/"themes/standard")
59
+ assert_dir_exist?(root/"assets")
60
+ end
61
+
62
+ def test_005_check_sample_view
63
+ repo = create_test_repo
64
+ assert repo.views.is_a?(Array), "Expected array of Views"
65
+ assert repo.views.size == 1, "Expected one initial view"
66
+ assert repo.views[0].name == "sample", "Expected to find view 'sample'"
67
+ assert repo.current_view.name == "sample", "Expected current view to be 'sample'"
68
+ end
69
+
70
+ def test_006_check_widgets_txt_created
71
+ repo = create_test_repo
72
+ widgets_file = "#{repo.root}/config/widgets.txt"
73
+ assert File.exist?(widgets_file), "widgets.txt should exist in config directory"
74
+
75
+ content = read_file(widgets_file)
76
+ assert_includes content, "links"
77
+ assert_includes content, "pages"
78
+ end
79
+
80
+ def test_007_create_view
81
+ repo = create_test_repo
82
+ vname = "testview"
83
+ t0 = repo.view_exist?(vname)
84
+ refute t0, "View should not exist yet"
85
+
86
+ repo.create_view(vname, "My Title", "Just a subtitle here") # testing
87
+
88
+ t1 = repo.view_exist?(vname)
89
+ assert t1, "View should exist"
90
+
91
+ # Check that pages directory was created
92
+ pages_dir = "#{repo.root}/views/#{vname}/pages"
93
+ assert Dir.exist?(pages_dir), "Pages directory should exist"
94
+
95
+ assert_raises(ViewDirAlreadyExists) do
96
+ repo.create_view(vname, "My Other Title", "Just dumbness here") # testing
97
+ end
98
+ end
99
+
100
+ def test_008_create_view_creates_all_config_files
101
+ repo = create_test_repo
102
+ vname = "testview"
103
+
104
+ repo.create_view(vname, "My Title", "Just a subtitle here")
105
+
106
+ # Check that all expected config files are created
107
+ view_dir = "#{repo.root}/views/#{vname}"
108
+ config_dir = "#{view_dir}/config"
109
+
110
+ # Main view config file (in view root, not config subdirectory)
111
+ assert_file_exist?("#{view_dir}/config.txt")
112
+
113
+ # Core config files
114
+ assert_file_exist?("#{config_dir}/global-head.txt")
115
+ assert_file_exist?("#{config_dir}/bootstrap_js.txt")
116
+ assert_file_exist?("#{config_dir}/bootstrap_css.txt")
117
+ assert_file_exist?("#{config_dir}/common.js")
118
+ assert_file_exist?("#{config_dir}/social.txt")
119
+ assert_file_exist?("#{config_dir}/reddit.txt")
120
+ assert_file_exist?("#{config_dir}/deploy.txt")
121
+ assert_file_exist?("#{config_dir}/status.txt")
122
+ assert_file_exist?("#{config_dir}/post_index.txt")
123
+
124
+ # Theme config files
125
+ assert_file_exist?("#{config_dir}/header.txt")
126
+ assert_file_exist?("#{config_dir}/footer.txt")
127
+ assert_file_exist?("#{config_dir}/left.txt")
128
+ assert_file_exist?("#{config_dir}/right.txt")
129
+ assert_file_exist?("#{config_dir}/main.txt")
130
+
131
+ # Banner config files
132
+ assert_file_exist?("#{config_dir}/svg.txt")
133
+
134
+ # Check that header.txt includes banner svg
135
+ header_content = read_file("#{config_dir}/header.txt")
136
+ assert_includes header_content, "banner svg"
137
+
138
+ # Check that svg.txt has proper content
139
+ svg_content = read_file("#{config_dir}/svg.txt")
140
+ assert_includes svg_content, "aspect"
141
+ assert_includes svg_content, "back.linear"
142
+ assert_includes svg_content, "title.color"
143
+ end
144
+
145
+ def test_009_new_view_becomes_current
146
+ repo = create_test_repo
147
+ tv2 = "testview2"
148
+ view = repo.create_view(tv2, "My 2nd Title", "Just another subtitle here")
149
+ assert repo.views.size == 2, "Expected 2 views, not #{repo.views.size}"
150
+ vnames = repo.views.map {|v| v.name }
151
+ assert vnames.include?(tv2), "Expected to find '#{tv2}' in views"
152
+ assert repo.current_view.name == view.name, "Expected '#{tv2}' as current view"
153
+ end
154
+
155
+ def test_010_open_view
156
+ repo = create_test_repo
157
+ vname = "testview"
158
+ title, sub = "My Awesome Title", "Just another subtitle"
159
+ repo.create_view(vname, title, sub)
160
+ t0 = repo.view_exist?(vname)
161
+ assert t0, "View should exist"
162
+
163
+ view = repo.open_view(vname)
164
+ assert view.title == title, "View title missing"
165
+ assert view.subtitle == sub, "View subtitle missing"
166
+ assert view.theme == "standard", "Expected standard theme, found '#{view.theme}'"
167
+ assert repo.current_view.name == vname, "Expected '#{vname}' in views"
168
+ end
169
+
170
+ def test_011_create_draft
171
+ repo = create_test_repo
172
+ fname = repo.create_draft
173
+ assert_file_exist?(fname)
174
+ assert_file_contains?(fname, ".title ADD TITLE HERE")
175
+ assert_file_contains?(fname, ".views BLOG1 BLOG2 BLOG3")
176
+ assert_file_contains?(fname, ".tags sample, tags")
177
+
178
+ f2 = repo.create_draft(title: "Draft post", tags: %w[things stuff])
179
+ assert_file_exist?(f2)
180
+ assert_file_contains?(f2, ".title Draft post")
181
+ assert_file_contains?(f2, ".views BLOG1 BLOG2 BLOG3")
182
+ assert_file_contains?(f2, ".tags things, stuff")
183
+ end
184
+
185
+ def test_012_finish_draft
186
+ $debug = true
187
+ repo = create_test_repo
188
+ fname = repo.create_draft
189
+
190
+ repo.finish_draft(fname)
191
+ postnum = "0001" # Assumes testing started with 0
192
+ postdir = repo.root/:posts/postnum
193
+ assert_dir_exist?(postdir/:assets)
194
+ assert_file_exist?(postdir/"source.lt3")
195
+ $debug = false
196
+ end
197
+
198
+ def test_013_check_initial_post
199
+ repo = create_test_repo
200
+ root = repo.root
201
+ file = "#{root}/themes/standard/initial/post.lt3"
202
+ assert_file_exist?(file)
203
+ assert_file_lines(file, 12)
204
+ end
205
+
206
+ def test_014_check_interpolated_initial_post
207
+ repo = create_test_repo
208
+ predef = repo.instance_eval { @predef }
209
+ str = predef.initial_post
210
+ lines = str.split("\n")
211
+ str2 = predef.initial_post(title: "My post title")
212
+ lines = str2.split("\n")
213
+ assert lines[3] == ".title My post title", "Expected 'title' to be filled in (found '#{lines[3]}')"
214
+ end
215
+
216
+ def test_015_find_theme_file
217
+ repo = create_test_repo
218
+ t = Scriptorium::Theme.new(repo.root, "standard")
219
+
220
+ path1 = t.file("initial/post.lt3")
221
+ want1 = "test/scriptorium-TEST/themes/standard/initial/post.lt3"
222
+ assert path1 == want1, "Expected: #{want1}, got: #{path1}"
223
+ path2 = t.file("right.txt")
224
+ want2 = "test/scriptorium-TEST/themes/standard/layout/config/right.txt"
225
+ assert path2 == want2, "Expected: #{want2}"
226
+
227
+ assert_raises(MoreThanOneResult) { t.file("post.lt3") }
228
+ end
229
+
230
+ def test_014_check_post_template
231
+ repo = create_test_repo
232
+ root = repo.root
233
+ file = "#{root}/themes/standard/templates/post.lt3"
234
+ assert_file_exist?(file)
235
+ content = read_file(file)
236
+ # Key sections expected in the post template
237
+ assert_includes content, 'href="../index.html"', "Back-to-index link missing"
238
+ assert_includes content, '%{reddit_button}', "Reddit button placeholder missing"
239
+ assert_includes content, 'Copy link', "Copy link button missing"
240
+ # Highlight.js assets are included via CDN
241
+ assert_includes content, 'cdnjs.cloudflare.com/ajax/libs/highlight.js', "Highlight.js CDN not referenced"
242
+ end
243
+
244
+ def test_015_change_view
245
+ repo = create_test_repo
246
+ v1 = repo.view # initially 'sample'
247
+ assert v1.name == 'sample', "Expected view to be 'sample'"
248
+
249
+ vname = "testing"
250
+ t0 = repo.view_exist?(vname)
251
+ refute t0, "View should not exist yet"
252
+
253
+ repo.create_view(vname, "My Title", "Nothing here") # testing
254
+ v2 = repo.view
255
+ assert v2.name == 'testing', "Expected view to be 'sample'"
256
+
257
+ v3 = repo.view('sample')
258
+ assert v3.name == 'sample', "Expected view to be 'sample'"
259
+
260
+ v4 = repo.view('testing')
261
+ assert v4.name == 'testing', "Expected view to be 'sample'"
262
+ end
263
+
264
+ def test_016_lookup_view
265
+ repo = create_test_repo
266
+ root = repo.root
267
+ v0 = repo.lookup_view('sample')
268
+ assert v0.name == 'sample', "Expected view to be 'sample'"
269
+ assert_raises(CannotLookupView) { repo.lookup_view('view99')}
270
+ repo.create_view("newview", "My Title", "Nothing here") # testing
271
+ v2 = repo.view
272
+ v3 = repo.lookup_view(v2.name)
273
+ assert v3.name == v2.name, "Expected new view '#{v2.name}' found as '#{v3.name}"
274
+ end
275
+
276
+ def test_017_tree_method
277
+ repo = create_test_repo
278
+ repo.tree("/tmp/test-tree.txt")
279
+ assert_file_exist?("/tmp/tree.txt")
280
+ num = File.readlines("/tmp/tree.txt").size
281
+ assert num > 0, "Tree file appears too short"
282
+ end
283
+
284
+
285
+
286
+
287
+
288
+
289
+ def test_022_simple_generate_post
290
+ repo = create_test_repo
291
+ dname = repo.create_draft(title: "My first post", tags: %w[things stuff])
292
+ body =
293
+ <<~EOS
294
+ This is just another fake blog post.
295
+
296
+ <p>
297
+ If it had been an _actual post, it might have
298
+ said something meaninful.
299
+
300
+ <p>
301
+ But here we are.
302
+ EOS
303
+ text = read_file(dname)
304
+ text.sub!(/BEGIN HERE.../, body)
305
+ write_file(dname, text)
306
+ num = repo.finish_draft(dname)
307
+ repo.generate_post(num)
308
+ repo.tree("/tmp/tree.txt")
309
+ assert_file_exist?(repo.root/:posts/d4(num)/"body.html")
310
+ assert_file_exist?(repo.root/:posts/d4(num)/"meta.txt")
311
+ assert_file_exist?(repo.root/:views/:sample/:output/:posts/"#{d4(num)}-my-first-post.html")
312
+ end
313
+
314
+
315
+
316
+
317
+
318
+ def test_025_layout_file_missing
319
+ repo = create_test_repo
320
+ view = repo.create_view("testview", "Test View", "Test Subtitle")
321
+
322
+ # Remove the layout.txt file to test the exception
323
+ layout_file = view.dir/:config/"layout.txt"
324
+ File.delete(layout_file) if File.exist?(layout_file)
325
+
326
+ assert_raises(LayoutFileMissing) do
327
+ view.read_layout
328
+ end
329
+ end
330
+
331
+ def test_026_verify_permalink_generation
332
+ repo = create_test_repo
333
+ dname = repo.create_draft(title: "Permalink Test Post", tags: %w[test permalink])
334
+ body = <<~EOS
335
+ This is a test post for permalink functionality.
336
+
337
+ <p>
338
+ It should be generated in both the posts/ and permalink/ directories.
339
+ EOS
340
+ text = read_file(dname)
341
+ text.sub!(/BEGIN HERE.../, body)
342
+ write_file(dname, text)
343
+ num = repo.finish_draft(dname)
344
+ repo.generate_post(num)
345
+
346
+ # Check that post was generated in both locations
347
+ # Use the same pattern as the working test
348
+ regular_post = repo.root/:views/:sample/:output/:posts/"#{d4(num)}-permalink-test-post.html"
349
+ assert File.exist?(regular_post), "Regular post should exist at #{regular_post}"
350
+
351
+ # Permalink location
352
+ permalink_post = repo.root/:views/:sample/:output/:permalink/"#{d4(num)}-permalink-test-post.html"
353
+ assert File.exist?(permalink_post), "Permalink post should exist at #{permalink_post}"
354
+
355
+ # Both files should have different content (permalink has "Visit Blog" link)
356
+ regular_content = read_file(regular_post)
357
+ permalink_content = read_file(permalink_post)
358
+ refute_equal regular_content, permalink_content, "Post content should differ (permalink has 'Visit Blog' link)"
359
+
360
+ # Regular post should NOT contain the "Visit Blog" link
361
+ refute_includes regular_content, "Visit Blog"
362
+
363
+ # Permalink post should contain the "Visit Blog" link
364
+ assert_includes permalink_content, "Visit Blog"
365
+ assert_includes permalink_content, 'href="../index.html"'
366
+
367
+ # Permalink post should contain the "Copy link" button
368
+ assert_includes permalink_content, "Copy link"
369
+ end
370
+
371
+ # View-related tests
372
+ def test_view_method_writes_to_currentview_txt
373
+ # Create a test repo for this test
374
+ test_repo_path = "test_repo_view_test"
375
+ FileUtils.rm_rf(test_repo_path) if Dir.exist?(test_repo_path)
376
+
377
+ repo = Scriptorium::Repo.create(test_repo_path, testmode: true)
378
+ repo.create_view("view1", "View One", "First view")
379
+ repo.create_view("view2", "View Two", "Second view")
380
+
381
+ # Initially, current view should be the last created view (view2)
382
+ assert_equal "view2", repo.current_view.name
383
+
384
+ # Switch to view1
385
+ repo.view("view1")
386
+
387
+ # Check that current view changed in memory
388
+ assert_equal "view1", repo.current_view.name
389
+
390
+ # Check that currentview.txt was written
391
+ currentview_file = File.join(test_repo_path, "config", "currentview.txt")
392
+ assert File.exist?(currentview_file), "currentview.txt should exist"
393
+
394
+ content = read_file(currentview_file).strip
395
+ assert_equal "view1", content, "currentview.txt should contain 'view1'"
396
+
397
+ # Cleanup
398
+ FileUtils.rm_rf(test_repo_path)
399
+ end
400
+
401
+ def test_view_method_persists_across_repo_reload
402
+ # Create a test repo for this test
403
+ test_repo_path = "test_repo_view_test"
404
+ FileUtils.rm_rf(test_repo_path) if Dir.exist?(test_repo_path)
405
+
406
+ repo = Scriptorium::Repo.create(test_repo_path, testmode: true)
407
+ repo.create_view("view1", "View One", "First view")
408
+ repo.create_view("view2", "View Two", "Second view")
409
+
410
+ # Switch to view1
411
+ repo.view("view1")
412
+ assert_equal "view1", repo.current_view.name
413
+
414
+ # Create a new repo instance pointing to the same directory
415
+ new_repo = Scriptorium::Repo.open(test_repo_path)
416
+
417
+ # The new repo should load the current view from the file
418
+ assert_equal "view1", new_repo.current_view.name
419
+
420
+ # Cleanup
421
+ FileUtils.rm_rf(test_repo_path)
422
+ end
423
+
424
+ def test_create_view_switches_to_new_view
425
+ # Create a test repo for this test
426
+ test_repo_path = "test_repo_view_test"
427
+ FileUtils.rm_rf(test_repo_path) if Dir.exist?(test_repo_path)
428
+
429
+ repo = Scriptorium::Repo.create(test_repo_path, testmode: true)
430
+ repo.create_view("view1", "View One", "First view")
431
+ repo.create_view("view2", "View Two", "Second view")
432
+
433
+ # Start with view2 as current
434
+ assert_equal "view2", repo.current_view.name
435
+
436
+ # Create a new view - should switch to it
437
+ repo.create_view("view3", "View Three", "Third view")
438
+ assert_equal "view3", repo.current_view.name
439
+
440
+ # Check that currentview.txt was updated
441
+ currentview_file = File.join(test_repo_path, "config", "currentview.txt")
442
+ content = read_file(currentview_file).strip
443
+ assert_equal "view3", content, "currentview.txt should contain 'view3'"
444
+
445
+ # Cleanup
446
+ FileUtils.rm_rf(test_repo_path)
447
+ end
448
+
449
+ def test_load_views_reads_currentview_txt
450
+ # Create a test repo for this test
451
+ test_repo_path = "test_repo_view_test"
452
+ FileUtils.rm_rf(test_repo_path) if Dir.exist?(test_repo_path)
453
+
454
+ repo = Scriptorium::Repo.create(test_repo_path, testmode: true)
455
+ repo.create_view("view1", "View One", "First view")
456
+ repo.create_view("view2", "View Two", "Second view")
457
+
458
+ # Switch to view1
459
+ repo.view("view1")
460
+ assert_equal "view1", repo.current_view.name
461
+
462
+ # Create a new repo instance to test load_views
463
+ new_repo = Scriptorium::Repo.open(test_repo_path)
464
+
465
+ # Should load the correct current view from file
466
+ assert_equal "view1", new_repo.current_view.name
467
+
468
+ # Cleanup
469
+ FileUtils.rm_rf(test_repo_path)
470
+ end
471
+
472
+ def test_load_views_handles_missing_currentview_txt
473
+ # Create a test repo for this test
474
+ test_repo_path = "test_repo_view_test"
475
+ FileUtils.rm_rf(test_repo_path) if Dir.exist?(test_repo_path)
476
+
477
+ repo = Scriptorium::Repo.create(test_repo_path, testmode: true)
478
+ repo.create_view("view1", "View One", "First view")
479
+
480
+ # Remove currentview.txt
481
+ currentview_file = File.join(test_repo_path, "config", "currentview.txt")
482
+ File.delete(currentview_file) if File.exist?(currentview_file)
483
+
484
+ # Create a new repo instance
485
+ new_repo = Scriptorium::Repo.open(test_repo_path)
486
+
487
+ # Should handle missing file gracefully
488
+ # The behavior depends on how the repo handles nil current_view
489
+ assert new_repo.current_view.nil? || new_repo.current_view.is_a?(Scriptorium::View)
490
+
491
+ # Cleanup
492
+ FileUtils.rm_rf(test_repo_path)
493
+ end
494
+
495
+ def test_load_views_handles_invalid_view_name
496
+ # Create a test repo for this test
497
+ test_repo_path = "test_repo_view_test"
498
+ FileUtils.rm_rf(test_repo_path) if Dir.exist?(test_repo_path)
499
+
500
+ repo = Scriptorium::Repo.create(test_repo_path, testmode: true)
501
+ repo.create_view("view1", "View One", "First view")
502
+
503
+ # Write an invalid view name to currentview.txt
504
+ currentview_file = File.join(test_repo_path, "config", "currentview.txt")
505
+ write_file(currentview_file, "nonexistent_view")
506
+
507
+ # Create a new repo instance - should handle invalid view name gracefully
508
+ # The current implementation raises an exception, so we expect that
509
+ assert_raises(CannotLookupView) do
510
+ Scriptorium::Repo.open(test_repo_path)
511
+ end
512
+
513
+ # Cleanup
514
+ FileUtils.rm_rf(test_repo_path)
515
+ end
516
+
517
+ def test_view_method_returns_view_object
518
+ # Create a test repo for this test
519
+ test_repo_path = "test_repo_view_test"
520
+ FileUtils.rm_rf(test_repo_path) if Dir.exist?(test_repo_path)
521
+
522
+ repo = Scriptorium::Repo.create(test_repo_path, testmode: true)
523
+ repo.create_view("view1", "View One", "First view")
524
+ repo.create_view("view2", "View Two", "Second view")
525
+
526
+ # The view method should return the view object, not nil
527
+ result = repo.view("view1")
528
+ assert_instance_of Scriptorium::View, result
529
+ assert_equal "view1", result.name
530
+
531
+ # Cleanup
532
+ FileUtils.rm_rf(test_repo_path)
533
+ end
534
+
535
+ def test_view_method_with_nil_returns_current_view
536
+ # Create a test repo for this test
537
+ test_repo_path = "test_repo_view_test"
538
+ FileUtils.rm_rf(test_repo_path) if Dir.exist?(test_repo_path)
539
+
540
+ repo = Scriptorium::Repo.create(test_repo_path, testmode: true)
541
+ repo.create_view("view1", "View One", "First view")
542
+
543
+ # view() with no arguments should return current view
544
+ current = repo.current_view
545
+ result = repo.view
546
+ assert_equal current, result
547
+
548
+ # Cleanup
549
+ FileUtils.rm_rf(test_repo_path)
550
+ end
551
+
552
+ def test_view_method_with_view_object
553
+ # Create a test repo for this test
554
+ test_repo_path = "test_repo_view_test"
555
+ FileUtils.rm_rf(test_repo_path) if Dir.exist?(test_repo_path)
556
+
557
+ repo = Scriptorium::Repo.create(test_repo_path, testmode: true)
558
+ repo.create_view("view1", "View One", "First view")
559
+ repo.create_view("view2", "View Two", "Second view")
560
+
561
+ # Should work with a View object instead of string
562
+ view_obj = repo.lookup_view("view1")
563
+ repo.view(view_obj)
564
+ assert_equal "view1", repo.current_view.name
565
+
566
+ # Cleanup
567
+ FileUtils.rm_rf(test_repo_path)
568
+ end
569
+ end