scriptorium 0.0.3 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. checksums.yaml +4 -4
  2. data/README.lt3 +324 -0
  3. data/README.md +3155 -1
  4. data/assets/.DS_Store +0 -0
  5. data/assets/README.md +44 -0
  6. data/assets/back-icon.png +0 -0
  7. data/assets/icons/facebook.svg +1 -0
  8. data/assets/icons/github.svg +1 -0
  9. data/assets/icons/instagram.svg +1 -0
  10. data/assets/icons/reddit.svg +1 -0
  11. data/assets/icons/ui/.DS_Store +0 -0
  12. data/assets/icons/ui/back.png +0 -0
  13. data/assets/icons/ui/copy.png +0 -0
  14. data/assets/icons/ui/down.png +0 -0
  15. data/assets/icons/ui/end.png +0 -0
  16. data/assets/icons/ui/exit.png +0 -0
  17. data/assets/icons/ui/foo +10 -0
  18. data/assets/icons/ui/home.png +0 -0
  19. data/assets/icons/ui/left.png +0 -0
  20. data/assets/icons/ui/next.png +0 -0
  21. data/assets/icons/ui/right.png +0 -0
  22. data/assets/icons/ui/start.png +0 -0
  23. data/assets/icons/ui/up.png +0 -0
  24. data/assets/icons/x.svg +1 -0
  25. data/assets/icons/youtube.svg +1 -0
  26. data/assets/samples/placeholder.svg +9 -0
  27. data/assets/themes/standard/favicon.svg +6 -0
  28. data/bin/scriptorium +1511 -0
  29. data/doc/README.txt +6 -0
  30. data/doc/anti-amnesia/20250727-054000-scriptorium-overview.md +95 -0
  31. data/doc/anti-amnesia/20250727-060000-api-design-tui-planning.md +34 -0
  32. data/doc/anti-amnesia/20250727-061000-runeblog-tui-analysis.md +50 -0
  33. data/doc/anti-amnesia/20250727-123000-anti-amnesia-conventions.md +31 -0
  34. data/doc/anti-amnesia/20250727-154000-livetext-plugin-file-stats.md +73 -0
  35. data/doc/anti-amnesia/20250727-172600-cursor-rbenv-ruby-version-mystery.md +64 -0
  36. data/doc/anti-amnesia/20250727-172600-unified-minitest-framework.md +70 -0
  37. data/doc/anti-amnesia/20250727-172900-ai-cognitive-assessment-capabilities.md +40 -0
  38. data/doc/anti-amnesia/20250727-173000-widget-testing-achievement.md +110 -0
  39. data/doc/anti-amnesia/20250727-180000-post-id-num-refactoring.md +73 -0
  40. data/doc/anti-amnesia/20250728-124243-aaa-syntax-clarification.md +46 -0
  41. data/doc/anti-amnesia/20250728-124421-conversation-summary-concise.md +124 -0
  42. data/doc/anti-amnesia/20250729-190000-scriptorium-tui-testing-complete.md +46 -0
  43. data/doc/anti-amnesia/20250729-200000-scriptorium-tui-testing-edit-file-workflow.md +97 -0
  44. data/doc/anti-amnesia/20250729-210000-reddit-autopost-integration-complete.md +158 -0
  45. data/doc/anti-amnesia/20250729-211500-dependency-management-system.md +211 -0
  46. data/doc/anti-amnesia/20250729-213000-python-virtual-environment-setup.md +141 -0
  47. data/doc/anti-amnesia/20250729-214500-theme-management-commands.md +211 -0
  48. data/doc/anti-amnesia/20250729-215000-version-update-to-0.6.0.md +134 -0
  49. data/doc/anti-amnesia/20250729-220000-user-guide-complete.md +41 -0
  50. data/doc/anti-amnesia/20250804-190500-cognitive-loop-bug.md +45 -0
  51. data/doc/anti-amnesia/20250804-190700-anti-amnesia-timestamping-fix.md +30 -0
  52. data/doc/anti-amnesia/20250804-213700-publishing-test-fix.md +49 -0
  53. data/doc/anti-amnesia/20250804-214400-additional-test-fixes.md +46 -0
  54. data/doc/anti-amnesia/20250804-220000-asset-function-logic-clarification.md +41 -0
  55. data/doc/anti-amnesia/20250806-202032-asset-function-logic-clarification.md +41 -0
  56. data/doc/anti-amnesia/20250807-213025.md +116 -0
  57. data/doc/anti-amnesia/20250813-082428-syntax-highlighting-and-navigation-improvements.md +256 -0
  58. data/doc/banner_svg_config.md +114 -0
  59. data/doc/contrib.lt3 +8 -0
  60. data/doc/dependencies.md +281 -0
  61. data/doc/hacker.lt3 +5 -0
  62. data/doc/reddit_credentials_template.json +8 -0
  63. data/doc/reddit_integration.md +207 -0
  64. data/doc/user.lt3 +38 -0
  65. data/doc/user_guide_section_1.md +137 -0
  66. data/doc/user_guide_section_10.md +515 -0
  67. data/doc/user_guide_section_11.md +708 -0
  68. data/doc/user_guide_section_2.md +233 -0
  69. data/doc/user_guide_section_3.md +5 -0
  70. data/doc/user_guide_section_4.md +221 -0
  71. data/doc/user_guide_section_5.md +243 -0
  72. data/doc/user_guide_section_6.md +147 -0
  73. data/doc/user_guide_section_7.md +311 -0
  74. data/doc/user_guide_section_8.md +224 -0
  75. data/doc/user_guide_section_9.md +375 -0
  76. data/doc/userdoc-toc.txt +88 -0
  77. data/lib/rouge/lexers/livetext.rb +74 -0
  78. data/lib/scriptorium/api.rb +640 -0
  79. data/lib/scriptorium/banner_svg.rb +742 -0
  80. data/lib/scriptorium/contract.rb +33 -0
  81. data/lib/scriptorium/exceptions.rb +170 -1
  82. data/lib/scriptorium/helpers.rb +475 -0
  83. data/lib/scriptorium/post.rb +195 -0
  84. data/lib/scriptorium/reddit.rb +83 -0
  85. data/lib/scriptorium/repo.rb +624 -0
  86. data/lib/scriptorium/standard_files.rb +515 -0
  87. data/lib/scriptorium/syntax_highlighter.rb +234 -0
  88. data/lib/scriptorium/theme.rb +179 -0
  89. data/lib/scriptorium/version.rb +2 -2
  90. data/lib/scriptorium/view.rb +976 -0
  91. data/lib/scriptorium/widgets/featured_posts.rb +149 -0
  92. data/lib/scriptorium/widgets/links.rb +112 -0
  93. data/lib/scriptorium/widgets/pages.rb +133 -0
  94. data/lib/scriptorium/widgets/widget.rb +133 -0
  95. data/lib/scriptorium.rb +21 -40
  96. data/lib/skeleton.rb +8 -2
  97. data/scriptorium.gemspec +15 -4
  98. data/test/README.md +69 -0
  99. data/test/all +43 -0
  100. data/test/api_demo.rb +99 -0
  101. data/test/assets/imagenotfound.jpg +0 -0
  102. data/test/assets/images/.DS_Store +0 -0
  103. data/test/assets/images/README.md +27 -0
  104. data/test/assets/images/odd_aspect.png +0 -0
  105. data/test/assets/images/perfect.png +0 -0
  106. data/test/assets/images/small.png +0 -0
  107. data/test/assets/images/tall.png +0 -0
  108. data/test/assets/images/very_tall.png +0 -0
  109. data/test/assets/images/very_wide.png +0 -0
  110. data/test/assets/images/wide.png +0 -0
  111. data/test/assets/testbanner.jpg +0 -0
  112. data/test/banner_svg/simple_helpers.rb +13 -0
  113. data/test/banner_svg/unit.rb +768 -0
  114. data/test/ed_test.rb +204 -0
  115. data/test/integration/cursor_banner_combinations.rb +193 -0
  116. data/test/integration/cursor_banner_features.rb +374 -0
  117. data/test/integration/integration_test.rb +326 -0
  118. data/test/livetext_plugin_test.rb +229 -0
  119. data/test/manual/asset_mgmt.rb +67 -0
  120. data/test/manual/banner-tests/config.txt +3 -0
  121. data/test/manual/banner-tests/index.html +45 -0
  122. data/test/manual/banner-tests/test01.html +58 -0
  123. data/test/manual/banner-tests/test02.html +58 -0
  124. data/test/manual/banner-tests/test03.html +58 -0
  125. data/test/manual/banner-tests/test04.html +65 -0
  126. data/test/manual/banner-tests/test05.html +65 -0
  127. data/test/manual/banner-tests/test06.html +65 -0
  128. data/test/manual/banner-tests/test07.html +65 -0
  129. data/test/manual/banner-tests/test08.html +59 -0
  130. data/test/manual/banner-tests/test09.html +59 -0
  131. data/test/manual/banner-tests/test10.html +59 -0
  132. data/test/manual/banner-tests/test11.html +59 -0
  133. data/test/manual/banner-tests/test12.html +59 -0
  134. data/test/manual/banner-tests/test13.html +59 -0
  135. data/test/manual/banner-tests/test14.html +59 -0
  136. data/test/manual/banner-tests/test15.html +58 -0
  137. data/test/manual/banner-tests/test16.html +58 -0
  138. data/test/manual/banner-tests/test17.html +58 -0
  139. data/test/manual/banner-tests/test18.html +68 -0
  140. data/test/manual/banner-tests/test19.html +68 -0
  141. data/test/manual/banner-tests/test20.html +68 -0
  142. data/test/manual/banner-tests/test21.html +68 -0
  143. data/test/manual/banner-tests/test22.html +68 -0
  144. data/test/manual/banner-tests/test23.html +68 -0
  145. data/test/manual/banner-tests/test24.html +68 -0
  146. data/test/manual/banner-tests/test25.html +67 -0
  147. data/test/manual/banner_environment.rb +192 -0
  148. data/test/manual/deploy_symlink_demo.rb +142 -0
  149. data/test/manual/environment.rb +67 -0
  150. data/test/manual/make_banner.rb +153 -0
  151. data/test/manual/sample_banner_config.txt +12 -0
  152. data/test/manual/symlink_demo.rb +117 -0
  153. data/test/manual/test1.rb +47 -0
  154. data/test/manual/test2.rb +12 -0
  155. data/test/manual/test3.rb +38 -0
  156. data/test/manual/test4.rb +40 -0
  157. data/test/manual/test5.rb +24 -0
  158. data/test/manual/test6.rb +73 -0
  159. data/test/manual/test_banner_combinations.rb +120 -0
  160. data/test/manual/test_banner_features.rb +306 -0
  161. data/test/manual/test_banner_from_file.rb +150 -0
  162. data/test/manual/test_banner_in_header.rb +35 -0
  163. data/test/manual/test_code_highlighting.rb +68 -0
  164. data/test/manual/test_complex_header.rb +74 -0
  165. data/test/manual/test_empty_header.rb +32 -0
  166. data/test/manual/test_radial_custom.rb +58 -0
  167. data/test/manual/test_radial_large_radius.rb +52 -0
  168. data/test/manual/test_svg_debug.rb +47 -0
  169. data/test/manual/test_syntax_highlighting.rb +147 -0
  170. data/test/pages-demo/config/currentview.txt +1 -0
  171. data/test/pages-demo/views/demo/config/bootstrap_css.txt +5 -0
  172. data/test/pages-demo/views/demo/config/bootstrap_js.txt +4 -0
  173. data/test/pages-demo/views/demo/config/common.js +57 -0
  174. data/test/pages-demo/views/demo/config/footer.txt +1 -0
  175. data/test/pages-demo/views/demo/config/global-head.txt +8 -0
  176. data/test/pages-demo/views/demo/config/header.txt +1 -0
  177. data/test/pages-demo/views/demo/config/layout.txt +1 -0
  178. data/test/pages-demo/views/demo/config/left.txt +1 -0
  179. data/test/pages-demo/views/demo/config/main.txt +1 -0
  180. data/test/pages-demo/views/demo/config/right.txt +1 -0
  181. data/test/pages-demo/views/demo/config.txt +3 -0
  182. data/test/pages-demo/views/demo/output/panes/footer.html +1 -0
  183. data/test/pages-demo/views/demo/output/panes/header.html +1 -0
  184. data/test/pages-demo/views/demo/output/panes/left.html +1 -0
  185. data/test/pages-demo/views/demo/output/panes/main.html +1 -0
  186. data/test/pages-demo/views/demo/output/panes/right.html +1 -0
  187. data/test/rubytext/rubytext_comprehensive_test.rb +307 -0
  188. data/test/rubytext/rubytext_demo_test.rb +42 -0
  189. data/test/rubytext/rubytext_testing_guide.md +277 -0
  190. data/test/run_automated_tests.rb +45 -0
  191. data/test/scriptorium-TEST-1754622690-146/config/bootstrap_css.txt +5 -0
  192. data/test/scriptorium-TEST-1754622690-146/config/bootstrap_js.txt +4 -0
  193. data/test/scriptorium-TEST-1754622690-146/config/common.js +57 -0
  194. data/test/scriptorium-TEST-1754622690-146/config/currentview.txt +1 -0
  195. data/test/scriptorium-TEST-1754622690-146/config/global-head.txt +9 -0
  196. data/test/scriptorium-TEST-1754622690-146/config/last_post_num.txt +1 -0
  197. data/test/scriptorium-TEST-1754622690-146/config/os_helpers.rb +4 -0
  198. data/test/scriptorium-TEST-1754622690-146/config/widgets.txt +3 -0
  199. data/test/scriptorium-TEST-1754622690-146/posts/0001/meta.txt +8 -0
  200. data/test/scriptorium-TEST-1754622690-146/posts/0001/source.lt3 +6 -0
  201. data/test/scriptorium-TEST-1754622690-146/themes/standard/README.txt +1 -0
  202. data/test/scriptorium-TEST-1754622690-146/themes/standard/config.txt +1 -0
  203. data/test/scriptorium-TEST-1754622690-146/themes/standard/initial/post.lt3 +12 -0
  204. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/footer.txt +2 -0
  205. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/header.txt +4 -0
  206. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/left.txt +3 -0
  207. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/main.txt +5 -0
  208. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/right.txt +3 -0
  209. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/gen/text.css +1 -0
  210. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/layout.txt +5 -0
  211. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index.lt3 +1 -0
  212. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index_entry.lt3 +14 -0
  213. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/post.lt3 +13 -0
  214. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/widget.lt3 +1 -0
  215. data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_css.txt +5 -0
  216. data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_js.txt +4 -0
  217. data/test/scriptorium-TEST-1754622690-146/views/sample/config/common.js +57 -0
  218. data/test/scriptorium-TEST-1754622690-146/views/sample/config/deploy.txt +5 -0
  219. data/test/scriptorium-TEST-1754622690-146/views/sample/config/footer.txt +2 -0
  220. data/test/scriptorium-TEST-1754622690-146/views/sample/config/global-head.txt +9 -0
  221. data/test/scriptorium-TEST-1754622690-146/views/sample/config/header.txt +4 -0
  222. data/test/scriptorium-TEST-1754622690-146/views/sample/config/layout.txt +5 -0
  223. data/test/scriptorium-TEST-1754622690-146/views/sample/config/left.txt +3 -0
  224. data/test/scriptorium-TEST-1754622690-146/views/sample/config/main.txt +5 -0
  225. data/test/scriptorium-TEST-1754622690-146/views/sample/config/reddit.txt +10 -0
  226. data/test/scriptorium-TEST-1754622690-146/views/sample/config/right.txt +3 -0
  227. data/test/scriptorium-TEST-1754622690-146/views/sample/config/social.txt +7 -0
  228. data/test/scriptorium-TEST-1754622690-146/views/sample/config/status.txt +7 -0
  229. data/test/scriptorium-TEST-1754622690-146/views/sample/config.txt +3 -0
  230. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/footer.html +3 -0
  231. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/header.html +3 -0
  232. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/left.html +3 -0
  233. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/main.html +3 -0
  234. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/right.html +3 -0
  235. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/footer.html +1 -0
  236. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/header.html +1 -0
  237. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/left.html +1 -0
  238. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/main.html +1 -0
  239. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/right.html +1 -0
  240. data/test/staging/.DS_Store +0 -0
  241. data/test/syntax_highlighting_test.lt3 +124 -0
  242. data/test/test_helpers.rb +230 -0
  243. data/test/tui_editor_integration_test.rb +296 -0
  244. data/test/tui_integration_test.rb +637 -0
  245. data/test/unit/api.rb +1056 -0
  246. data/test/unit/asset_management.rb +245 -0
  247. data/test/unit/clipboard_test.rb +60 -0
  248. data/test/unit/contract_test.rb +91 -0
  249. data/test/unit/core.rb +857 -0
  250. data/test/unit/deploy_test.rb +187 -0
  251. data/test/unit/gem_asset_management.rb +189 -0
  252. data/test/unit/livetext_basic.rb +69 -0
  253. data/test/unit/livetext_compatibility.rb +89 -0
  254. data/test/unit/post.rb +244 -0
  255. data/test/unit/read_commented_file_test.rb +276 -0
  256. data/test/unit/reddit_test.rb +235 -0
  257. data/test/unit/repo.rb +548 -0
  258. data/test/unit/social_test.rb +369 -0
  259. data/test/unit/symlink_test.rb +213 -0
  260. data/test/unit/view.rb +431 -0
  261. data/test/unit/widgets.rb +669 -0
  262. data/test/wizard_test.rb +123 -0
  263. data/ui/README.md +67 -0
  264. data/ui/common/lib/ui_common.rb +8 -0
  265. data/ui/rubytext/README.md +191 -0
  266. data/ui/rubytext/bin/scriptorium-rubytext +402 -0
  267. data/ui/rubytext/lib/rubytext_ui.rb +300 -0
  268. data/ui/tui/bin/scriptorium +1420 -0
  269. data/ui/tui/test/tui_test.rb +23 -0
  270. data/ui/web/app/app.rb +1378 -0
  271. data/ui/web/app/error_helpers.rb +150 -0
  272. data/ui/web/app/views/advanced_config.erb +190 -0
  273. data/ui/web/app/views/asset_management.erb +589 -0
  274. data/ui/web/app/views/banner_config.erb +200 -0
  275. data/ui/web/app/views/configure_view.erb +401 -0
  276. data/ui/web/app/views/dashboard.erb +162 -0
  277. data/ui/web/app/views/deploy_config.erb +146 -0
  278. data/ui/web/app/views/edit_pages.erb +195 -0
  279. data/ui/web/app/views/edit_post.erb +54 -0
  280. data/ui/web/app/views/error_page.erb +29 -0
  281. data/ui/web/app/views/header_config.erb +155 -0
  282. data/ui/web/app/views/layout_config.erb +147 -0
  283. data/ui/web/app/views/navbar_config.erb +411 -0
  284. data/ui/web/app/views/view_dashboard.erb +138 -0
  285. data/ui/web/bin/scriptorium-web +153 -0
  286. data/ui/web/test/web_basic_test.rb +38 -0
  287. data/ui/web/test_navbar.txt +7 -0
  288. data/ui/web/tmp/web_server.log +5 -0
  289. data/ui/web/tmp/web_server.pid +1 -0
  290. metadata +359 -7
  291. data/lib/scriptorium/engine.rb +0 -22
  292. data/test/engine/unit.rb +0 -44
data/test/unit/repo.rb ADDED
@@ -0,0 +1,548 @@
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_005_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_006_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_007_new_view_becomes_current
101
+ repo = create_test_repo
102
+ tv2 = "testview2"
103
+ view = repo.create_view(tv2, "My 2nd Title", "Just another subtitle here")
104
+ assert repo.views.size == 2, "Expected 2 views, not #{repo.views.size}"
105
+ vnames = repo.views.map {|v| v.name }
106
+ assert vnames.include?(tv2), "Expected to find '#{tv2}' in views"
107
+ assert repo.current_view.name == view.name, "Expected '#{tv2}' as current view"
108
+ end
109
+
110
+ def test_008_open_view
111
+ repo = create_test_repo
112
+ vname = "testview"
113
+ title, sub = "My Awesome Title", "Just another subtitle"
114
+ repo.create_view(vname, title, sub)
115
+ t0 = repo.view_exist?(vname)
116
+ assert t0, "View should exist"
117
+
118
+ view = repo.open_view(vname)
119
+ assert view.title == title, "View title missing"
120
+ assert view.subtitle == sub, "View subtitle missing"
121
+ assert view.theme == "standard", "Expected standard theme, found '#{view.theme}'"
122
+ assert repo.current_view.name == vname, "Expected '#{vname}' in views"
123
+ end
124
+
125
+ def test_009_create_draft
126
+ repo = create_test_repo
127
+ fname = repo.create_draft
128
+ assert_file_exist?(fname)
129
+ assert_file_contains?(fname, ".title ADD TITLE HERE")
130
+ assert_file_contains?(fname, ".views BLOG1 BLOG2 BLOG3")
131
+ assert_file_contains?(fname, ".tags sample, tags")
132
+
133
+ f2 = repo.create_draft(title: "Draft post", tags: %w[things stuff])
134
+ assert_file_exist?(f2)
135
+ assert_file_contains?(f2, ".title Draft post")
136
+ assert_file_contains?(f2, ".views BLOG1 BLOG2 BLOG3")
137
+ assert_file_contains?(f2, ".tags things, stuff")
138
+ end
139
+
140
+ def test_010_finish_draft
141
+ $debug = true
142
+ repo = create_test_repo
143
+ fname = repo.create_draft
144
+
145
+ repo.finish_draft(fname)
146
+ postnum = "0001" # Assumes testing started with 0
147
+ postdir = repo.root/:posts/postnum
148
+ assert_dir_exist?(postdir/:assets)
149
+ assert_file_exist?(postdir/"source.lt3")
150
+ $debug = false
151
+ end
152
+
153
+ def test_011_check_initial_post
154
+ repo = create_test_repo
155
+ root = repo.root
156
+ file = "#{root}/themes/standard/initial/post.lt3" # FIXME hardcoded
157
+ assert_file_exist?(file)
158
+ assert_file_lines(file, 12)
159
+ end
160
+
161
+ def test_012_check_interpolated_initial_post
162
+ repo = create_test_repo
163
+ predef = repo.instance_eval { @predef }
164
+ str = predef.initial_post
165
+ lines = str.split("\n")
166
+ assert lines[2] == ".id 0000", "Expected '.id 0000' with unspecified num"
167
+ str2 = predef.initial_post(num: 237)
168
+ lines = str2.split("\n")
169
+ assert lines[2] == ".id 0237", "Expected 'num' to be filled in (found '#{lines[2]}')"
170
+ end
171
+
172
+ def test_013_find_theme_file
173
+ repo = create_test_repo
174
+ t = Scriptorium::Theme.new(repo.root, "standard")
175
+
176
+ path1 = t.file("initial/post.lt3")
177
+ want1 = "./test/scriptorium-TEST/themes/standard/initial/post.lt3"
178
+ assert path1 == want1, "Expected: #{want1}"
179
+ path2 = t.file("right.txt")
180
+ want2 = "./test/scriptorium-TEST/themes/standard/layout/config/right.txt"
181
+ assert path2 == want2, "Expected: #{want2}"
182
+
183
+ assert_raises(MoreThanOneResult) { t.file("post.lt3") }
184
+ end
185
+
186
+ def test_014_check_post_template
187
+ repo = create_test_repo
188
+ root = repo.root
189
+ file = "#{root}/themes/standard/templates/post.lt3" # FIXME hardcoded
190
+ assert_file_exist?(file)
191
+ assert_file_lines(file, 16) # Template now includes copy link button but no inline JavaScript
192
+ end
193
+
194
+ def test_015_change_view
195
+ repo = create_test_repo
196
+ root = repo.root
197
+
198
+ v1 = repo.view # initially 'sample'
199
+ assert v1.name == 'sample', "Expected view to be 'sample'"
200
+
201
+ vname = "testing"
202
+ t0 = repo.view_exist?(vname)
203
+ refute t0, "View should not exist yet"
204
+
205
+ repo.create_view(vname, "My Title", "Nothing here") # testing
206
+ v2 = repo.view
207
+ assert v2.name == 'testing', "Expected view to be 'sample'"
208
+
209
+ v3 = repo.view('sample')
210
+ assert v3.name == 'sample', "Expected view to be 'sample'"
211
+
212
+ v4 = repo.view('testing')
213
+ assert v4.name == 'testing', "Expected view to be 'sample'"
214
+ end
215
+
216
+ def test_016_lookup_view
217
+ repo = create_test_repo
218
+ root = repo.root
219
+ v0 = repo.lookup_view('sample')
220
+ assert v0.name == 'sample', "Expected view to be 'sample'"
221
+ assert_raises(CannotLookupView) { repo.lookup_view('view99')}
222
+ repo.create_view("newview", "My Title", "Nothing here") # testing
223
+ v2 = repo.view
224
+ v3 = repo.lookup_view(v2.name)
225
+ assert v3.name == v2.name, "Expected new view '#{v2.name}' found as '#{v3.name}"
226
+ end
227
+
228
+ def test_017_tree_method
229
+ repo = create_test_repo
230
+ repo.tree("/tmp/test-tree.txt")
231
+ assert_file_exist?("/tmp/tree.txt")
232
+ num = File.readlines("/tmp/tree.txt").size
233
+ assert num > 0, "Tree file appears too short"
234
+ end
235
+
236
+
237
+
238
+ def test_019_mock_vars_into_template
239
+ title = "This is my title"
240
+ pubdate = "August 2, 2024"
241
+ tags = "history, journal, birthday"
242
+ body =
243
+ <<~EOS
244
+ This is just a fake blog post.
245
+
246
+ <p>
247
+ If it had been an <i>actual</i> post, it
248
+ might have said something.
249
+
250
+ <p>
251
+ That's all.
252
+ EOS
253
+ vars = {:"post.title" => title, :"post.pubdate" => pubdate,
254
+ :"post.tags" => tags, :"post.body" => body, :"reddit_button" => ""}
255
+ predef = Scriptorium::StandardFiles.new
256
+ template = predef.post_template("standard")
257
+ result = template % vars
258
+ assert result =~ /August 2, 2024/
259
+ File.open("/tmp/mock.html", "w") do |f|
260
+ f.puts result
261
+ end
262
+ assert_file_lines("/tmp/mock.html", 24) # Template + expanded body content (newlines in body create extra lines)
263
+ end
264
+
265
+
266
+
267
+ def test_022_simple_generate_post
268
+ repo = create_test_repo
269
+ dname = repo.create_draft(title: "My first post", tags: %w[things stuff])
270
+ body =
271
+ <<~EOS
272
+ This is just another fake blog post.
273
+
274
+ <p>
275
+ If it had been an _actual post, it might have
276
+ said something meaninful.
277
+
278
+ <p>
279
+ But here we are.
280
+ EOS
281
+ text = File.read(dname)
282
+ text.sub!(/BEGIN HERE.../, body)
283
+ write_file(dname, text)
284
+ num = repo.finish_draft(dname)
285
+ repo.generate_post(num)
286
+ repo.tree("/tmp/tree.txt")
287
+ assert_file_exist?(repo.root/:posts/d4(num)/"body.html")
288
+ assert_file_exist?(repo.root/:posts/d4(num)/"meta.txt")
289
+ assert_file_exist?(repo.root/:views/:sample/:output/:posts/"#{d4(num)}-my-first-post.html")
290
+ end
291
+
292
+
293
+
294
+
295
+
296
+ def test_025_layout_file_missing
297
+ repo = create_test_repo
298
+ view = repo.create_view("testview", "Test View", "Test Subtitle")
299
+
300
+ # Remove the layout.txt file to test the exception
301
+ layout_file = view.dir/:config/"layout.txt"
302
+ File.delete(layout_file) if File.exist?(layout_file)
303
+
304
+ assert_raises(LayoutFileMissing) do
305
+ view.read_layout
306
+ end
307
+ end
308
+
309
+ def test_026_verify_permalink_generation
310
+ repo = create_test_repo
311
+ dname = repo.create_draft(title: "Permalink Test Post", tags: %w[test permalink])
312
+ body = <<~EOS
313
+ This is a test post for permalink functionality.
314
+
315
+ <p>
316
+ It should be generated in both the posts/ and permalink/ directories.
317
+ EOS
318
+ text = File.read(dname)
319
+ text.sub!(/BEGIN HERE.../, body)
320
+ write_file(dname, text)
321
+ num = repo.finish_draft(dname)
322
+ repo.generate_post(num)
323
+
324
+ # Check that post was generated in both locations
325
+ # Use the same pattern as the working test
326
+ regular_post = repo.root/:views/:sample/:output/:posts/"#{d4(num)}-permalink-test-post.html"
327
+ assert File.exist?(regular_post), "Regular post should exist at #{regular_post}"
328
+
329
+ # Permalink location
330
+ permalink_post = repo.root/:views/:sample/:output/:permalink/"#{d4(num)}-permalink-test-post.html"
331
+ assert File.exist?(permalink_post), "Permalink post should exist at #{permalink_post}"
332
+
333
+ # Both files should have different content (permalink has "Visit Blog" link)
334
+ regular_content = File.read(regular_post)
335
+ permalink_content = File.read(permalink_post)
336
+ refute_equal regular_content, permalink_content, "Post content should differ (permalink has 'Visit Blog' link)"
337
+
338
+ # Regular post should NOT contain the "Visit Blog" link
339
+ refute_includes regular_content, "Visit Blog"
340
+ refute_includes regular_content, 'href="../index.html"'
341
+
342
+ # Permalink post should contain the "Visit Blog" link
343
+ assert_includes permalink_content, "Visit Blog"
344
+ assert_includes permalink_content, 'href="../index.html"'
345
+
346
+ # Permalink post should contain the "Copy link" button
347
+ assert_includes permalink_content, "Copy link"
348
+ end
349
+
350
+ # View-related tests
351
+ def test_view_method_writes_to_currentview_txt
352
+ # Create a test repo for this test
353
+ test_repo_path = "test_repo_view_test"
354
+ FileUtils.rm_rf(test_repo_path) if Dir.exist?(test_repo_path)
355
+
356
+ repo = Scriptorium::Repo.create(test_repo_path, testmode: true)
357
+ repo.create_view("view1", "View One", "First view")
358
+ repo.create_view("view2", "View Two", "Second view")
359
+
360
+ # Initially, current view should be the last created view (view2)
361
+ assert_equal "view2", repo.current_view.name
362
+
363
+ # Switch to view1
364
+ repo.view("view1")
365
+
366
+ # Check that current view changed in memory
367
+ assert_equal "view1", repo.current_view.name
368
+
369
+ # Check that currentview.txt was written
370
+ currentview_file = File.join(test_repo_path, "config", "currentview.txt")
371
+ assert File.exist?(currentview_file), "currentview.txt should exist"
372
+
373
+ content = File.read(currentview_file).strip
374
+ assert_equal "view1", content, "currentview.txt should contain 'view1'"
375
+
376
+ # Cleanup
377
+ FileUtils.rm_rf(test_repo_path)
378
+ end
379
+
380
+ def test_view_method_persists_across_repo_reload
381
+ # Create a test repo for this test
382
+ test_repo_path = "test_repo_view_test"
383
+ FileUtils.rm_rf(test_repo_path) if Dir.exist?(test_repo_path)
384
+
385
+ repo = Scriptorium::Repo.create(test_repo_path, testmode: true)
386
+ repo.create_view("view1", "View One", "First view")
387
+ repo.create_view("view2", "View Two", "Second view")
388
+
389
+ # Switch to view1
390
+ repo.view("view1")
391
+ assert_equal "view1", repo.current_view.name
392
+
393
+ # Create a new repo instance pointing to the same directory
394
+ new_repo = Scriptorium::Repo.open(test_repo_path)
395
+
396
+ # The new repo should load the current view from the file
397
+ assert_equal "view1", new_repo.current_view.name
398
+
399
+ # Cleanup
400
+ FileUtils.rm_rf(test_repo_path)
401
+ end
402
+
403
+ def test_create_view_switches_to_new_view
404
+ # Create a test repo for this test
405
+ test_repo_path = "test_repo_view_test"
406
+ FileUtils.rm_rf(test_repo_path) if Dir.exist?(test_repo_path)
407
+
408
+ repo = Scriptorium::Repo.create(test_repo_path, testmode: true)
409
+ repo.create_view("view1", "View One", "First view")
410
+ repo.create_view("view2", "View Two", "Second view")
411
+
412
+ # Start with view2 as current
413
+ assert_equal "view2", repo.current_view.name
414
+
415
+ # Create a new view - should switch to it
416
+ repo.create_view("view3", "View Three", "Third view")
417
+ assert_equal "view3", repo.current_view.name
418
+
419
+ # Check that currentview.txt was updated
420
+ currentview_file = File.join(test_repo_path, "config", "currentview.txt")
421
+ content = File.read(currentview_file).strip
422
+ assert_equal "view3", content, "currentview.txt should contain 'view3'"
423
+
424
+ # Cleanup
425
+ FileUtils.rm_rf(test_repo_path)
426
+ end
427
+
428
+ def test_load_views_reads_currentview_txt
429
+ # Create a test repo for this test
430
+ test_repo_path = "test_repo_view_test"
431
+ FileUtils.rm_rf(test_repo_path) if Dir.exist?(test_repo_path)
432
+
433
+ repo = Scriptorium::Repo.create(test_repo_path, testmode: true)
434
+ repo.create_view("view1", "View One", "First view")
435
+ repo.create_view("view2", "View Two", "Second view")
436
+
437
+ # Switch to view1
438
+ repo.view("view1")
439
+ assert_equal "view1", repo.current_view.name
440
+
441
+ # Create a new repo instance to test load_views
442
+ new_repo = Scriptorium::Repo.open(test_repo_path)
443
+
444
+ # Should load the correct current view from file
445
+ assert_equal "view1", new_repo.current_view.name
446
+
447
+ # Cleanup
448
+ FileUtils.rm_rf(test_repo_path)
449
+ end
450
+
451
+ def test_load_views_handles_missing_currentview_txt
452
+ # Create a test repo for this test
453
+ test_repo_path = "test_repo_view_test"
454
+ FileUtils.rm_rf(test_repo_path) if Dir.exist?(test_repo_path)
455
+
456
+ repo = Scriptorium::Repo.create(test_repo_path, testmode: true)
457
+ repo.create_view("view1", "View One", "First view")
458
+
459
+ # Remove currentview.txt
460
+ currentview_file = File.join(test_repo_path, "config", "currentview.txt")
461
+ File.delete(currentview_file) if File.exist?(currentview_file)
462
+
463
+ # Create a new repo instance
464
+ new_repo = Scriptorium::Repo.open(test_repo_path)
465
+
466
+ # Should handle missing file gracefully
467
+ # The behavior depends on how the repo handles nil current_view
468
+ assert new_repo.current_view.nil? || new_repo.current_view.is_a?(Scriptorium::View)
469
+
470
+ # Cleanup
471
+ FileUtils.rm_rf(test_repo_path)
472
+ end
473
+
474
+ def test_load_views_handles_invalid_view_name
475
+ # Create a test repo for this test
476
+ test_repo_path = "test_repo_view_test"
477
+ FileUtils.rm_rf(test_repo_path) if Dir.exist?(test_repo_path)
478
+
479
+ repo = Scriptorium::Repo.create(test_repo_path, testmode: true)
480
+ repo.create_view("view1", "View One", "First view")
481
+
482
+ # Write an invalid view name to currentview.txt
483
+ currentview_file = File.join(test_repo_path, "config", "currentview.txt")
484
+ File.write(currentview_file, "nonexistent_view")
485
+
486
+ # Create a new repo instance - should handle invalid view name gracefully
487
+ # The current implementation raises an exception, so we expect that
488
+ assert_raises(CannotLookupView) do
489
+ Scriptorium::Repo.open(test_repo_path)
490
+ end
491
+
492
+ # Cleanup
493
+ FileUtils.rm_rf(test_repo_path)
494
+ end
495
+
496
+ def test_view_method_returns_view_object
497
+ # Create a test repo for this test
498
+ test_repo_path = "test_repo_view_test"
499
+ FileUtils.rm_rf(test_repo_path) if Dir.exist?(test_repo_path)
500
+
501
+ repo = Scriptorium::Repo.create(test_repo_path, testmode: true)
502
+ repo.create_view("view1", "View One", "First view")
503
+ repo.create_view("view2", "View Two", "Second view")
504
+
505
+ # The view method should return the view object, not nil
506
+ result = repo.view("view1")
507
+ assert_instance_of Scriptorium::View, result
508
+ assert_equal "view1", result.name
509
+
510
+ # Cleanup
511
+ FileUtils.rm_rf(test_repo_path)
512
+ end
513
+
514
+ def test_view_method_with_nil_returns_current_view
515
+ # Create a test repo for this test
516
+ test_repo_path = "test_repo_view_test"
517
+ FileUtils.rm_rf(test_repo_path) if Dir.exist?(test_repo_path)
518
+
519
+ repo = Scriptorium::Repo.create(test_repo_path, testmode: true)
520
+ repo.create_view("view1", "View One", "First view")
521
+
522
+ # view() with no arguments should return current view
523
+ current = repo.current_view
524
+ result = repo.view
525
+ assert_equal current, result
526
+
527
+ # Cleanup
528
+ FileUtils.rm_rf(test_repo_path)
529
+ end
530
+
531
+ def test_view_method_with_view_object
532
+ # Create a test repo for this test
533
+ test_repo_path = "test_repo_view_test"
534
+ FileUtils.rm_rf(test_repo_path) if Dir.exist?(test_repo_path)
535
+
536
+ repo = Scriptorium::Repo.create(test_repo_path, testmode: true)
537
+ repo.create_view("view1", "View One", "First view")
538
+ repo.create_view("view2", "View Two", "Second view")
539
+
540
+ # Should work with a View object instead of string
541
+ view_obj = repo.lookup_view("view1")
542
+ repo.view(view_obj)
543
+ assert_equal "view1", repo.current_view.name
544
+
545
+ # Cleanup
546
+ FileUtils.rm_rf(test_repo_path)
547
+ end
548
+ end