scriptorium 0.6.1 → 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 (358) hide show
  1. checksums.yaml +4 -4
  2. data/assets/icons/social/reddit.png +0 -0
  3. data/assets/icons/social/x-logo.png +0 -0
  4. data/assets/imagenotfound.jpg +0 -0
  5. data/bin/sblog +84 -5
  6. data/bin/scriptorium +1 -0
  7. data/doc/anti-amnesia/20250727-054000-scriptorium-overview.md +0 -1
  8. data/doc/anti-amnesia/20250727-123000-anti-amnesia-conventions.md +0 -29
  9. data/doc/anti-amnesia/20250727-172600-cursor-rbenv-ruby-version-mystery.md +0 -19
  10. data/doc/anti-amnesia/20250727-172900-ai-cognitive-assessment-capabilities.md +1 -1
  11. data/doc/anti-amnesia/20250728-124243-aaa-syntax-clarification.md +1 -1
  12. data/doc/anti-amnesia/20250729-210000-reddit-autopost-integration-complete.md +1 -1
  13. data/doc/anti-amnesia/20250804-190500-cognitive-loop-bug.md +0 -10
  14. data/doc/anti-amnesia/20250804-190700-anti-amnesia-timestamping-fix.md +1 -4
  15. data/doc/anti-amnesia/20250901-211714-codemirror-integration-and-web-tests.md +172 -0
  16. data/doc/anti-amnesia/20250902-002402-backup-restore-system.md +126 -0
  17. data/doc/anti-amnesia/20250907-203339-backup-metadata-implementation.md +66 -0
  18. data/doc/imported/0001-elixir-conf-2014/metadata.txt +7 -0
  19. data/doc/imported/0001-elixir-conf-2014/post.html +37 -0
  20. data/doc/imported/0001-elixir-conf-2014/source.lt3 +22 -0
  21. data/doc/imported/0002-programmers-and-word-processing/metadata.txt +7 -0
  22. data/doc/imported/0002-programmers-and-word-processing/post.html +192 -0
  23. data/doc/imported/0002-programmers-and-word-processing/source.lt3 +146 -0
  24. data/doc/imported/0003-how-to-turn-your-brain-sideways/metadata.txt +7 -0
  25. data/doc/imported/0003-how-to-turn-your-brain-sideways/post.html +60 -0
  26. data/doc/imported/0003-how-to-turn-your-brain-sideways/source.lt3 +40 -0
  27. data/doc/imported/0004-upcoming-lone-star-ruby-conference/metadata.txt +7 -0
  28. data/doc/imported/0004-upcoming-lone-star-ruby-conference/post.html +42 -0
  29. data/doc/imported/0004-upcoming-lone-star-ruby-conference/source.lt3 +24 -0
  30. data/doc/imported/0005-elixir-conf-2015-announced/metadata.txt +7 -0
  31. data/doc/imported/0005-elixir-conf-2015-announced/post.html +30 -0
  32. data/doc/imported/0005-elixir-conf-2015-announced/source.lt3 +16 -0
  33. data/doc/imported/0006-ruby-for-dinosaurs/metadata.txt +7 -0
  34. data/doc/imported/0006-ruby-for-dinosaurs/post.html +43 -0
  35. data/doc/imported/0006-ruby-for-dinosaurs/source.lt3 +27 -0
  36. data/doc/imported/0007-phoenix-isnt-rails/metadata.txt +7 -0
  37. data/doc/imported/0007-phoenix-isnt-rails/post.html +116 -0
  38. data/doc/imported/0007-phoenix-isnt-rails/source.lt3 +87 -0
  39. data/doc/imported/0008-concerning-the-term-monkeypatching/metadata.txt +7 -0
  40. data/doc/imported/0008-concerning-the-term-monkeypatching/post.html +129 -0
  41. data/doc/imported/0008-concerning-the-term-monkeypatching/source.lt3 +92 -0
  42. data/doc/imported/0009-announcement-coming-soon/metadata.txt +7 -0
  43. data/doc/imported/0009-announcement-coming-soon/post.html +33 -0
  44. data/doc/imported/0009-announcement-coming-soon/source.lt3 +19 -0
  45. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/metadata.txt +7 -0
  46. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/post.html +175 -0
  47. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/source.lt3 +139 -0
  48. data/doc/imported/0011-computer-science-as-a-lost-art/metadata.txt +7 -0
  49. data/doc/imported/0011-computer-science-as-a-lost-art/post.html +139 -0
  50. data/doc/imported/0011-computer-science-as-a-lost-art/source.lt3 +104 -0
  51. data/doc/imported/0012-ruby-day-in-turin-italy/metadata.txt +7 -0
  52. data/doc/imported/0012-ruby-day-in-turin-italy/post.html +42 -0
  53. data/doc/imported/0012-ruby-day-in-turin-italy/source.lt3 +24 -0
  54. data/doc/imported/0013-rubyday-was-a-success/metadata.txt +7 -0
  55. data/doc/imported/0013-rubyday-was-a-success/post.html +44 -0
  56. data/doc/imported/0013-rubyday-was-a-success/source.lt3 +27 -0
  57. data/doc/imported/0014-working-on-the-blogging-software/metadata.txt +7 -0
  58. data/doc/imported/0014-working-on-the-blogging-software/post.html +63 -0
  59. data/doc/imported/0014-working-on-the-blogging-software/source.lt3 +41 -0
  60. data/doc/imported/0015-ok-its-not-really-a-lost-art/metadata.txt +7 -0
  61. data/doc/imported/0015-ok-its-not-really-a-lost-art/post.html +172 -0
  62. data/doc/imported/0015-ok-its-not-really-a-lost-art/source.lt3 +134 -0
  63. data/doc/imported/0016-an-in-operator-for-ruby/metadata.txt +7 -0
  64. data/doc/imported/0016-an-in-operator-for-ruby/post.html +155 -0
  65. data/doc/imported/0016-an-in-operator-for-ruby/source.lt3 +106 -0
  66. data/doc/imported/0017-the-forgotten-mathematician/metadata.txt +7 -0
  67. data/doc/imported/0017-the-forgotten-mathematician/post.html +161 -0
  68. data/doc/imported/0017-the-forgotten-mathematician/source.lt3 +119 -0
  69. data/doc/imported/0018-ruby-puns/metadata.txt +7 -0
  70. data/doc/imported/0018-ruby-puns/post.html +46 -0
  71. data/doc/imported/0018-ruby-puns/source.lt3 +28 -0
  72. data/doc/imported/0019-custom-exceptions-via-metaprogramming/metadata.txt +7 -0
  73. data/doc/imported/0019-custom-exceptions-via-metaprogramming/post.html +138 -0
  74. data/doc/imported/0019-custom-exceptions-via-metaprogramming/source.lt3 +101 -0
  75. data/doc/imported/0020-fffff/metadata.txt +7 -0
  76. data/doc/imported/0020-fffff/post.html +24 -0
  77. data/doc/imported/0020-fffff/source.lt3 +12 -0
  78. data/doc/imported/0021-trying-ror-yet-again/metadata.txt +7 -0
  79. data/doc/imported/0021-trying-ror-yet-again/post.html +26 -0
  80. data/doc/imported/0021-trying-ror-yet-again/source.lt3 +12 -0
  81. data/doc/imported/0023-doctor-sleep/metadata.txt +7 -0
  82. data/doc/imported/0023-doctor-sleep/post.html +63 -0
  83. data/doc/imported/0023-doctor-sleep/source.lt3 +44 -0
  84. data/doc/imported/0024-just-a-test/metadata.txt +7 -0
  85. data/doc/imported/0024-just-a-test/post.html +24 -0
  86. data/doc/imported/0024-just-a-test/source.lt3 +12 -0
  87. data/doc/imported/import_summary.txt +98 -0
  88. data/doc/livetext-informal-spec.txt +65 -0
  89. data/doc/myuserdoc/ch-0.lt3 +31 -0
  90. data/doc/myuserdoc/ch-1.lt3 +37 -0
  91. data/doc/myuserdoc/ch-10.lt3 +22 -0
  92. data/doc/myuserdoc/ch-2.lt3 +37 -0
  93. data/doc/myuserdoc/ch-3.lt3 +19 -0
  94. data/doc/myuserdoc/ch-4.lt3 +43 -0
  95. data/doc/myuserdoc/ch-5.lt3 +22 -0
  96. data/doc/myuserdoc/ch-6.lt3 +19 -0
  97. data/doc/myuserdoc/ch-7.lt3 +16 -0
  98. data/doc/myuserdoc/ch-8.lt3 +13 -0
  99. data/doc/myuserdoc/ch-9.lt3 +19 -0
  100. data/doc/myuserdoc/tweak.rb +18 -0
  101. data/doc/{userdoc-toc.txt → myuserdoc/userdoc-toc.txt} +27 -27
  102. data/doc/old-posts/0001-elixir-conf-2014.lt3 +24 -0
  103. data/doc/old-posts/0002-programmers-and-word-processing.lt3 +150 -0
  104. data/doc/old-posts/0003-how-to-turn-your-brain-sideways.lt3 +43 -0
  105. data/doc/old-posts/0004-upcoming-lone-star-ruby-conference.lt3 +26 -0
  106. data/doc/old-posts/0005-elixir-conf-2015-announced.lt3 +17 -0
  107. data/doc/old-posts/0006-ruby-for-dinosaurs.lt3 +30 -0
  108. data/doc/old-posts/0007-phoenix-isnt-rails.lt3 +90 -0
  109. data/doc/old-posts/0008-concerning-the-term-monkeypatching.lt3 +105 -0
  110. data/doc/old-posts/0009-announcement-coming-soon.lt3 +20 -0
  111. data/doc/old-posts/0010-immutable-data-ditching-the-wax-tablet.lt3 +142 -0
  112. data/doc/old-posts/0011-computer-science-as-a-lost-art.lt3 +117 -0
  113. data/doc/old-posts/0012-ruby-day-in-turin-italy.lt3 +26 -0
  114. data/doc/old-posts/0013-rubyday-was-a-success.lt3 +28 -0
  115. data/doc/old-posts/0014-working-on-the-blogging-software.lt3 +42 -0
  116. data/doc/old-posts/0015-ok-its-not-really-a-lost-art.lt3 +137 -0
  117. data/doc/old-posts/0016-an-in-operator-for-ruby.lt3 +142 -0
  118. data/doc/old-posts/0017-the-forgotten-mathematician.lt3 +129 -0
  119. data/doc/old-posts/0018-ruby-puns.lt3 +31 -0
  120. data/doc/old-posts/0019-custom-exceptions-via-metaprogramming.lt3 +116 -0
  121. data/doc/old-posts/0021-trying-ror-yet-again.lt3 +35 -0
  122. data/doc/old-posts/0023-doctor-sleep.lt3 +43 -0
  123. data/doc/old-posts/0024-just-a-test.lt3 +12 -0
  124. data/doc/old-posts/0025-trying-another-post.lt3 +12 -0
  125. data/doc/old-repo +1 -0
  126. data/doc/reddit_integration.md +2 -2
  127. data/doc/user.lt3 +0 -3
  128. data/lib/scriptorium/api.rb +1811 -78
  129. data/lib/scriptorium/banner_svg.rb +55 -68
  130. data/lib/scriptorium/contract.rb +3 -2
  131. data/lib/scriptorium/exceptions.rb +133 -102
  132. data/lib/scriptorium/helpers.rb +282 -82
  133. data/lib/scriptorium/post.rb +81 -17
  134. data/lib/scriptorium/reddit.rb +1 -1
  135. data/lib/scriptorium/repo.rb +478 -164
  136. data/lib/scriptorium/standard_files.rb +30 -396
  137. data/lib/scriptorium/support/common_js/clipboard.js +35 -0
  138. data/lib/scriptorium/support/common_js/content-loader.js +187 -0
  139. data/lib/scriptorium/support/common_js/navigation.js +52 -0
  140. data/lib/scriptorium/support/common_js/syntax-highlighting.js +27 -0
  141. data/lib/scriptorium/support/config/reddit_template.txt +17 -0
  142. data/{test/scriptorium-TEST-1754622690-146/views/sample → lib/scriptorium/support}/config/social.txt +1 -0
  143. data/lib/scriptorium/support/highlight/css.txt +2 -0
  144. data/lib/scriptorium/support/highlight/custom.css +119 -0
  145. data/lib/scriptorium/support/highlight/js.txt +1 -0
  146. data/lib/scriptorium/support/post_index/config.txt +15 -0
  147. data/lib/scriptorium/support/post_index/style.css +55 -0
  148. data/lib/scriptorium/support/templates/index_entry.lt3 +16 -0
  149. data/{test/scriptorium-TEST-1754622690-146/themes/standard/initial/post.lt3 → lib/scriptorium/support/templates/initial_post.lt3} +5 -5
  150. data/lib/scriptorium/support/templates/post.lt3 +104 -0
  151. data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/header.txt → lib/scriptorium/support/theme/header.lt3} +1 -1
  152. data/lib/scriptorium/theme.rb +83 -70
  153. data/lib/scriptorium/version.rb +2 -2
  154. data/lib/scriptorium/view.rb +194 -149
  155. data/lib/scriptorium.rb +24 -1
  156. data/lib/skeleton.rb +4 -1
  157. data/scriptorium.gemspec +2 -1
  158. data/test/WEB_INTEGRATION_README.md +196 -0
  159. data/test/all +40 -0
  160. data/test/banner_svg/unit.rb +267 -35
  161. data/test/config/deployment.txt +5 -0
  162. data/test/integration/integration_test.rb +7 -7
  163. data/test/integration/preview_flow_test.rb +94 -0
  164. data/test/livetext_plugin_test.rb +453 -182
  165. data/test/manual/banner-tests/test01.html +82 -18
  166. data/test/manual/banner-tests/test02.html +82 -18
  167. data/test/manual/banner-tests/test03.html +82 -18
  168. data/test/manual/banner-tests/test04.html +89 -25
  169. data/test/manual/banner-tests/test05.html +89 -25
  170. data/test/manual/banner-tests/test06.html +89 -25
  171. data/test/manual/banner-tests/test07.html +89 -25
  172. data/test/manual/banner-tests/test08.html +82 -18
  173. data/test/manual/banner-tests/test09.html +82 -18
  174. data/test/manual/banner-tests/test10.html +82 -18
  175. data/test/manual/banner-tests/test11.html +82 -18
  176. data/test/manual/banner-tests/test12.html +82 -18
  177. data/test/manual/banner-tests/test13.html +82 -18
  178. data/test/manual/banner-tests/test14.html +82 -18
  179. data/test/manual/banner-tests/test15.html +82 -18
  180. data/test/manual/banner-tests/test16.html +82 -18
  181. data/test/manual/banner-tests/test17.html +82 -18
  182. data/test/manual/banner-tests/test18.html +90 -26
  183. data/test/manual/banner-tests/test19.html +90 -26
  184. data/test/manual/banner-tests/test20.html +90 -26
  185. data/test/manual/banner-tests/test21.html +90 -26
  186. data/test/manual/banner-tests/test22.html +90 -26
  187. data/test/manual/banner-tests/test23.html +90 -26
  188. data/test/manual/banner-tests/test24.html +90 -26
  189. data/test/manual/banner-tests/test25.html +89 -25
  190. data/test/manual/banner_environment.rb +15 -2
  191. data/test/manual/codemirror_demo.html +773 -0
  192. data/test/manual/create_posts_for_web.rb +114 -0
  193. data/test/manual/preview_manual_test.rb +129 -0
  194. data/test/manual/test_banner_features.rb +14 -14
  195. data/test/manual/test_banner_integration.rb +115 -0
  196. data/test/manual/test_banner_radial.rb +87 -0
  197. data/test/manual/test_syntax_highlighting.rb +60 -40
  198. data/test/support/preview_utils.rb +88 -0
  199. data/test/test_gem_assets.rb +48 -0
  200. data/test/test_helpers.rb +10 -0
  201. data/test/tui_editor_integration_test.rb +15 -15
  202. data/test/tui_integration_test.rb +687 -441
  203. data/test/unit/api.rb +757 -37
  204. data/test/unit/asset_management.rb +195 -221
  205. data/test/unit/backup_test.rb +451 -0
  206. data/test/unit/contract_test.rb +1 -23
  207. data/test/unit/core.rb +415 -61
  208. data/test/unit/deploy_config_test.rb +248 -0
  209. data/test/unit/deploy_test.rb +312 -21
  210. data/test/unit/edit_post_test.rb +168 -0
  211. data/test/unit/gem_asset_management.rb +36 -42
  212. data/test/unit/livetext_basic.rb +23 -35
  213. data/test/unit/livetext_compatibility.rb +7 -14
  214. data/test/unit/parse_cmd_test.rb +260 -0
  215. data/test/unit/{symlink_test.rb → permalink_copy_test.rb} +47 -49
  216. data/test/unit/post.rb +91 -26
  217. data/test/unit/post_index_config_test.rb +258 -0
  218. data/test/unit/post_state_helpers_test.rb +137 -0
  219. data/test/unit/read_commented_file_test.rb +8 -6
  220. data/test/unit/repo.rb +75 -54
  221. data/test/unit/social_test.rb +41 -44
  222. data/test/unit/syntax_highlighting.rb +70 -0
  223. data/test/unit/theme_management_test.rb +91 -0
  224. data/test/unit/view.rb +79 -12
  225. data/test/unit/widgets.rb +8 -8
  226. data/test/web_integration_test.rb +231 -0
  227. data/test/web_test_helper.rb +218 -0
  228. data/test/web_workflow_test.rb +527 -0
  229. data/ui/tui/bin/scriptorium +885 -415
  230. data/ui/web/app/app.rb +1398 -176
  231. data/ui/web/app/assets/livetext_mode.js +244 -0
  232. data/ui/web/app/error_helpers.rb +16 -16
  233. data/ui/web/app/views/advanced_config.erb +8 -2
  234. data/ui/web/app/views/asset_management.erb +56 -0
  235. data/ui/web/app/views/backup_management.erb +238 -0
  236. data/ui/web/app/views/config_widget.erb +232 -0
  237. data/ui/web/app/views/dashboard.erb +64 -72
  238. data/ui/web/app/views/deploy_config.erb +3 -0
  239. data/ui/web/app/views/edit_pages.erb +170 -2
  240. data/ui/web/app/views/edit_post.erb +130 -9
  241. data/ui/web/app/views/edit_theme.erb +73 -0
  242. data/ui/web/app/views/edit_theme_file.erb +74 -0
  243. data/ui/web/app/views/theme_management.erb +130 -0
  244. data/ui/web/app/views/view_dashboard.erb +666 -25
  245. data/ui/web/app/views/widgets.erb +249 -0
  246. data/ui/web/bin/scriptorium-web +35 -24
  247. data/ui/web/tmp/timing.log +17 -0
  248. data/ui/web/tmp/web_server.log +0 -5
  249. metadata +190 -116
  250. data/assets/back-icon.png +0 -0
  251. data/assets/icons/facebook.svg +0 -1
  252. data/assets/icons/github.svg +0 -1
  253. data/assets/icons/instagram.svg +0 -1
  254. data/assets/icons/reddit.svg +0 -1
  255. data/assets/icons/x.svg +0 -1
  256. data/assets/icons/youtube.svg +0 -1
  257. data/bin/scriptorium +0 -1511
  258. data/doc/anti-amnesia/20250727-060000-api-design-tui-planning.md +0 -34
  259. data/doc/anti-amnesia/20250727-061000-runeblog-tui-analysis.md +0 -50
  260. data/doc/anti-amnesia/20250727-154000-livetext-plugin-file-stats.md +0 -73
  261. data/doc/anti-amnesia/20250727-172600-unified-minitest-framework.md +0 -70
  262. data/doc/anti-amnesia/20250727-173000-widget-testing-achievement.md +0 -110
  263. data/doc/anti-amnesia/20250727-180000-post-id-num-refactoring.md +0 -73
  264. data/doc/anti-amnesia/20250728-124421-conversation-summary-concise.md +0 -124
  265. data/doc/anti-amnesia/20250729-190000-scriptorium-tui-testing-complete.md +0 -46
  266. data/doc/anti-amnesia/20250729-200000-scriptorium-tui-testing-edit-file-workflow.md +0 -97
  267. data/doc/anti-amnesia/20250729-211500-dependency-management-system.md +0 -211
  268. data/doc/anti-amnesia/20250729-213000-python-virtual-environment-setup.md +0 -141
  269. data/doc/anti-amnesia/20250729-214500-theme-management-commands.md +0 -211
  270. data/doc/anti-amnesia/20250729-215000-version-update-to-0.6.0.md +0 -134
  271. data/doc/anti-amnesia/20250729-220000-user-guide-complete.md +0 -41
  272. data/doc/anti-amnesia/20250804-213700-publishing-test-fix.md +0 -49
  273. data/doc/anti-amnesia/20250804-214400-additional-test-fixes.md +0 -46
  274. data/doc/anti-amnesia/20250804-220000-asset-function-logic-clarification.md +0 -41
  275. data/doc/anti-amnesia/20250806-202032-asset-function-logic-clarification.md +0 -41
  276. data/doc/anti-amnesia/20250813-082428-syntax-highlighting-and-navigation-improvements.md +0 -256
  277. data/lib/scriptorium/syntax_highlighter.rb +0 -234
  278. data/test/manual/deploy_symlink_demo.rb +0 -142
  279. data/test/manual/symlink_demo.rb +0 -117
  280. data/test/manual/test2.rb +0 -12
  281. data/test/manual/test_banner_from_file.rb +0 -150
  282. data/test/manual/test_banner_in_header.rb +0 -35
  283. data/test/manual/test_code_highlighting.rb +0 -68
  284. data/test/manual/test_complex_header.rb +0 -74
  285. data/test/manual/test_empty_header.rb +0 -32
  286. data/test/manual/test_radial_custom.rb +0 -58
  287. data/test/manual/test_radial_large_radius.rb +0 -52
  288. data/test/manual/test_svg_debug.rb +0 -47
  289. data/test/pages-demo/config/currentview.txt +0 -1
  290. data/test/pages-demo/views/demo/config/common.js +0 -57
  291. data/test/pages-demo/views/demo/config/footer.txt +0 -1
  292. data/test/pages-demo/views/demo/config/global-head.txt +0 -8
  293. data/test/pages-demo/views/demo/config/header.txt +0 -1
  294. data/test/pages-demo/views/demo/config/layout.txt +0 -1
  295. data/test/pages-demo/views/demo/config/left.txt +0 -1
  296. data/test/pages-demo/views/demo/config/main.txt +0 -1
  297. data/test/pages-demo/views/demo/config/right.txt +0 -1
  298. data/test/pages-demo/views/demo/config.txt +0 -3
  299. data/test/pages-demo/views/demo/output/panes/footer.html +0 -1
  300. data/test/pages-demo/views/demo/output/panes/header.html +0 -1
  301. data/test/pages-demo/views/demo/output/panes/left.html +0 -1
  302. data/test/pages-demo/views/demo/output/panes/main.html +0 -1
  303. data/test/pages-demo/views/demo/output/panes/right.html +0 -1
  304. data/test/scriptorium-TEST-1754622690-146/config/bootstrap_css.txt +0 -5
  305. data/test/scriptorium-TEST-1754622690-146/config/bootstrap_js.txt +0 -4
  306. data/test/scriptorium-TEST-1754622690-146/config/common.js +0 -57
  307. data/test/scriptorium-TEST-1754622690-146/config/currentview.txt +0 -1
  308. data/test/scriptorium-TEST-1754622690-146/config/global-head.txt +0 -9
  309. data/test/scriptorium-TEST-1754622690-146/config/last_post_num.txt +0 -1
  310. data/test/scriptorium-TEST-1754622690-146/config/os_helpers.rb +0 -4
  311. data/test/scriptorium-TEST-1754622690-146/config/widgets.txt +0 -3
  312. data/test/scriptorium-TEST-1754622690-146/posts/0001/meta.txt +0 -8
  313. data/test/scriptorium-TEST-1754622690-146/posts/0001/source.lt3 +0 -6
  314. data/test/scriptorium-TEST-1754622690-146/themes/standard/README.txt +0 -1
  315. data/test/scriptorium-TEST-1754622690-146/themes/standard/config.txt +0 -1
  316. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/gen/text.css +0 -1
  317. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index.lt3 +0 -1
  318. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index_entry.lt3 +0 -14
  319. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/post.lt3 +0 -13
  320. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/widget.lt3 +0 -1
  321. data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_css.txt +0 -5
  322. data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_js.txt +0 -4
  323. data/test/scriptorium-TEST-1754622690-146/views/sample/config/common.js +0 -57
  324. data/test/scriptorium-TEST-1754622690-146/views/sample/config/deploy.txt +0 -5
  325. data/test/scriptorium-TEST-1754622690-146/views/sample/config/footer.txt +0 -2
  326. data/test/scriptorium-TEST-1754622690-146/views/sample/config/global-head.txt +0 -9
  327. data/test/scriptorium-TEST-1754622690-146/views/sample/config/header.txt +0 -4
  328. data/test/scriptorium-TEST-1754622690-146/views/sample/config/layout.txt +0 -5
  329. data/test/scriptorium-TEST-1754622690-146/views/sample/config/left.txt +0 -3
  330. data/test/scriptorium-TEST-1754622690-146/views/sample/config/main.txt +0 -5
  331. data/test/scriptorium-TEST-1754622690-146/views/sample/config/right.txt +0 -3
  332. data/test/scriptorium-TEST-1754622690-146/views/sample/config/status.txt +0 -7
  333. data/test/scriptorium-TEST-1754622690-146/views/sample/config.txt +0 -3
  334. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/footer.html +0 -3
  335. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/header.html +0 -3
  336. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/left.html +0 -3
  337. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/main.html +0 -3
  338. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/right.html +0 -3
  339. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/footer.html +0 -1
  340. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/header.html +0 -1
  341. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/left.html +0 -1
  342. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/main.html +0 -1
  343. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/right.html +0 -1
  344. data/ui/web/tmp/web_server.pid +0 -1
  345. /data/{test/pages-demo/views/demo/config/bootstrap_css.txt → lib/scriptorium/support/bootstrap/css.txt} +0 -0
  346. /data/{test/pages-demo/views/demo/config/bootstrap_js.txt → lib/scriptorium/support/bootstrap/js.txt} +0 -0
  347. /data/{test/scriptorium-TEST-1754622690-146/views/sample → lib/scriptorium/support}/config/reddit.txt +0 -0
  348. /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout → lib/scriptorium/support/templates}/layout.txt +0 -0
  349. /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/footer.txt → lib/scriptorium/support/theme/footer.lt3} +0 -0
  350. /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/left.txt → lib/scriptorium/support/theme/left.lt3} +0 -0
  351. /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/main.txt → lib/scriptorium/support/theme/main.lt3} +0 -0
  352. /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/right.txt → lib/scriptorium/support/theme/right.lt3} +0 -0
  353. /data/test/manual/banner-tests/{config.txt → svg.txt} +0 -0
  354. /data/test/manual/{test6.rb → test_advanced_widgets.rb} +0 -0
  355. /data/test/manual/{test1.rb → test_basic_posts.rb} +0 -0
  356. /data/test/manual/{test4.rb → test_layout_widgets.rb} +0 -0
  357. /data/test/manual/{test5.rb → test_pagination.rb} +0 -0
  358. /data/test/manual/{test3.rb → test_random_posts.rb} +0 -0
data/test/unit/core.rb CHANGED
@@ -29,7 +29,7 @@ class TestReadWrite < Minitest::Test
29
29
 
30
30
  def test_001_need_file_basic_functionality
31
31
  file_path = "#{@test_dir}/test_file.txt"
32
- File.write(file_path, "test content")
32
+ write_file(file_path, "test content")
33
33
 
34
34
  result = need(:file, file_path)
35
35
  assert_equal file_path, result
@@ -62,19 +62,19 @@ class TestReadWrite < Minitest::Test
62
62
  end
63
63
 
64
64
  def test_004_need_nil_path_raises_error
65
- assert_raises(CannotRequirePathNil) do
65
+ assert_raises(RequirePathNil) do
66
66
  need(:file, nil)
67
67
  end
68
68
  end
69
69
 
70
70
  def test_005_need_empty_path_raises_error
71
- assert_raises(CannotRequirePathEmpty) do
71
+ assert_raises(RequirePathEmpty) do
72
72
  need(:file, "")
73
73
  end
74
74
  end
75
75
 
76
76
  def test_006_need_whitespace_path_raises_error
77
- assert_raises(CannotRequirePathEmpty) do
77
+ assert_raises(RequirePathEmpty) do
78
78
  need(:file, " ")
79
79
  end
80
80
  end
@@ -113,29 +113,27 @@ class TestReadWrite < Minitest::Test
113
113
 
114
114
  def test_011_system_with_description
115
115
  # Test that description is included in error message
116
- begin
117
- system!("false", "testing failure")
118
- flunk "Expected system! to raise an error"
119
- rescue CommandFailedWithDesc => e
120
- assert_match /testing failure/, e.message
121
- assert_match /Command failed/, e.message
122
- end
116
+ system!("false", "testing failure")
117
+ flunk "Expected system! to raise an error"
118
+ rescue CommandFailedWithDesc => e
119
+ assert_match /testing failure/, e.message
120
+ assert_match /Command failed/, e.message
123
121
  end
124
122
 
125
123
  def test_012_system_nil_command_raises_error
126
- assert_raises(CannotExecuteCommandNil) do
124
+ assert_raises(CommandNil) do
127
125
  system!(nil)
128
126
  end
129
127
  end
130
128
 
131
129
  def test_013_system_empty_command_raises_error
132
- assert_raises(CannotExecuteCommandEmpty) do
130
+ assert_raises(CommandEmpty) do
133
131
  system!("")
134
132
  end
135
133
  end
136
134
 
137
135
  def test_014_system_whitespace_command_raises_error
138
- assert_raises(CannotExecuteCommandEmpty) do
136
+ assert_raises(CommandEmpty) do
139
137
  system!(" ")
140
138
  end
141
139
  end
@@ -185,19 +183,19 @@ class TestReadWrite < Minitest::Test
185
183
  end
186
184
 
187
185
  def test_020_make_dir_nil_path_raises_error
188
- assert_raises(CannotCreateDirectoryPathNil) do
186
+ assert_raises(DirectoryPathNil) do
189
187
  make_dir(nil)
190
188
  end
191
189
  end
192
190
 
193
191
  def test_021_make_dir_empty_path_raises_error
194
- assert_raises(CannotCreateDirectoryPathEmpty) do
192
+ assert_raises(DirectoryPathEmpty) do
195
193
  make_dir("")
196
194
  end
197
195
  end
198
196
 
199
197
  def test_022_make_dir_whitespace_path_raises_error
200
- assert_raises(CannotCreateDirectoryPathEmpty) do
198
+ assert_raises(DirectoryPathEmpty) do
201
199
  make_dir(" ")
202
200
  end
203
201
  end
@@ -211,7 +209,7 @@ class TestReadWrite < Minitest::Test
211
209
  file_path = "#{read_only_dir}/test_dir"
212
210
 
213
211
  # This should raise a permission denied error
214
- assert_raises(CannotCreateDirectoryPermissionDenied) do
212
+ assert_raises(DirectoryPermissionDenied) do
215
213
  make_dir(file_path)
216
214
  end
217
215
 
@@ -230,7 +228,7 @@ class TestReadWrite < Minitest::Test
230
228
  write_file(file_path, content.join("\n"))
231
229
 
232
230
  assert File.exist?(file_path)
233
- assert_equal content.join("\n") + "\n", File.read(file_path)
231
+ assert_equal content.join("\n") + "\n", read_file(file_path)
234
232
  end
235
233
 
236
234
  def test_025_write_file_creates_parent_directories
@@ -249,7 +247,7 @@ class TestReadWrite < Minitest::Test
249
247
  write_file(file_path, "")
250
248
 
251
249
  assert File.exist?(file_path)
252
- assert_equal "\n", File.read(file_path)
250
+ assert_equal "\n", read_file(file_path)
253
251
  end
254
252
 
255
253
  def test_027_write_file_single_line
@@ -259,7 +257,7 @@ class TestReadWrite < Minitest::Test
259
257
  write_file(file_path, content)
260
258
 
261
259
  assert File.exist?(file_path)
262
- assert_equal content + "\n", File.read(file_path)
260
+ assert_equal content + "\n", read_file(file_path)
263
261
  end
264
262
 
265
263
  def test_028_write_file_multiple_lines
@@ -270,23 +268,23 @@ class TestReadWrite < Minitest::Test
270
268
 
271
269
  assert File.exist?(file_path)
272
270
  expected = content.join("\n") + "\n"
273
- assert_equal expected, File.read(file_path)
271
+ assert_equal expected, read_file(file_path)
274
272
  end
275
273
 
276
274
  def test_029_write_file_nil_path_raises_error
277
- assert_raises(CannotWriteFilePathNil) do
275
+ assert_raises(FilePathNil) do
278
276
  write_file(nil, "content")
279
277
  end
280
278
  end
281
279
 
282
280
  def test_030_write_file_empty_path_raises_error
283
- assert_raises(CannotWriteFilePathEmpty) do
281
+ assert_raises(FilePathEmpty) do
284
282
  write_file("", "content")
285
283
  end
286
284
  end
287
285
 
288
286
  def test_031_write_file_whitespace_path_raises_error
289
- assert_raises(CannotWriteFilePathEmpty) do
287
+ assert_raises(FilePathEmpty) do
290
288
  write_file(" ", "content")
291
289
  end
292
290
  end
@@ -300,7 +298,7 @@ class TestReadWrite < Minitest::Test
300
298
  file_path = "#{read_only_dir}/test.txt"
301
299
 
302
300
  # This should raise a permission denied error
303
- assert_raises(CannotWriteFilePermissionDenied) do
301
+ assert_raises(FilePermissionDenied) do
304
302
  write_file(file_path, "content")
305
303
  end
306
304
 
@@ -308,6 +306,115 @@ class TestReadWrite < Minitest::Test
308
306
  FileUtils.chmod(0755, read_only_dir)
309
307
  end
310
308
 
309
+ def test_033_write_file_empty_true_creates_file
310
+ file_path = "#{@test_dir}/empty_true.txt"
311
+
312
+ # Should create file with empty: true even for empty content
313
+ write_file(file_path, "", empty: true)
314
+
315
+ assert File.exist?(file_path)
316
+ assert_equal "", read_file(file_path) # No newline added
317
+ end
318
+
319
+ def test_034_write_file_empty_true_nil_content
320
+ file_path = "#{@test_dir}/empty_true_nil.txt"
321
+
322
+ # Should create file with empty: true even for nil content
323
+ write_file(file_path, nil, empty: true)
324
+
325
+ assert File.exist?(file_path)
326
+ assert_equal "", read_file(file_path) # No newline added
327
+ end
328
+
329
+ def test_035_write_file_empty_true_whitespace_content
330
+ file_path = "#{@test_dir}/empty_true_whitespace.txt"
331
+
332
+ # Should create file with empty: true for whitespace-only content
333
+ write_file(file_path, " ", empty: true)
334
+
335
+ assert File.exist?(file_path)
336
+ assert_equal "", read_file(file_path) # No newline added
337
+ end
338
+
339
+ def test_036_write_file_empty_true_existing_file
340
+ file_path = "#{@test_dir}/empty_true_existing.txt"
341
+
342
+ # Create file with content first
343
+ write_file(file_path, "original content")
344
+ original_size = File.size(file_path)
345
+
346
+ # Now write empty content with empty: true
347
+ write_file(file_path, "", empty: true)
348
+
349
+ assert File.exist?(file_path)
350
+ assert_equal "original content\n", read_file(file_path) # File should preserve original content
351
+ assert_equal original_size, File.size(file_path) # File size should be unchanged
352
+ end
353
+
354
+ def test_037_write_file_empty_true_with_content
355
+ file_path = "#{@test_dir}/empty_true_with_content.txt"
356
+ content = "some content"
357
+
358
+ # empty: true should be ignored when there's actual content
359
+ write_file(file_path, content, empty: true)
360
+
361
+ assert File.exist?(file_path)
362
+ assert_equal content + "\n", read_file(file_path) # Normal behavior
363
+ end
364
+
365
+ # ========================================
366
+ # write_file! tests
367
+ # ========================================
368
+
369
+ def test_038_write_file_bang_basic_functionality
370
+ file_path = "#{@test_dir}/bang_basic.txt"
371
+
372
+ write_file!(file_path, "line1", "line2", "line3")
373
+
374
+ assert File.exist?(file_path)
375
+ assert_equal "line1\nline2\nline3\n", read_file(file_path)
376
+ end
377
+
378
+ def test_039_write_file_bang_empty_true_no_lines
379
+ file_path = "#{@test_dir}/bang_empty_true.txt"
380
+
381
+ # Should create file with empty: true when no lines provided
382
+ write_file!(file_path, empty: true)
383
+
384
+ assert File.exist?(file_path)
385
+ assert_equal "", read_file(file_path) # No newline added
386
+ end
387
+
388
+ def test_040_write_file_bang_empty_true_nil_lines
389
+ file_path = "#{@test_dir}/bang_empty_true_nil.txt"
390
+
391
+ # Should create file with empty: true when all lines are nil
392
+ write_file!(file_path, nil, nil, nil, empty: true)
393
+
394
+ assert File.exist?(file_path)
395
+ assert_equal "", read_file(file_path) # No newline added
396
+ end
397
+
398
+ def test_041_write_file_bang_empty_true_whitespace_lines
399
+ file_path = "#{@test_dir}/bang_empty_true_whitespace.txt"
400
+
401
+ # Should create file with empty: true when all lines are whitespace
402
+ write_file!(file_path, "", " ", "", empty: true)
403
+
404
+ assert File.exist?(file_path)
405
+ assert_equal "", read_file(file_path) # No newline added
406
+ end
407
+
408
+ def test_042_write_file_bang_empty_true_with_content
409
+ file_path = "#{@test_dir}/bang_empty_true_with_content.txt"
410
+
411
+ # empty: true should be ignored when there's actual content
412
+ write_file!(file_path, "line1", "", "line3", empty: true)
413
+
414
+ assert File.exist?(file_path)
415
+ assert_equal "line1\n\nline3\n", read_file(file_path) # Normal behavior
416
+ end
417
+
311
418
  # ========================================
312
419
  # read_file tests
313
420
  # ========================================
@@ -315,27 +422,27 @@ class TestReadWrite < Minitest::Test
315
422
  def test_033_read_file_basic_functionality
316
423
  file_path = "#{@test_dir}/read_test.txt"
317
424
  content = "line1\nline2\nline3"
318
- File.write(file_path, content)
425
+ write_file(file_path, content)
319
426
 
320
427
  result = read_file(file_path)
321
428
 
322
- assert_equal content, result
429
+ assert_equal content + "\n", result
323
430
  end
324
431
 
325
432
  def test_034_read_file_as_lines
326
433
  file_path = "#{@test_dir}/lines_test.txt"
327
434
  content = "line1\nline2\nline3"
328
- File.write(file_path, content)
435
+ write_file(file_path, content)
329
436
 
330
437
  result = read_file(file_path, lines: true)
331
438
 
332
- assert_equal ["line1\n", "line2\n", "line3"], result
439
+ assert_equal ["line1\n", "line2\n", "line3\n"], result
333
440
  end
334
441
 
335
442
  def test_035_read_file_as_lines_with_chomp
336
443
  file_path = "#{@test_dir}/chomp_test.txt"
337
444
  content = "line1\nline2\nline3\n"
338
- File.write(file_path, content)
445
+ write_file(file_path, content)
339
446
 
340
447
  result = read_file(file_path, lines: true, chomp: true)
341
448
 
@@ -345,7 +452,7 @@ class TestReadWrite < Minitest::Test
345
452
  def test_036_read_file_as_lines_without_chomp
346
453
  file_path = "#{@test_dir}/no_chomp_test.txt"
347
454
  content = "line1\nline2\nline3\n"
348
- File.write(file_path, content)
455
+ write_file(file_path, content)
349
456
 
350
457
  result = read_file(file_path, lines: true, chomp: false)
351
458
 
@@ -364,25 +471,25 @@ class TestReadWrite < Minitest::Test
364
471
  def test_038_read_file_missing_file_without_fallback_raises_error
365
472
  file_path = "#{@test_dir}/nonexistent.txt"
366
473
 
367
- assert_raises(CannotReadFileNotFound) do
474
+ assert_raises(ReadFileNotFound) do
368
475
  read_file(file_path)
369
476
  end
370
477
  end
371
478
 
372
479
  def test_039_read_file_nil_path_raises_error
373
- assert_raises(CannotReadFilePathNil) do
480
+ assert_raises(ReadFilePathNil) do
374
481
  read_file(nil)
375
482
  end
376
483
  end
377
484
 
378
485
  def test_040_read_file_empty_path_raises_error
379
- assert_raises(CannotReadFilePathEmpty) do
486
+ assert_raises(ReadFilePathEmpty) do
380
487
  read_file("")
381
488
  end
382
489
  end
383
490
 
384
491
  def test_041_read_file_whitespace_path_raises_error
385
- assert_raises(CannotReadFilePathEmpty) do
492
+ assert_raises(ReadFilePathEmpty) do
386
493
  read_file(" ")
387
494
  end
388
495
  end
@@ -390,10 +497,10 @@ class TestReadWrite < Minitest::Test
390
497
  def test_042_read_file_permission_denied_simulation
391
498
  # Create a file and make it unreadable
392
499
  file_path = "#{@test_dir}/unreadable.txt"
393
- File.write(file_path, "content")
500
+ write_file(file_path, "content")
394
501
  FileUtils.chmod(0000, file_path)
395
502
 
396
- assert_raises(CannotReadFilePermissionDenied) do
503
+ assert_raises(ReadFilePermissionDenied) do
397
504
  read_file(file_path)
398
505
  end
399
506
 
@@ -403,46 +510,46 @@ class TestReadWrite < Minitest::Test
403
510
 
404
511
  def test_043_read_file_empty_file
405
512
  file_path = "#{@test_dir}/empty.txt"
406
- File.write(file_path, "")
513
+ write_file(file_path, "")
407
514
 
408
515
  result = read_file(file_path)
409
516
 
410
- assert_equal "", result
517
+ assert_equal "\n", result
411
518
  end
412
519
 
413
520
  def test_044_read_file_empty_file_as_lines
414
521
  file_path = "#{@test_dir}/empty_lines.txt"
415
- File.write(file_path, "")
522
+ write_file(file_path, "")
416
523
 
417
524
  result = read_file(file_path, lines: true)
418
525
 
419
- assert_equal [], result
526
+ assert_equal ["\n"], result
420
527
  end
421
528
 
422
529
  def test_045_read_file_single_line
423
530
  file_path = "#{@test_dir}/single_line.txt"
424
531
  content = "single line"
425
- File.write(file_path, content)
532
+ write_file(file_path, content)
426
533
 
427
534
  result = read_file(file_path)
428
535
 
429
- assert_equal content, result
536
+ assert_equal content + "\n", result
430
537
  end
431
538
 
432
539
  def test_046_read_file_single_line_as_lines
433
540
  file_path = "#{@test_dir}/single_line.txt"
434
541
  content = "single line"
435
- File.write(file_path, content)
542
+ write_file(file_path, content)
436
543
 
437
544
  result = read_file(file_path, lines: true)
438
545
 
439
- assert_equal [content], result
546
+ assert_equal [content + "\n"], result
440
547
  end
441
548
 
442
549
  def test_047_read_file_with_trailing_newline
443
550
  file_path = "#{@test_dir}/trailing_newline.txt"
444
551
  content = "line1\nline2\n"
445
- File.write(file_path, content)
552
+ write_file(file_path, content)
446
553
 
447
554
  result = read_file(file_path)
448
555
 
@@ -452,7 +559,7 @@ class TestReadWrite < Minitest::Test
452
559
  def test_048_read_file_with_trailing_newline_as_lines
453
560
  file_path = "#{@test_dir}/trailing_newline.txt"
454
561
  content = "line1\nline2\n"
455
- File.write(file_path, content)
562
+ write_file(file_path, content)
456
563
 
457
564
  result = read_file(file_path, lines: true)
458
565
 
@@ -462,7 +569,7 @@ class TestReadWrite < Minitest::Test
462
569
  def test_049_read_file_with_trailing_newline_as_lines_no_chomp
463
570
  file_path = "#{@test_dir}/trailing_newline.txt"
464
571
  content = "line1\nline2\n"
465
- File.write(file_path, content)
572
+ write_file(file_path, content)
466
573
 
467
574
  result = read_file(file_path, lines: true, chomp: false)
468
575
 
@@ -507,11 +614,11 @@ class TestReadWrite < Minitest::Test
507
614
 
508
615
  # First write
509
616
  write_file(file_path, "first content")
510
- assert_equal "first content\n", File.read(file_path)
617
+ assert_equal "first content\n", read_file(file_path)
511
618
 
512
619
  # Second write (should overwrite)
513
620
  write_file(file_path, "second content")
514
- assert_equal "second content\n", File.read(file_path)
621
+ assert_equal "second content\n", read_file(file_path)
515
622
  end
516
623
 
517
624
  def test_053_write_file_with_special_characters
@@ -638,9 +745,9 @@ class TestReadWrite < Minitest::Test
638
745
  end
639
746
 
640
747
  def test_063_get_asset_path
641
- name = "back-icon.png"
748
+ name = "back.png"
642
749
  result = get_asset_path(name)
643
- assert result == "assets/#{name}", "Expected #{name} to be in assets (got #{result})"
750
+ assert result == "assets/icons/ui/#{name}", "Expected #{name} to be found recursively (got #{result})"
644
751
  assert_raises(AssetNotFound) { get_asset_path("nonexistent.png") }
645
752
  end
646
753
 
@@ -734,7 +841,7 @@ class TestReadWrite < Minitest::Test
734
841
 
735
842
  def test_073_getvars_basic
736
843
  config_file = "#{@test_dir}/config.txt"
737
- File.write(config_file, <<~EOS)
844
+ write_file(config_file, <<~EOS)
738
845
  title My Blog
739
846
  subtitle A test blog
740
847
  theme standard
@@ -748,7 +855,7 @@ class TestReadWrite < Minitest::Test
748
855
 
749
856
  def test_074_getvars_with_comments
750
857
  config_file = "#{@test_dir}/config_with_comments.txt"
751
- File.write(config_file, <<~EOS)
858
+ write_file(config_file, <<~EOS)
752
859
  # This is a comment
753
860
  title My Blog # Another comment
754
861
  subtitle A test blog
@@ -766,7 +873,7 @@ class TestReadWrite < Minitest::Test
766
873
 
767
874
  def test_075_getvars_with_empty_values
768
875
  config_file = "#{@test_dir}/config_empty.txt"
769
- File.write(config_file, <<~EOS)
876
+ write_file(config_file, <<~EOS)
770
877
  title My Blog
771
878
  subtitle
772
879
  theme standard
@@ -774,7 +881,7 @@ class TestReadWrite < Minitest::Test
774
881
 
775
882
  result = getvars(config_file)
776
883
  assert_equal "My Blog", result[:title]
777
- assert_nil result[:subtitle] # When no value after key, split returns nil
884
+ assert_equal "", result[:subtitle] # When no value after key, split returns empty string
778
885
  assert_equal "standard", result[:theme]
779
886
  end
780
887
 
@@ -822,7 +929,7 @@ class TestReadWrite < Minitest::Test
822
929
 
823
930
  def test_081_make_tree_basic
824
931
  tree_text = <<~EOS
825
- test_tree/
932
+ .
826
933
  ├── file1.txt
827
934
  ├── dir1/
828
935
  │ ├── file2.txt
@@ -831,7 +938,7 @@ class TestReadWrite < Minitest::Test
831
938
  └── file4.txt
832
939
  EOS
833
940
 
834
- make_tree(@test_dir, tree_text)
941
+ make_tree("#{@test_dir}/test_tree", tree_text)
835
942
 
836
943
  assert File.exist?("#{@test_dir}/test_tree/file1.txt")
837
944
  assert File.exist?("#{@test_dir}/test_tree/dir1/file2.txt")
@@ -841,17 +948,264 @@ class TestReadWrite < Minitest::Test
841
948
 
842
949
  def test_082_make_tree_with_comments
843
950
  tree_text = <<~EOS
844
- test_tree_with_comments/
951
+ .
845
952
  ├── file1.txt # This is a comment
846
953
  ├── dir1/ # Another comment
847
954
  │ └── file2.txt
848
955
  └── file3.txt
849
956
  EOS
850
957
 
851
- make_tree(@test_dir, tree_text)
958
+ make_tree("#{@test_dir}/test_tree_with_comments", tree_text)
852
959
 
853
960
  assert File.exist?("#{@test_dir}/test_tree_with_comments/file1.txt")
854
961
  assert File.exist?("#{@test_dir}/test_tree_with_comments/dir1/file2.txt")
855
962
  assert File.exist?("#{@test_dir}/test_tree_with_comments/file3.txt")
856
963
  end
964
+
965
+ def test_083_make_tree_simplified
966
+ tree_text = <<~EOS
967
+ .
968
+ ├── config/
969
+ ├── views/
970
+ ├── drafts/
971
+ ├── posts/
972
+ ├── assets/
973
+ │ └── library/
974
+ └── themes/
975
+ EOS
976
+
977
+ target_dir = "#{@test_dir}/simplified_tree"
978
+ make_tree(target_dir, tree_text)
979
+
980
+ assert File.exist?("#{target_dir}/config")
981
+ assert File.exist?("#{target_dir}/views")
982
+ assert File.exist?("#{target_dir}/drafts")
983
+ assert File.exist?("#{target_dir}/posts")
984
+ assert File.exist?("#{target_dir}/assets/library")
985
+ assert File.exist?("#{target_dir}/themes")
986
+ end
987
+
988
+ # ========================================
989
+ # Additional exception tests for untested exceptions
990
+ # ========================================
991
+
992
+ def test_093_general_validation_exceptions
993
+ # Test that exception classes exist
994
+ assert NilValueError
995
+ assert EmptyValueError
996
+ assert InvalidFormatError
997
+
998
+ # Test actual exception raising for some that we can trigger
999
+ assert_raises(InvalidType) do
1000
+ need(:invalid_type, "some/path")
1001
+ end
1002
+
1003
+ # Test require path exceptions
1004
+ assert_raises(RequirePathNil) do
1005
+ need(:file, nil)
1006
+ end
1007
+
1008
+ assert_raises(RequirePathEmpty) do
1009
+ need(:file, "")
1010
+ end
1011
+ end
1012
+
1013
+ def test_094_repository_exceptions
1014
+ # Test RepoDirAlreadyExists
1015
+ assert RepoDirAlreadyExists
1016
+
1017
+ # Test ViewDirDoesntExist
1018
+ assert ViewDirDoesntExist
1019
+
1020
+ # Test ThemeFileNotFound
1021
+ assert ThemeFileNotFound
1022
+
1023
+ # Test NoGemPath
1024
+ assert NoGemPath
1025
+ end
1026
+
1027
+ def test_095_file_io_exceptions
1028
+ # Test that exception classes exist
1029
+ assert FileNotFoundError
1030
+ assert DirectoryNotFoundError
1031
+ assert FileDiskFull
1032
+ assert FileDirectoryNotFound
1033
+ assert CannotWriteFileError
1034
+ assert DirectoryParentNotFound
1035
+ assert DirectoryDiskFull
1036
+ assert DirectoryError
1037
+ assert ReadFileError
1038
+
1039
+ # Test actual exception raising for some that we can trigger
1040
+ assert_raises(ReadFilePathNil) do
1041
+ read_file(nil)
1042
+ end
1043
+
1044
+ assert_raises(ReadFilePathEmpty) do
1045
+ read_file("")
1046
+ end
1047
+
1048
+ assert_raises(FilePathNil) do
1049
+ write_file(nil, "content")
1050
+ end
1051
+
1052
+ assert_raises(FilePathEmpty) do
1053
+ write_file("", "content")
1054
+ end
1055
+
1056
+ # Test directory-related exceptions
1057
+ assert_raises(DirectoryPathNil) do
1058
+ make_dir(nil)
1059
+ end
1060
+
1061
+ assert_raises(DirectoryPathEmpty) do
1062
+ make_dir("")
1063
+ end
1064
+ end
1065
+
1066
+ def test_096_command_system_exceptions
1067
+ # Test that exception classes exist
1068
+ assert CommandFailed
1069
+ assert CannotExecuteCommand
1070
+ assert SectionOutputError
1071
+ assert WriteFrontPageError
1072
+
1073
+ # Test actual exception raising for some that we can trigger
1074
+ assert_raises(CommandNil) do
1075
+ system!(nil)
1076
+ end
1077
+
1078
+ assert_raises(CommandEmpty) do
1079
+ system!("")
1080
+ end
1081
+ end
1082
+
1083
+ def test_097_validation_exceptions
1084
+ # Test ViewTargetNil
1085
+ assert ViewTargetNil
1086
+
1087
+ # Test ViewTargetEmpty
1088
+ assert ViewTargetEmpty
1089
+
1090
+ # Test ViewTargetInvalid
1091
+ assert ViewTargetInvalid
1092
+
1093
+ # Test PostIdNil
1094
+ assert PostIdNil
1095
+
1096
+ # Test PostIdEmpty
1097
+ assert PostIdEmpty
1098
+
1099
+ # Test PostIdInvalid
1100
+ assert PostIdInvalid
1101
+
1102
+ # Test CannotCreateView
1103
+ assert CannotCreateView
1104
+
1105
+ # Test CannotBuildWidget
1106
+ assert CannotBuildWidget
1107
+
1108
+ # Test CannotGetPost
1109
+ assert CannotGetPost
1110
+
1111
+ # Test CannotSetPubdate
1112
+ assert CannotSetPubdate
1113
+ end
1114
+
1115
+ # ========================================
1116
+ # format_date() tests
1117
+ # ========================================
1118
+
1119
+ def test_001_format_date_basic_functionality
1120
+ # Test basic date formatting
1121
+ result = format_date("month dd yyyy", Date.new(2025, 2, 22))
1122
+ assert_equal "February 22 2025", result
1123
+ end
1124
+
1125
+ def test_002_format_date_with_ordinal
1126
+ # Test ordinal day formatting
1127
+ result = format_date("month day, yyyy", Date.new(2025, 2, 22))
1128
+ assert_equal "February 22nd, 2025", result
1129
+ end
1130
+
1131
+ def test_003_format_date_with_break
1132
+ # Test line break formatting
1133
+ result = format_date("month dd break yyyy", Date.new(2025, 2, 22))
1134
+ assert_equal "February 22<br>2025", result
1135
+ end
1136
+
1137
+ def test_004_format_date_string_input
1138
+ # Test string date input
1139
+ result = format_date("dd month yyyy", "2025-02-22")
1140
+ assert_equal "22 February 2025", result
1141
+ end
1142
+
1143
+ def test_005_format_date_nil_input
1144
+ # Test nil date input (should return original format)
1145
+ result = format_date("month dd yyyy", nil)
1146
+ assert_equal "month dd yyyy", result
1147
+ end
1148
+
1149
+ def test_006_format_date_invalid_string
1150
+ # Test invalid date string (should return original format)
1151
+ result = format_date("month dd yyyy", "invalid-date")
1152
+ assert_equal "month dd yyyy", result
1153
+ end
1154
+
1155
+ def test_007_format_date_ordinal_edge_cases
1156
+ # Test ordinal edge cases
1157
+ assert_equal "1st", ordinalize(1)
1158
+ assert_equal "2nd", ordinalize(2)
1159
+ assert_equal "3rd", ordinalize(3)
1160
+ assert_equal "4th", ordinalize(4)
1161
+ assert_equal "11th", ordinalize(11)
1162
+ assert_equal "12th", ordinalize(12)
1163
+ assert_equal "13th", ordinalize(13)
1164
+ assert_equal "21st", ordinalize(21)
1165
+ assert_equal "22nd", ordinalize(22)
1166
+ assert_equal "23rd", ordinalize(23)
1167
+ end
1168
+
1169
+ def test_008_format_date_various_formats
1170
+ # Test various format combinations
1171
+ date = Date.new(2025, 2, 22)
1172
+
1173
+ assert_equal "02/22/2025", format_date("mm/dd/yyyy", date)
1174
+ assert_equal "22/02/25", format_date("dd/mm/yy", date)
1175
+ assert_equal "February 22nd, 2025", format_date("month day, yyyy", date)
1176
+ assert_equal "22 February 2025", format_date("dd month yyyy", date)
1177
+ assert_equal "February 22<br>2025", format_date("month dd break yyyy", date)
1178
+ end
1179
+
1180
+ def test_009_format_date_smart_padding
1181
+ # Test smart padding logic
1182
+ date = Date.new(2025, 7, 4)
1183
+
1184
+ # Should pad when surrounded by punctuation
1185
+ assert_equal "04/07/2025", format_date("dd/mm/yyyy", date)
1186
+ assert_equal "07/04/2025", format_date("mm/dd/yyyy", date)
1187
+ assert_equal "2025-07-04", format_date("yyyy-mm-dd", date)
1188
+ assert_equal "04.07.2025", format_date("dd.mm.yyyy", date)
1189
+
1190
+ # Should not pad when surrounded by spaces
1191
+ assert_equal "July 4, 2025", format_date("month dd, yyyy", date)
1192
+ assert_equal "4 July 2025", format_date("dd month yyyy", date)
1193
+ assert_equal "July 4 2025", format_date("month dd yyyy", date)
1194
+ end
1195
+
1196
+ private
1197
+
1198
+ def ordinalize(number)
1199
+ case number % 100
1200
+ when 11, 12, 13
1201
+ "#{number}th"
1202
+ else
1203
+ case number % 10
1204
+ when 1 then "#{number}st"
1205
+ when 2 then "#{number}nd"
1206
+ when 3 then "#{number}rd"
1207
+ else "#{number}th"
1208
+ end
1209
+ end
1210
+ end
857
1211
  end