scriptorium 0.0.2 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. checksums.yaml +4 -4
  2. data/README.lt3 +324 -0
  3. data/README.md +3155 -1
  4. data/assets/.DS_Store +0 -0
  5. data/assets/README.md +44 -0
  6. data/assets/back-icon.png +0 -0
  7. data/assets/icons/facebook.svg +1 -0
  8. data/assets/icons/github.svg +1 -0
  9. data/assets/icons/instagram.svg +1 -0
  10. data/assets/icons/reddit.svg +1 -0
  11. data/assets/icons/ui/.DS_Store +0 -0
  12. data/assets/icons/ui/back.png +0 -0
  13. data/assets/icons/ui/copy.png +0 -0
  14. data/assets/icons/ui/down.png +0 -0
  15. data/assets/icons/ui/end.png +0 -0
  16. data/assets/icons/ui/exit.png +0 -0
  17. data/assets/icons/ui/foo +10 -0
  18. data/assets/icons/ui/home.png +0 -0
  19. data/assets/icons/ui/left.png +0 -0
  20. data/assets/icons/ui/next.png +0 -0
  21. data/assets/icons/ui/right.png +0 -0
  22. data/assets/icons/ui/start.png +0 -0
  23. data/assets/icons/ui/up.png +0 -0
  24. data/assets/icons/x.svg +1 -0
  25. data/assets/icons/youtube.svg +1 -0
  26. data/assets/samples/placeholder.svg +9 -0
  27. data/assets/themes/standard/favicon.svg +6 -0
  28. data/bin/scriptorium +1511 -0
  29. data/doc/README.txt +6 -0
  30. data/doc/anti-amnesia/20250727-054000-scriptorium-overview.md +95 -0
  31. data/doc/anti-amnesia/20250727-060000-api-design-tui-planning.md +34 -0
  32. data/doc/anti-amnesia/20250727-061000-runeblog-tui-analysis.md +50 -0
  33. data/doc/anti-amnesia/20250727-123000-anti-amnesia-conventions.md +31 -0
  34. data/doc/anti-amnesia/20250727-154000-livetext-plugin-file-stats.md +73 -0
  35. data/doc/anti-amnesia/20250727-172600-cursor-rbenv-ruby-version-mystery.md +64 -0
  36. data/doc/anti-amnesia/20250727-172600-unified-minitest-framework.md +70 -0
  37. data/doc/anti-amnesia/20250727-172900-ai-cognitive-assessment-capabilities.md +40 -0
  38. data/doc/anti-amnesia/20250727-173000-widget-testing-achievement.md +110 -0
  39. data/doc/anti-amnesia/20250727-180000-post-id-num-refactoring.md +73 -0
  40. data/doc/anti-amnesia/20250728-124243-aaa-syntax-clarification.md +46 -0
  41. data/doc/anti-amnesia/20250728-124421-conversation-summary-concise.md +124 -0
  42. data/doc/anti-amnesia/20250729-190000-scriptorium-tui-testing-complete.md +46 -0
  43. data/doc/anti-amnesia/20250729-200000-scriptorium-tui-testing-edit-file-workflow.md +97 -0
  44. data/doc/anti-amnesia/20250729-210000-reddit-autopost-integration-complete.md +158 -0
  45. data/doc/anti-amnesia/20250729-211500-dependency-management-system.md +211 -0
  46. data/doc/anti-amnesia/20250729-213000-python-virtual-environment-setup.md +141 -0
  47. data/doc/anti-amnesia/20250729-214500-theme-management-commands.md +211 -0
  48. data/doc/anti-amnesia/20250729-215000-version-update-to-0.6.0.md +134 -0
  49. data/doc/anti-amnesia/20250729-220000-user-guide-complete.md +41 -0
  50. data/doc/anti-amnesia/20250804-190500-cognitive-loop-bug.md +45 -0
  51. data/doc/anti-amnesia/20250804-190700-anti-amnesia-timestamping-fix.md +30 -0
  52. data/doc/anti-amnesia/20250804-213700-publishing-test-fix.md +49 -0
  53. data/doc/anti-amnesia/20250804-214400-additional-test-fixes.md +46 -0
  54. data/doc/anti-amnesia/20250804-220000-asset-function-logic-clarification.md +41 -0
  55. data/doc/anti-amnesia/20250806-202032-asset-function-logic-clarification.md +41 -0
  56. data/doc/anti-amnesia/20250807-213025.md +116 -0
  57. data/doc/anti-amnesia/20250813-082428-syntax-highlighting-and-navigation-improvements.md +256 -0
  58. data/doc/banner_svg_config.md +114 -0
  59. data/doc/contrib.lt3 +8 -0
  60. data/doc/dependencies.md +281 -0
  61. data/doc/hacker.lt3 +5 -0
  62. data/doc/reddit_credentials_template.json +8 -0
  63. data/doc/reddit_integration.md +207 -0
  64. data/doc/user.lt3 +38 -0
  65. data/doc/user_guide_section_1.md +137 -0
  66. data/doc/user_guide_section_10.md +515 -0
  67. data/doc/user_guide_section_11.md +708 -0
  68. data/doc/user_guide_section_2.md +233 -0
  69. data/doc/user_guide_section_3.md +5 -0
  70. data/doc/user_guide_section_4.md +221 -0
  71. data/doc/user_guide_section_5.md +243 -0
  72. data/doc/user_guide_section_6.md +147 -0
  73. data/doc/user_guide_section_7.md +311 -0
  74. data/doc/user_guide_section_8.md +224 -0
  75. data/doc/user_guide_section_9.md +375 -0
  76. data/doc/userdoc-toc.txt +88 -0
  77. data/lib/rouge/lexers/livetext.rb +74 -0
  78. data/lib/scriptorium/api.rb +640 -0
  79. data/lib/scriptorium/banner_svg.rb +742 -0
  80. data/lib/scriptorium/contract.rb +33 -0
  81. data/lib/scriptorium/exceptions.rb +174 -0
  82. data/lib/scriptorium/helpers.rb +475 -0
  83. data/lib/scriptorium/post.rb +195 -0
  84. data/lib/scriptorium/reddit.rb +83 -0
  85. data/lib/scriptorium/repo.rb +624 -0
  86. data/lib/scriptorium/standard_files.rb +515 -0
  87. data/lib/scriptorium/syntax_highlighter.rb +234 -0
  88. data/lib/scriptorium/theme.rb +179 -0
  89. data/lib/scriptorium/version.rb +2 -2
  90. data/lib/scriptorium/view.rb +976 -0
  91. data/lib/scriptorium/widgets/featured_posts.rb +149 -0
  92. data/lib/scriptorium/widgets/links.rb +112 -0
  93. data/lib/scriptorium/widgets/pages.rb +133 -0
  94. data/lib/scriptorium/widgets/widget.rb +133 -0
  95. data/lib/scriptorium.rb +22 -9
  96. data/lib/skeleton.rb +11 -2
  97. data/scriptorium.gemspec +15 -4
  98. data/test/README.md +69 -0
  99. data/test/all +43 -0
  100. data/test/api_demo.rb +99 -0
  101. data/test/assets/imagenotfound.jpg +0 -0
  102. data/test/assets/images/.DS_Store +0 -0
  103. data/test/assets/images/README.md +27 -0
  104. data/test/assets/images/odd_aspect.png +0 -0
  105. data/test/assets/images/perfect.png +0 -0
  106. data/test/assets/images/small.png +0 -0
  107. data/test/assets/images/tall.png +0 -0
  108. data/test/assets/images/very_tall.png +0 -0
  109. data/test/assets/images/very_wide.png +0 -0
  110. data/test/assets/images/wide.png +0 -0
  111. data/test/assets/testbanner.jpg +0 -0
  112. data/test/banner_svg/simple_helpers.rb +13 -0
  113. data/test/banner_svg/unit.rb +768 -0
  114. data/test/ed_test.rb +204 -0
  115. data/test/integration/cursor_banner_combinations.rb +193 -0
  116. data/test/integration/cursor_banner_features.rb +374 -0
  117. data/test/integration/integration_test.rb +326 -0
  118. data/test/livetext_plugin_test.rb +229 -0
  119. data/test/manual/asset_mgmt.rb +67 -0
  120. data/test/manual/banner-tests/config.txt +3 -0
  121. data/test/manual/banner-tests/index.html +45 -0
  122. data/test/manual/banner-tests/test01.html +58 -0
  123. data/test/manual/banner-tests/test02.html +58 -0
  124. data/test/manual/banner-tests/test03.html +58 -0
  125. data/test/manual/banner-tests/test04.html +65 -0
  126. data/test/manual/banner-tests/test05.html +65 -0
  127. data/test/manual/banner-tests/test06.html +65 -0
  128. data/test/manual/banner-tests/test07.html +65 -0
  129. data/test/manual/banner-tests/test08.html +59 -0
  130. data/test/manual/banner-tests/test09.html +59 -0
  131. data/test/manual/banner-tests/test10.html +59 -0
  132. data/test/manual/banner-tests/test11.html +59 -0
  133. data/test/manual/banner-tests/test12.html +59 -0
  134. data/test/manual/banner-tests/test13.html +59 -0
  135. data/test/manual/banner-tests/test14.html +59 -0
  136. data/test/manual/banner-tests/test15.html +58 -0
  137. data/test/manual/banner-tests/test16.html +58 -0
  138. data/test/manual/banner-tests/test17.html +58 -0
  139. data/test/manual/banner-tests/test18.html +68 -0
  140. data/test/manual/banner-tests/test19.html +68 -0
  141. data/test/manual/banner-tests/test20.html +68 -0
  142. data/test/manual/banner-tests/test21.html +68 -0
  143. data/test/manual/banner-tests/test22.html +68 -0
  144. data/test/manual/banner-tests/test23.html +68 -0
  145. data/test/manual/banner-tests/test24.html +68 -0
  146. data/test/manual/banner-tests/test25.html +67 -0
  147. data/test/manual/banner_environment.rb +192 -0
  148. data/test/manual/deploy_symlink_demo.rb +142 -0
  149. data/test/manual/environment.rb +67 -0
  150. data/test/manual/make_banner.rb +153 -0
  151. data/test/manual/sample_banner_config.txt +12 -0
  152. data/test/manual/symlink_demo.rb +117 -0
  153. data/test/manual/test1.rb +47 -0
  154. data/test/manual/test2.rb +12 -0
  155. data/test/manual/test3.rb +38 -0
  156. data/test/manual/test4.rb +40 -0
  157. data/test/manual/test5.rb +24 -0
  158. data/test/manual/test6.rb +73 -0
  159. data/test/manual/test_banner_combinations.rb +120 -0
  160. data/test/manual/test_banner_features.rb +306 -0
  161. data/test/manual/test_banner_from_file.rb +150 -0
  162. data/test/manual/test_banner_in_header.rb +35 -0
  163. data/test/manual/test_code_highlighting.rb +68 -0
  164. data/test/manual/test_complex_header.rb +74 -0
  165. data/test/manual/test_empty_header.rb +32 -0
  166. data/test/manual/test_radial_custom.rb +58 -0
  167. data/test/manual/test_radial_large_radius.rb +52 -0
  168. data/test/manual/test_svg_debug.rb +47 -0
  169. data/test/manual/test_syntax_highlighting.rb +147 -0
  170. data/test/pages-demo/config/currentview.txt +1 -0
  171. data/test/pages-demo/views/demo/config/bootstrap_css.txt +5 -0
  172. data/test/pages-demo/views/demo/config/bootstrap_js.txt +4 -0
  173. data/test/pages-demo/views/demo/config/common.js +57 -0
  174. data/test/pages-demo/views/demo/config/footer.txt +1 -0
  175. data/test/pages-demo/views/demo/config/global-head.txt +8 -0
  176. data/test/pages-demo/views/demo/config/header.txt +1 -0
  177. data/test/pages-demo/views/demo/config/layout.txt +1 -0
  178. data/test/pages-demo/views/demo/config/left.txt +1 -0
  179. data/test/pages-demo/views/demo/config/main.txt +1 -0
  180. data/test/pages-demo/views/demo/config/right.txt +1 -0
  181. data/test/pages-demo/views/demo/config.txt +3 -0
  182. data/test/pages-demo/views/demo/output/panes/footer.html +1 -0
  183. data/test/pages-demo/views/demo/output/panes/header.html +1 -0
  184. data/test/pages-demo/views/demo/output/panes/left.html +1 -0
  185. data/test/pages-demo/views/demo/output/panes/main.html +1 -0
  186. data/test/pages-demo/views/demo/output/panes/right.html +1 -0
  187. data/test/rubytext/rubytext_comprehensive_test.rb +307 -0
  188. data/test/rubytext/rubytext_demo_test.rb +42 -0
  189. data/test/rubytext/rubytext_testing_guide.md +277 -0
  190. data/test/run_automated_tests.rb +45 -0
  191. data/test/scriptorium-TEST-1754622690-146/config/bootstrap_css.txt +5 -0
  192. data/test/scriptorium-TEST-1754622690-146/config/bootstrap_js.txt +4 -0
  193. data/test/scriptorium-TEST-1754622690-146/config/common.js +57 -0
  194. data/test/scriptorium-TEST-1754622690-146/config/currentview.txt +1 -0
  195. data/test/scriptorium-TEST-1754622690-146/config/global-head.txt +9 -0
  196. data/test/scriptorium-TEST-1754622690-146/config/last_post_num.txt +1 -0
  197. data/test/scriptorium-TEST-1754622690-146/config/os_helpers.rb +4 -0
  198. data/test/scriptorium-TEST-1754622690-146/config/widgets.txt +3 -0
  199. data/test/scriptorium-TEST-1754622690-146/posts/0001/meta.txt +8 -0
  200. data/test/scriptorium-TEST-1754622690-146/posts/0001/source.lt3 +6 -0
  201. data/test/scriptorium-TEST-1754622690-146/themes/standard/README.txt +1 -0
  202. data/test/scriptorium-TEST-1754622690-146/themes/standard/config.txt +1 -0
  203. data/test/scriptorium-TEST-1754622690-146/themes/standard/initial/post.lt3 +12 -0
  204. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/footer.txt +2 -0
  205. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/header.txt +4 -0
  206. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/left.txt +3 -0
  207. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/main.txt +5 -0
  208. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/right.txt +3 -0
  209. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/gen/text.css +1 -0
  210. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/layout.txt +5 -0
  211. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index.lt3 +1 -0
  212. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index_entry.lt3 +14 -0
  213. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/post.lt3 +13 -0
  214. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/widget.lt3 +1 -0
  215. data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_css.txt +5 -0
  216. data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_js.txt +4 -0
  217. data/test/scriptorium-TEST-1754622690-146/views/sample/config/common.js +57 -0
  218. data/test/scriptorium-TEST-1754622690-146/views/sample/config/deploy.txt +5 -0
  219. data/test/scriptorium-TEST-1754622690-146/views/sample/config/footer.txt +2 -0
  220. data/test/scriptorium-TEST-1754622690-146/views/sample/config/global-head.txt +9 -0
  221. data/test/scriptorium-TEST-1754622690-146/views/sample/config/header.txt +4 -0
  222. data/test/scriptorium-TEST-1754622690-146/views/sample/config/layout.txt +5 -0
  223. data/test/scriptorium-TEST-1754622690-146/views/sample/config/left.txt +3 -0
  224. data/test/scriptorium-TEST-1754622690-146/views/sample/config/main.txt +5 -0
  225. data/test/scriptorium-TEST-1754622690-146/views/sample/config/reddit.txt +10 -0
  226. data/test/scriptorium-TEST-1754622690-146/views/sample/config/right.txt +3 -0
  227. data/test/scriptorium-TEST-1754622690-146/views/sample/config/social.txt +7 -0
  228. data/test/scriptorium-TEST-1754622690-146/views/sample/config/status.txt +7 -0
  229. data/test/scriptorium-TEST-1754622690-146/views/sample/config.txt +3 -0
  230. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/footer.html +3 -0
  231. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/header.html +3 -0
  232. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/left.html +3 -0
  233. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/main.html +3 -0
  234. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/right.html +3 -0
  235. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/footer.html +1 -0
  236. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/header.html +1 -0
  237. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/left.html +1 -0
  238. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/main.html +1 -0
  239. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/right.html +1 -0
  240. data/test/staging/.DS_Store +0 -0
  241. data/test/syntax_highlighting_test.lt3 +124 -0
  242. data/test/test_helpers.rb +230 -0
  243. data/test/tui_editor_integration_test.rb +296 -0
  244. data/test/tui_integration_test.rb +637 -0
  245. data/test/unit/api.rb +1056 -0
  246. data/test/unit/asset_management.rb +245 -0
  247. data/test/unit/clipboard_test.rb +60 -0
  248. data/test/unit/contract_test.rb +91 -0
  249. data/test/unit/core.rb +857 -0
  250. data/test/unit/deploy_test.rb +187 -0
  251. data/test/unit/gem_asset_management.rb +189 -0
  252. data/test/unit/livetext_basic.rb +69 -0
  253. data/test/unit/livetext_compatibility.rb +89 -0
  254. data/test/unit/post.rb +244 -0
  255. data/test/unit/read_commented_file_test.rb +276 -0
  256. data/test/unit/reddit_test.rb +235 -0
  257. data/test/unit/repo.rb +548 -0
  258. data/test/unit/social_test.rb +369 -0
  259. data/test/unit/symlink_test.rb +213 -0
  260. data/test/unit/view.rb +431 -0
  261. data/test/unit/widgets.rb +669 -0
  262. data/test/wizard_test.rb +123 -0
  263. data/ui/README.md +67 -0
  264. data/ui/common/lib/ui_common.rb +8 -0
  265. data/ui/rubytext/README.md +191 -0
  266. data/ui/rubytext/bin/scriptorium-rubytext +402 -0
  267. data/ui/rubytext/lib/rubytext_ui.rb +300 -0
  268. data/ui/tui/bin/scriptorium +1420 -0
  269. data/ui/tui/test/tui_test.rb +23 -0
  270. data/ui/web/app/app.rb +1378 -0
  271. data/ui/web/app/error_helpers.rb +150 -0
  272. data/ui/web/app/views/advanced_config.erb +190 -0
  273. data/ui/web/app/views/asset_management.erb +589 -0
  274. data/ui/web/app/views/banner_config.erb +200 -0
  275. data/ui/web/app/views/configure_view.erb +401 -0
  276. data/ui/web/app/views/dashboard.erb +162 -0
  277. data/ui/web/app/views/deploy_config.erb +146 -0
  278. data/ui/web/app/views/edit_pages.erb +195 -0
  279. data/ui/web/app/views/edit_post.erb +54 -0
  280. data/ui/web/app/views/error_page.erb +29 -0
  281. data/ui/web/app/views/header_config.erb +155 -0
  282. data/ui/web/app/views/layout_config.erb +147 -0
  283. data/ui/web/app/views/navbar_config.erb +411 -0
  284. data/ui/web/app/views/view_dashboard.erb +138 -0
  285. data/ui/web/bin/scriptorium-web +153 -0
  286. data/ui/web/test/web_basic_test.rb +38 -0
  287. data/ui/web/test_navbar.txt +7 -0
  288. data/ui/web/tmp/web_server.log +5 -0
  289. data/ui/web/tmp/web_server.pid +1 -0
  290. metadata +360 -5
@@ -0,0 +1,192 @@
1
+ # Set up environment for banner tests
2
+ ENV['PATH'] = "#{ENV['HOME']}/.rbenv/shims:#{ENV['PATH']}"
3
+
4
+ # Minimal environment for banner tests
5
+ require 'fileutils'
6
+ require 'find'
7
+
8
+ # Create test directory if it doesn't exist
9
+ test_dir = File.dirname(__FILE__)/"banner-tests"
10
+ FileUtils.rm_rf(test_dir) if Dir.exist?(test_dir)
11
+ Dir.mkdir(test_dir)
12
+
13
+ @pid = nil
14
+ # Start server from the project root directory
15
+ server_dir = File.expand_path("../..", __FILE__)
16
+ Dir.chdir(server_dir) do
17
+ Process.spawn ("ruby -run -e httpd . -p 8000 >/dev/null 2>&1")
18
+ sleep 1
19
+ end
20
+
21
+ def instruct(msg)
22
+ lines = msg.split("\n")
23
+ longest = lines.map {|line| line.length }.max
24
+ puts " +" + "-" * (longest + 4) + "+"
25
+ lines.each do |line|
26
+ puts " | #{line.ljust(longest + 2)} |"
27
+ end
28
+ puts " +" + "-" * (longest + 4) + "+"
29
+ puts
30
+ end
31
+
32
+ def examine(view_name)
33
+ index_url = "http://127.0.0.1:8000/manual/banner-tests/index.html"
34
+
35
+ if ARGV.include?('--automated')
36
+ # Basic validation for automated mode
37
+ index_file = File.dirname(__FILE__)/"banner-tests/index.html"
38
+ if File.exist?(index_file)
39
+ content = File.read(index_file)
40
+ test_count = content.scan(/<div class='test-item'>/).count
41
+
42
+ # Check that at least one test file has custom content
43
+ test_file = File.dirname(__FILE__)/"banner-tests/test04.html" # Red to Blue gradient test
44
+ if File.exist?(test_file)
45
+ test_content = File.read(test_file)
46
+ unless test_content.include?('linearGradient') || test_content.include?('radialGradient') || test_content.include?('fill=\'#ff0000\'')
47
+ puts "⚠ Warning: No custom banner content found"
48
+ end
49
+ end
50
+ else
51
+ puts "✗ Error: Index file not generated"
52
+ exit 1
53
+ end
54
+
55
+ return
56
+ end
57
+
58
+ puts "Press Enter to open the generated front page to inspect the result."
59
+ STDIN.gets
60
+ system("open #{index_url}")
61
+
62
+ puts "Press Enter to kill webrick"
63
+ STDIN.gets
64
+ line = `ps | grep "ruby -run -e httpd" | grep -v grep`
65
+ pid = line.split.first
66
+ pid2 = pid.to_i + 1
67
+ system("kill #{pid} #{pid2}")
68
+ end
69
+
70
+ # Simple post creation for banner tests
71
+ def create_banner_post(title, subtitle, config_content, description, view_name, post_num = nil)
72
+ # Write config file FIRST, before creating banner
73
+ File.write("banner-tests/config.txt", config_content)
74
+
75
+ # Create banner AFTER config is written, in the correct directory
76
+ # Load the main Scriptorium library
77
+ require_relative '../../lib/scriptorium'
78
+
79
+ # Change to the test directory so the banner can find config.txt
80
+ Dir.chdir("banner-tests") do
81
+ banner = Scriptorium::BannerSVG.new(title, subtitle)
82
+
83
+ # Parse config first to set instance variables
84
+ banner.parse_header_svg
85
+
86
+ # Generate static SVG for manual inspection
87
+ # First parse config to set instance variables, then generate SVG
88
+ banner.parse_header_svg
89
+ svg_output = banner.generate_svg
90
+
91
+ # Use provided post_num or generate one
92
+ Dir.mkdir("posts") unless Dir.exist?("posts")
93
+ post_num ||= Dir.glob("posts/*").length + 1
94
+ post_dir = "posts/#{post_num.to_s.rjust(4, '0')}"
95
+ Dir.mkdir(post_dir) unless Dir.exist?(post_dir)
96
+
97
+ # Create individual page with banner at the top
98
+ post_html = <<~HTML
99
+ <!DOCTYPE html>
100
+ <html>
101
+ <head>
102
+ <title>#{title}</title>
103
+ <style>
104
+ body { font-family: Arial, sans-serif; margin: 0; padding: 0; background: #f5f5f5; }
105
+ .banner { width: 100%; margin: 0; padding: 0; }
106
+ .content { padding: 20px; }
107
+ .description { margin: 10px 0; font-weight: bold; }
108
+ .config { background: #f0f0f0; padding: 10px; margin: 10px 0; font-family: monospace; font-size: 12px; }
109
+ .navigation { background: #e0e0e0; padding: 10px; margin: 20px 0; text-align: center; }
110
+ .navigation a { margin: 0 10px; padding: 5px 10px; background: #007cba; color: white; text-decoration: none; border-radius: 3px; }
111
+ .navigation a:hover { background: #005a87; }
112
+ </style>
113
+ </head>
114
+ <body>
115
+ #{svg_output}
116
+ <div class="content">
117
+ <h1>#{title}</h1>
118
+ <div class="description">#{description}</div>
119
+ <div class="config">#{config_content}</div>
120
+ <div class="navigation">
121
+ <a href="../index.html">Back to Index</a>
122
+ </div>
123
+ </div>
124
+ </body>
125
+ </html>
126
+ HTML
127
+
128
+ # Write individual post file
129
+ File.write("#{post_dir}/index.html", post_html)
130
+
131
+ # Add to view for index generation
132
+ view_dir = "views/#{view_name}/output/posts"
133
+ FileUtils.mkdir_p(view_dir) unless Dir.exist?(view_dir)
134
+ File.write("#{view_dir}/#{post_num.to_s.rjust(4, '0')}.html", post_html)
135
+
136
+ # Return post info for navigation links
137
+ { number: post_num, title: title, filename: "#{post_num.to_s.rjust(4, '0')}.html" }
138
+ end
139
+ end
140
+
141
+ def generate_front_page(view_name)
142
+ # Create simple index page
143
+ posts = Dir.glob("scriptorium-TEST/views/#{view_name}/output/posts/*.html").sort
144
+
145
+ index_html = <<~HTML
146
+ <!DOCTYPE html>
147
+ <html>
148
+ <head>
149
+ <title>Banner Tests - #{view_name}</title>
150
+ <style>
151
+ body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
152
+ .post { background: white; margin: 20px 0; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
153
+ .banner { margin: 20px 0; border: 1px solid #ddd; background: white; }
154
+ .description { margin: 10px 0; font-weight: bold; }
155
+ .config { background: #f0f0f0; padding: 10px; margin: 10px 0; font-family: monospace; font-size: 12px; }
156
+ </style>
157
+ </head>
158
+ <body>
159
+ <h1>Banner Tests - #{view_name}</h1>
160
+ HTML
161
+
162
+ posts.each do |post_file|
163
+ post_content = File.read(post_file)
164
+ # Extract title, banner, description, and config from post
165
+ title_match = post_content.match(/<h1>(.*?)<\/h1>/)
166
+ banner_match = post_content.match(/<div class="banner">(.*?)<\/div>/m)
167
+ desc_match = post_content.match(/<div class="description">(.*?)<\/div>/)
168
+ config_match = post_content.match(/<div class="config">(.*?)<\/div>/m)
169
+
170
+ title = title_match ? title_match[1] : "Unknown"
171
+ banner = banner_match ? banner_match[1] : ""
172
+ description = desc_match ? desc_match[1] : ""
173
+ config = config_match ? config_match[1] : ""
174
+
175
+ index_html += <<~HTML
176
+ <div class="post">
177
+ <h2>#{title}</h2>
178
+ <div class="banner">#{banner}</div>
179
+ <div class="description">#{description}</div>
180
+ <div class="config">#{config}</div>
181
+ </div>
182
+ HTML
183
+ end
184
+
185
+ index_html += <<~HTML
186
+ </body>
187
+ </html>
188
+ HTML
189
+
190
+ FileUtils.mkdir_p("scriptorium-TEST/views/#{view_name}/output")
191
+ File.write("scriptorium-TEST/views/#{view_name}/output/index.html", index_html)
192
+ end
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Manual test script to demonstrate deployment with symlinks
4
+ # Run with: ruby test/manual/deploy_symlink_demo.rb
5
+
6
+ require_relative '../../lib/scriptorium'
7
+ require_relative '../test_helpers'
8
+
9
+ class DeploySymlinkDemo
10
+ include Scriptorium::Helpers
11
+ include TestHelpers
12
+
13
+ def run
14
+ puts "=== Deployment with Symlinks Demo ==="
15
+ puts
16
+
17
+ # Create test repository
18
+ puts "1. Creating test repository..."
19
+ @repo = Scriptorium::Repo.create("test/scriptorium-TEST", testmode: true)
20
+ @view = @repo.create_view("demo_view", "Demo View", "A demo view for deployment")
21
+ puts " ✓ Created repository and view"
22
+ puts
23
+
24
+ # Create a test post
25
+ puts "2. Creating test post..."
26
+ post_title = "Deployment Test Post with Symlinks"
27
+ post_body = "This post tests deployment with symlinks."
28
+
29
+ draft_name = @repo.create_draft(title: post_title, body: post_body)
30
+ post_num = @repo.finish_draft(draft_name)
31
+ puts " ✓ Created post ##{post_num}: #{post_title}"
32
+ puts
33
+
34
+ # Generate the post
35
+ puts "3. Generating post..."
36
+ @repo.generate_post(post_num)
37
+ puts " ✓ Generated post"
38
+ puts
39
+
40
+ # Check the files
41
+ puts "4. Checking generated files..."
42
+
43
+ numbered_slug = slugify(post_num, post_title) + ".html"
44
+ clean_slug = clean_slugify(post_title) + ".html"
45
+
46
+ numbered_path = @view.dir/:output/:permalink/numbered_slug
47
+ clean_symlink_path = @view.dir/:output/:permalink/clean_slug
48
+
49
+ puts " Numbered file: #{numbered_path}"
50
+ puts " Clean symlink: #{clean_symlink_path}"
51
+ puts
52
+
53
+ # Verify files exist
54
+ if File.exist?(numbered_path)
55
+ puts " ✓ Numbered file exists"
56
+ else
57
+ puts " ✗ Numbered file missing"
58
+ end
59
+
60
+ if File.exist?(clean_symlink_path)
61
+ puts " ✓ Clean symlink exists"
62
+ else
63
+ puts " ✗ Clean symlink missing"
64
+ end
65
+
66
+ if File.symlink?(clean_symlink_path)
67
+ puts " ✓ Clean symlink is actually a symlink"
68
+ symlink_target = File.readlink(clean_symlink_path)
69
+ puts " ✓ Symlink points to: #{symlink_target}"
70
+ else
71
+ puts " ✗ Clean symlink is not a symlink"
72
+ end
73
+ puts
74
+
75
+ # Test deployment simulation
76
+ puts "5. Testing deployment simulation..."
77
+
78
+ # Create a test deployment directory
79
+ test_deploy_dir = "test/deploy-test"
80
+ FileUtils.rm_rf(test_deploy_dir) if Dir.exist?(test_deploy_dir)
81
+ FileUtils.mkdir_p(test_deploy_dir)
82
+
83
+ # Simulate rsync command with symlink preservation
84
+ output_dir = @view.dir/:output
85
+ rsync_cmd = "rsync -r -z -l #{output_dir}/ #{test_deploy_dir}/"
86
+ puts " Simulating: #{rsync_cmd}"
87
+
88
+ # Actually copy files (simulating rsync)
89
+ FileUtils.cp_r("#{output_dir}/.", test_deploy_dir)
90
+
91
+ # Check if symlinks were preserved
92
+ deployed_symlink = "#{test_deploy_dir}/permalink/#{clean_slug}"
93
+ deployed_target = "#{test_deploy_dir}/permalink/#{numbered_slug}"
94
+
95
+ if File.exist?(deployed_symlink)
96
+ puts " ✓ Symlink exists in deployment directory"
97
+
98
+ if File.symlink?(deployed_symlink)
99
+ puts " ✓ Symlink is preserved in deployment"
100
+ deployed_target_readlink = File.readlink(deployed_symlink)
101
+ puts " ✓ Deployed symlink points to: #{deployed_target_readlink}"
102
+
103
+ if File.exist?(deployed_target)
104
+ puts " ✓ Symlink target exists in deployment"
105
+ else
106
+ puts " ✗ Symlink target missing in deployment"
107
+ end
108
+ else
109
+ puts " ✗ Symlink was not preserved (copied as file)"
110
+ end
111
+ else
112
+ puts " ✗ Symlink missing in deployment directory"
113
+ end
114
+ puts
115
+
116
+ # Show deployment structure
117
+ puts "6. Deployment directory structure:"
118
+ if Dir.exist?(test_deploy_dir)
119
+ Dir.glob("#{test_deploy_dir}/**/*").sort.each do |file|
120
+ relative_path = file.sub("#{test_deploy_dir}/", "")
121
+ if File.symlink?(file)
122
+ target = File.readlink(file)
123
+ puts " #{relative_path} -> #{target} (symlink)"
124
+ elsif File.directory?(file)
125
+ puts " #{relative_path}/ (directory)"
126
+ else
127
+ puts " #{relative_path} (file)"
128
+ end
129
+ end
130
+ end
131
+ puts
132
+
133
+ puts "=== Demo Complete ==="
134
+ puts
135
+ puts "To clean up, run: rm -rf test/scriptorium-TEST test/deploy-test"
136
+ end
137
+ end
138
+
139
+ if __FILE__ == $0
140
+ demo = DeploySymlinkDemo.new
141
+ demo.run
142
+ end
@@ -0,0 +1,67 @@
1
+ # Set up environment for manual tests
2
+ ENV['PATH'] = "#{ENV['HOME']}/.rbenv/shims:#{ENV['PATH']}"
3
+
4
+ # rbenv should be enabled in the shell before running manual tests
5
+
6
+ require_relative '../../lib/scriptorium'
7
+ require_relative '../test_helpers'
8
+
9
+ include TestHelpers
10
+
11
+ def manual_setup
12
+ system("rm -rf test/scriptorium-TEST")
13
+
14
+ @repo = Scriptorium::Repo.create("test/scriptorium-TEST", testmode: true) # true for testing mode
15
+
16
+ @pid = nil
17
+ Dir.chdir(File.expand_path("..", __dir__)) do
18
+ Process.spawn ("ruby -run -e httpd . -p 8000 >/dev/null 2>&1")
19
+ sleep 1
20
+ puts "webrick started\n "
21
+ end
22
+ end
23
+
24
+ def instruct(msg)
25
+ lines = msg.split("\n")
26
+ longest = lines.map {|line| line.length }.max
27
+ puts " +" + "-" * (longest + 4) + "+"
28
+ lines.each do |line|
29
+ puts " | #{line.ljust(longest + 2)} |"
30
+ end
31
+ puts " +" + "-" * (longest + 4) + "+"
32
+ puts
33
+ end
34
+
35
+ def examine(view)
36
+ view = @repo.lookup_view(view)
37
+ index_url = "http://127.0.0.1:8000/scriptorium-TEST/views/#{view.name}/output/index.html"
38
+ puts "Generated front page located at: \n#{index_url}"
39
+
40
+ if ARGV.include?('--automated')
41
+ # Automated mode - just validate files were created
42
+ index_file = "test/scriptorium-TEST/views/#{view.name}/output/index.html"
43
+ if File.exist?(index_file)
44
+ puts "✓ Files generated successfully"
45
+ else
46
+ puts "✗ Error: Index file not generated"
47
+ exit 1
48
+ end
49
+ return
50
+ end
51
+
52
+ puts "Press Enter to open the generated front page to inspect the result."
53
+ STDIN.gets
54
+ cmd = "open #{index_url}"
55
+ see("cmd = ", cmd)
56
+ system("open #{index_url}")
57
+
58
+ puts "Press Enter to kill webrick"
59
+ STDIN.gets
60
+ line = `ps | grep "ruby -run -e httpd" | grep -v grep`
61
+ pid = line.split.first
62
+ puts "line: #{line}"
63
+ puts "pid: #{pid}"
64
+ pid2 = pid.to_i + 1
65
+ system("kill #{pid} #{pid2}")
66
+ puts "Killed\n "
67
+ end
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../../lib/scriptorium'
4
+ include Scriptorium::Helpers
5
+
6
+ # Simple banner test from configuration file
7
+ def test_banner_from_file(config_file, title = nil, subtitle = nil, output_file = nil)
8
+ # Default output file if not specified
9
+ output_file ||= "banner_output.html"
10
+
11
+ # Default titles if not specified
12
+ title ||= "Test Banner"
13
+ subtitle ||= "Generated from config file"
14
+
15
+ # Read the configuration file
16
+ unless File.exist?(config_file)
17
+ puts "Error: Configuration file '#{config_file}' not found"
18
+ exit 1
19
+ end
20
+
21
+ # Use the read_commented_file helper to read the config
22
+ config_lines = read_commented_file(config_file)
23
+ config_content = File.read(config_file) # Keep original for display
24
+
25
+ puts "Read configuration from: #{config_file}"
26
+ puts "Found #{config_lines.length} configuration lines (comments removed)"
27
+ puts "Title: #{title}"
28
+ puts "Subtitle: #{subtitle}"
29
+
30
+ # Create banner with the actual titles from View
31
+ banner = Scriptorium::BannerSVG.new(title, subtitle)
32
+
33
+ # Write config to temporary file for banner to read
34
+ temp_config = "config.txt"
35
+ File.write(temp_config, config_content)
36
+
37
+ # Parse and generate the banner
38
+ banner.parse_header_svg
39
+ svg_output = banner.generate_svg
40
+
41
+ # Clean up temp config file
42
+ File.delete(temp_config) if File.exist?(temp_config)
43
+
44
+ # Create HTML output
45
+ html_content = <<~HTML
46
+ <!DOCTYPE html>
47
+ <html>
48
+ <head>
49
+ <title>Banner Test - #{File.basename(config_file)}</title>
50
+ <style>
51
+ body {
52
+ font-family: Arial, sans-serif;
53
+ margin: 0;
54
+ padding: 20px;
55
+ background: #f5f5f5;
56
+ }
57
+ .banner {
58
+ width: 100%;
59
+ max-width: 800px;
60
+ margin: 0 auto 20px auto;
61
+ border: 2px solid #007cba;
62
+ border-radius: 8px;
63
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
64
+ }
65
+ .config {
66
+ background: white;
67
+ padding: 20px;
68
+ margin: 20px auto;
69
+ max-width: 800px;
70
+ border-radius: 8px;
71
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
72
+ }
73
+ .config h3 {
74
+ margin-top: 0;
75
+ color: #333;
76
+ }
77
+ .config pre {
78
+ background: #f8f9fa;
79
+ padding: 15px;
80
+ border-radius: 4px;
81
+ overflow-x: auto;
82
+ font-family: monospace;
83
+ font-size: 12px;
84
+ }
85
+ .info {
86
+ background: white;
87
+ padding: 20px;
88
+ margin: 20px auto;
89
+ max-width: 800px;
90
+ border-radius: 8px;
91
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
92
+ }
93
+ .processed-config {
94
+ background: #e8f4fd;
95
+ padding: 15px;
96
+ margin: 10px 0;
97
+ border-radius: 4px;
98
+ font-family: monospace;
99
+ font-size: 12px;
100
+ }
101
+ </style>
102
+ </head>
103
+ <body>
104
+ <div class="banner">
105
+ #{svg_output}
106
+ </div>
107
+
108
+ <div class="info">
109
+ <h3>Banner Information</h3>
110
+ <p><strong>Configuration file:</strong> #{config_file}</p>
111
+ <p><strong>Output file:</strong> #{output_file}</p>
112
+ <p><strong>Generated:</strong> #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}</p>
113
+ <p><strong>Configuration lines processed:</strong> #{config_lines.length}</p>
114
+ </div>
115
+
116
+ <div class="config">
117
+ <h3>Configuration Used </h3>
118
+ <pre>#{config_content}</pre>
119
+ </div>
120
+ </body>
121
+ </html>
122
+ HTML
123
+
124
+ # Write the HTML file
125
+ File.write(output_file, html_content)
126
+ puts "Generated banner HTML: #{output_file}"
127
+
128
+ # Open in browser if on macOS
129
+ if RUBY_PLATFORM.include?('darwin')
130
+ puts "Opening in browser..."
131
+ system("open #{output_file}")
132
+ else
133
+ puts "Open #{output_file} in your browser to view the banner"
134
+ end
135
+ end
136
+
137
+ # Main execution
138
+ if ARGV.empty?
139
+ puts "Usage: ruby make_banner.rb <config_file> [title] [subtitle] [output_file]"
140
+ puts ""
141
+ puts "Example:"
142
+ puts " ruby make_banner.rb my_banner_config.txt"
143
+ puts " ruby make_banner.rb my_banner_config.txt 'My Blog' 'A subtitle'"
144
+ puts " ruby make_banner.rb my_banner_config.txt 'My Blog' 'A subtitle' custom_output.html"
145
+ exit 1
146
+ end
147
+
148
+ config_file = ARGV[0]
149
+ title = ARGV[1] # Optional
150
+ subtitle = ARGV[2] # Optional
151
+ output_file = ARGV[3] # Optional
152
+
153
+ test_banner_from_file(config_file, title, subtitle, output_file)
@@ -0,0 +1,12 @@
1
+ aspect 7.0 # Aspect ratio (width:height)
2
+ text.font verdana # For both title and subtitle
3
+ text.color #cccccc
4
+ text.justify left # For both title and subtitle
5
+
6
+ title.scale 0.8 # ~48px (1.5x <h1>)
7
+ subtitle.scale 0.4 # 24px ~= <h2>
8
+
9
+ title.style bold # boldfaced title
10
+ subtitle.style bold italic # bold *and* italic (actually uses SVG weight and style)
11
+ title.xy 5 10
12
+ subtitle.xy 5 40
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Manual test script to demonstrate symlink functionality
4
+ # Run with: ruby test/manual/symlink_demo.rb
5
+
6
+ require_relative '../../lib/scriptorium'
7
+ require_relative '../test_helpers'
8
+
9
+ class SymlinkDemo
10
+ include Scriptorium::Helpers
11
+ include TestHelpers
12
+
13
+ def run
14
+ puts "=== Symlink Functionality Demo ==="
15
+ puts
16
+
17
+ # Create test repository
18
+ puts "1. Creating test repository..."
19
+ @repo = Scriptorium::Repo.create("test/scriptorium-TEST", testmode: true)
20
+ @view = @repo.create_view("demo_view", "Demo View", "A demo view for symlinks")
21
+ puts " ✓ Created repository and view"
22
+ puts
23
+
24
+ # Create a test post
25
+ puts "2. Creating test post..."
26
+ post_title = "My First Test Post with Special Characters: & < > \" ' !"
27
+ post_body = "This is the body of my test post. It contains some content."
28
+
29
+ draft_name = @repo.create_draft(title: post_title, body: post_body)
30
+ post_num = @repo.finish_draft(draft_name)
31
+ puts " ✓ Created post ##{post_num}: #{post_title}"
32
+ puts
33
+
34
+ # Generate the post
35
+ puts "3. Generating post..."
36
+ @repo.generate_post(post_num)
37
+ puts " ✓ Generated post"
38
+ puts
39
+
40
+ # Check the files
41
+ puts "4. Checking generated files..."
42
+
43
+ numbered_slug = slugify(post_num, post_title) + ".html"
44
+ clean_slug = clean_slugify(post_title) + ".html"
45
+
46
+ numbered_path = @view.dir/:output/:permalink/numbered_slug
47
+ clean_symlink_path = @view.dir/:output/:permalink/clean_slug
48
+
49
+ puts " Numbered file: #{numbered_path}"
50
+ puts " Clean symlink: #{clean_symlink_path}"
51
+ puts
52
+
53
+ # Verify files exist
54
+ if File.exist?(numbered_path)
55
+ puts " ✓ Numbered file exists"
56
+ else
57
+ puts " ✗ Numbered file missing"
58
+ end
59
+
60
+ if File.exist?(clean_symlink_path)
61
+ puts " ✓ Clean symlink exists"
62
+ else
63
+ puts " ✗ Clean symlink missing"
64
+ end
65
+
66
+ if File.symlink?(clean_symlink_path)
67
+ puts " ✓ Clean symlink is actually a symlink"
68
+ symlink_target = File.readlink(clean_symlink_path)
69
+ puts " ✓ Symlink points to: #{symlink_target}"
70
+ else
71
+ puts " ✗ Clean symlink is not a symlink"
72
+ end
73
+ puts
74
+
75
+ # Show the URLs
76
+ puts "5. Generated URLs:"
77
+ puts " Numbered URL: /permalink/#{numbered_slug}"
78
+ puts " Clean URL: /permalink/#{clean_slug}"
79
+ puts
80
+
81
+ # Test the symlink
82
+ puts "6. Testing symlink..."
83
+ if File.symlink?(clean_symlink_path)
84
+ symlink_target = File.readlink(clean_symlink_path)
85
+ if symlink_target == numbered_slug
86
+ puts " ✓ Symlink correctly points to numbered file"
87
+ else
88
+ puts " ✗ Symlink points to wrong target: #{symlink_target}"
89
+ end
90
+ else
91
+ puts " ✗ Clean symlink is not a symlink"
92
+ end
93
+ puts
94
+
95
+ # Show file contents
96
+ puts "7. File contents:"
97
+ if File.exist?(numbered_path)
98
+ content = File.read(numbered_path)
99
+ puts " Numbered file size: #{content.length} bytes"
100
+ puts " Contains 'Back to Blog' link: #{content.include?('Back to Blog') ? 'Yes' : 'No'}"
101
+ end
102
+
103
+ if File.exist?(clean_symlink_path) && File.symlink?(clean_symlink_path)
104
+ puts " Clean symlink: points to #{File.readlink(clean_symlink_path)}"
105
+ end
106
+ puts
107
+
108
+ puts "=== Demo Complete ==="
109
+ puts
110
+ puts "To clean up, run: rm -rf test/scriptorium-TEST"
111
+ end
112
+ end
113
+
114
+ if __FILE__ == $0
115
+ demo = SymlinkDemo.new
116
+ demo.run
117
+ end