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
@@ -0,0 +1,402 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Scriptorium RubyText UI
4
+ # Brings back the sophisticated interactive interface from Runeblog
5
+
6
+ require 'rubytext'
7
+ require_relative '../../lib/scriptorium'
8
+ require_relative '../lib/rubytext_ui'
9
+
10
+ # Set up environment
11
+ ENV['DBC_DISABLED'] = 'true' if ENV['RACK_ENV'] == 'test'
12
+
13
+ class ScriptoriumRubyTextUI
14
+ include Scriptorium::Exceptions
15
+ include Scriptorium::Helpers
16
+
17
+ def initialize
18
+ @repo = nil
19
+ @current_view = nil
20
+ @editor = ENV['EDITOR'] || 'vim'
21
+ end
22
+
23
+ def start
24
+ setup_rubytext
25
+ check_repository
26
+ main_loop
27
+ rescue => e
28
+ handle_error(e)
29
+ ensure
30
+ cleanup
31
+ end
32
+
33
+ private
34
+
35
+ def setup_rubytext
36
+ RubyText.start(:_echo, :keypad, scroll: true, fg: :blue, bg: :white)
37
+ puts fx("Scriptorium RubyText UI", :bold)
38
+ puts fx("v #{Scriptorium::VERSION}", :red)
39
+ puts
40
+ end
41
+
42
+ def check_repository
43
+ if Scriptorium::Repo.exist?
44
+ @repo = Scriptorium::Repo.new
45
+ @current_view = @repo.current_view
46
+ puts fx("Repository found: #{@repo.root}", :green)
47
+ puts fx("Current view: #{@current_view&.name || 'None'}", :green)
48
+ else
49
+ puts fx("No repository found.", :yellow)
50
+ if yesno("Create new repository?")
51
+ create_repository
52
+ else
53
+ puts "Exiting..."
54
+ exit 0
55
+ end
56
+ end
57
+ end
58
+
59
+ def create_repository
60
+ puts fx("Creating new repository...", :bold)
61
+ @repo = Scriptorium::Repo.create
62
+ puts fx("Repository created successfully!", :green)
63
+
64
+ if yesno("Create your first view?")
65
+ create_first_view
66
+ end
67
+ end
68
+
69
+ def create_first_view
70
+ print "View name: "
71
+ view_name = gets.chomp.strip
72
+ return if view_name.empty?
73
+
74
+ print "View title: "
75
+ view_title = gets.chomp.strip
76
+ return if view_title.empty?
77
+
78
+ print "View subtitle (optional): "
79
+ view_subtitle = gets.chomp.strip
80
+
81
+ @current_view = @repo.create_view(view_name, view_title, view_subtitle)
82
+ puts fx("View '#{view_name}' created successfully!", :green)
83
+ end
84
+
85
+ def main_loop
86
+ loop do
87
+ display_context
88
+ command = gets.chomp.strip
89
+
90
+ case command.downcase
91
+ when 'q', 'quit', 'exit'
92
+ break
93
+ when 'h', 'help'
94
+ show_help
95
+ when 'v', 'version'
96
+ show_version
97
+ when 'new post'
98
+ create_post
99
+ when 'list posts'
100
+ list_posts
101
+ when 'list views'
102
+ list_views
103
+ when 'change view'
104
+ change_view
105
+ when 'config'
106
+ edit_config
107
+ when 'manage pages'
108
+ manage_pages
109
+ when 'manage links'
110
+ manage_links
111
+ when 'rebuild'
112
+ rebuild_view
113
+ when 'preview'
114
+ preview_view
115
+ when 'publish'
116
+ publish_view
117
+ when ''
118
+ # Empty command, continue
119
+ else
120
+ puts fx("Unknown command: #{command}", :red)
121
+ puts "Type 'help' for available commands"
122
+ end
123
+ end
124
+ end
125
+
126
+ def display_context
127
+ view_name = @current_view&.name || "No view"
128
+ print fx("[#{view_name}] ", :red, :bold)
129
+ end
130
+
131
+ def show_help
132
+ help_text = <<~HELP
133
+ Available Commands:
134
+
135
+ Basics:
136
+ h, help Show this help
137
+ q, quit, exit Exit the program
138
+ v, version Show version information
139
+
140
+ Posts:
141
+ new post Create a new post
142
+ list posts List posts in current view
143
+ edit <id> Edit a specific post
144
+
145
+ Views:
146
+ list views List all views
147
+ change view Change current view
148
+ new view Create a new view
149
+
150
+ Configuration:
151
+ config Edit configuration files
152
+ manage pages Manage pages widget
153
+ manage links Manage links widget
154
+
155
+ Generation:
156
+ rebuild Rebuild current view
157
+ preview Preview current view
158
+ publish Publish current view
159
+ HELP
160
+
161
+ puts help_text
162
+ end
163
+
164
+ def show_version
165
+ puts fx("Scriptorium", :bold), fx(" v #{Scriptorium::VERSION}", :red)
166
+ end
167
+
168
+ def create_post
169
+ return unless check_view
170
+
171
+ print "Post title: "
172
+ title = gets.chomp.strip
173
+ return if title.empty?
174
+
175
+ print "Post body (optional): "
176
+ body = gets.chomp.strip
177
+
178
+ draft_name = @repo.create_draft(title: title, body: body)
179
+ post_num = @repo.finish_draft(draft_name)
180
+
181
+ puts fx("Post ##{post_num} created successfully!", :green)
182
+ end
183
+
184
+ def list_posts
185
+ return unless check_view
186
+
187
+ posts = @repo.all_posts(@current_view)
188
+ if posts.empty?
189
+ puts "No posts in current view."
190
+ else
191
+ puts fx("Posts in #{@current_view.name}:", :bold)
192
+ posts.each do |post|
193
+ puts " #{post.id}: #{post.title}"
194
+ end
195
+ end
196
+ end
197
+
198
+ def list_views
199
+ views = @repo.views
200
+ if views.empty?
201
+ puts "No views found."
202
+ else
203
+ puts fx("Available views:", :bold)
204
+ views.each do |view|
205
+ current = view == @current_view ? " (current)" : ""
206
+ puts " #{view.name}#{current}"
207
+ end
208
+ end
209
+ end
210
+
211
+ def change_view
212
+ views = @repo.views
213
+ return puts("No views available.") if views.empty?
214
+
215
+ puts fx("Available views:", :bold)
216
+ views.each_with_index do |view, i|
217
+ current = view == @current_view ? " (current)" : ""
218
+ puts " #{i + 1}. #{view.name}#{current}"
219
+ end
220
+
221
+ print "Select view (1-#{views.length}): "
222
+ choice = gets.chomp.strip.to_i
223
+
224
+ if choice >= 1 && choice <= views.length
225
+ @current_view = views[choice - 1]
226
+ @repo.current_view = @current_view
227
+ puts fx("Switched to view: #{@current_view.name}", :green)
228
+ else
229
+ puts fx("Invalid choice.", :red)
230
+ end
231
+ end
232
+
233
+ def edit_config
234
+ config_items = {
235
+ "General Settings" => "config/general.txt",
236
+ "View Settings" => "config/view.txt",
237
+ "Theme Settings" => "config/theme.txt"
238
+ }
239
+
240
+ puts fx("Configuration files:", :bold)
241
+ config_items.each_with_index do |(name, path), i|
242
+ puts " #{i + 1}. #{name}"
243
+ end
244
+
245
+ print "Select file (1-#{config_items.length}): "
246
+ choice = gets.chomp.strip.to_i
247
+
248
+ if choice >= 1 && choice <= config_items.length
249
+ file_path = config_items.values[choice - 1]
250
+ edit_file(file_path)
251
+ else
252
+ puts fx("Invalid choice.", :red)
253
+ end
254
+ end
255
+
256
+ def manage_pages
257
+ return unless check_view
258
+
259
+ pages_dir = @current_view.dir/"pages"
260
+ files = Dir.glob("#{pages_dir}/*.html") if Dir.exist?(pages_dir)
261
+ files ||= []
262
+
263
+ menu_items = files.map { |f| File.basename(f) } + ["[New Page]", "[Cancel]"]
264
+
265
+ puts fx("Manage Pages:", :bold)
266
+ menu_items.each_with_index do |item, i|
267
+ puts " #{i + 1}. #{item}"
268
+ end
269
+
270
+ print "Select option (1-#{menu_items.length}): "
271
+ choice = gets.chomp.strip.to_i
272
+
273
+ if choice >= 1 && choice <= menu_items.length
274
+ selected = menu_items[choice - 1]
275
+ case selected
276
+ when "[New Page]"
277
+ create_new_page
278
+ when "[Cancel]"
279
+ return
280
+ else
281
+ edit_page(selected)
282
+ end
283
+ else
284
+ puts fx("Invalid choice.", :red)
285
+ end
286
+ end
287
+
288
+ def manage_links
289
+ return unless check_view
290
+
291
+ links_dir = @current_view.dir/"widgets"/"links"
292
+ list_file = links_dir/"list.txt" if Dir.exist?(links_dir)
293
+
294
+ if list_file && File.exist?(list_file)
295
+ edit_file(list_file)
296
+ else
297
+ puts "No links widget found. Create one first."
298
+ end
299
+ end
300
+
301
+ def rebuild_view
302
+ return unless check_view
303
+
304
+ puts fx("Rebuilding view...", :bold)
305
+ @repo.generate_view(@current_view)
306
+ puts fx("View rebuilt successfully!", :green)
307
+ end
308
+
309
+ def preview_view
310
+ return unless check_view
311
+
312
+ puts fx("Starting preview server...", :bold)
313
+ # Implementation would start a local server
314
+ puts "Preview available at: http://localhost:8000"
315
+ end
316
+
317
+ def publish_view
318
+ return unless check_view
319
+
320
+ puts fx("Publishing view...", :bold)
321
+ # Implementation would handle deployment
322
+ puts fx("View published successfully!", :green)
323
+ end
324
+
325
+ def create_new_page
326
+ print "Page title: "
327
+ title = gets.chomp.strip
328
+ return if title.empty?
329
+
330
+ print "Page filename (without .html): "
331
+ filename = gets.chomp.strip
332
+ return if filename.empty?
333
+
334
+ filename += ".html" unless filename.end_with?(".html")
335
+ page_path = @current_view.dir/"pages"/filename
336
+
337
+ content = <<~HTML
338
+ <!DOCTYPE html>
339
+ <html>
340
+ <head>
341
+ <title>#{title}</title>
342
+ </head>
343
+ <body>
344
+ <h1>#{title}</h1>
345
+ <p>Page content goes here.</p>
346
+ </body>
347
+ </html>
348
+ HTML
349
+
350
+ File.write(page_path, content)
351
+ puts fx("Page '#{filename}' created successfully!", :green)
352
+ end
353
+
354
+ def edit_page(filename)
355
+ page_path = @current_view.dir/"pages"/filename
356
+ if File.exist?(page_path)
357
+ edit_file(page_path)
358
+ else
359
+ puts fx("Page '#{filename}' not found.", :red)
360
+ end
361
+ end
362
+
363
+ def edit_file(file_path)
364
+ full_path = File.expand_path(file_path, @repo.root)
365
+ if File.exist?(full_path)
366
+ system("#{@editor} #{full_path}")
367
+ else
368
+ puts fx("File '#{file_path}' not found.", :red)
369
+ end
370
+ end
371
+
372
+ def check_view
373
+ unless @current_view
374
+ puts fx("No view selected. Use 'change view' first.", :red)
375
+ return false
376
+ end
377
+ true
378
+ end
379
+
380
+ def yesno(question)
381
+ print fx("#{question} (y/n): ", :bold)
382
+ response = gets.chomp.strip.downcase
383
+ response == 'y' || response == 'yes'
384
+ end
385
+
386
+ def handle_error(error)
387
+ puts fx("Error: #{error.message}", :red)
388
+ puts error.backtrace if ENV['DEBUG']
389
+ end
390
+
391
+ def cleanup
392
+ RubyText.stop
393
+ puts "\nGoodbye!"
394
+ end
395
+ end
396
+
397
+ # Main execution
398
+ if __FILE__ == $0
399
+ ui = ScriptoriumRubyTextUI.new
400
+ ui.start
401
+ end
402
+
@@ -0,0 +1,300 @@
1
+ # Scriptorium RubyText UI Library
2
+ # Common functionality for the RubyText-based interface
3
+
4
+ require 'rubytext'
5
+ require_relative '../../../lib/scriptorium'
6
+
7
+ module Scriptorium
8
+ module RubyTextUI
9
+ # Common UI utilities and helpers
10
+
11
+ def self.setup_environment
12
+ # Set up RubyText environment
13
+ RubyText.start(:_echo, :keypad, scroll: true, fg: :blue, bg: :white)
14
+ end
15
+
16
+ def self.cleanup_environment
17
+ RubyText.stop
18
+ end
19
+
20
+ # Menu system for file selection and configuration
21
+ class MenuSystem
22
+ def self.select_file(title:, files:, allow_new: false, allow_cancel: true)
23
+ menu_items = files.dup
24
+ menu_items << "[New File]" if allow_new
25
+ menu_items << "[Cancel]" if allow_cancel
26
+
27
+ puts fx(title, :bold)
28
+ menu_items.each_with_index do |item, i|
29
+ puts " #{i + 1}. #{item}"
30
+ end
31
+
32
+ print "Select option (1-#{menu_items.length}): "
33
+ choice = gets.chomp.strip.to_i
34
+
35
+ if choice >= 1 && choice <= menu_items.length
36
+ selected = menu_items[choice - 1]
37
+ case selected
38
+ when "[New File]"
39
+ :new
40
+ when "[Cancel]"
41
+ :cancel
42
+ else
43
+ selected
44
+ end
45
+ else
46
+ :invalid
47
+ end
48
+ end
49
+
50
+ def self.select_from_hash(title:, items:)
51
+ puts fx(title, :bold)
52
+ items.each_with_index do |(name, path), i|
53
+ puts " #{i + 1}. #{name}"
54
+ end
55
+
56
+ print "Select option (1-#{items.length}): "
57
+ choice = gets.chomp.strip.to_i
58
+
59
+ if choice >= 1 && choice <= items.length
60
+ items.values[choice - 1]
61
+ else
62
+ nil
63
+ end
64
+ end
65
+ end
66
+
67
+ # Editor integration
68
+ class Editor
69
+ def self.edit_file(file_path, editor: nil)
70
+ editor ||= ENV['EDITOR'] || 'vim'
71
+ full_path = File.expand_path(file_path)
72
+
73
+ if File.exist?(full_path)
74
+ system("#{editor} #{full_path}")
75
+ true
76
+ else
77
+ false
78
+ end
79
+ end
80
+
81
+ def self.create_file(file_path, content: nil, editor: nil)
82
+ full_path = File.expand_path(file_path)
83
+ dir = File.dirname(full_path)
84
+
85
+ # Create directory if it doesn't exist
86
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
87
+
88
+ # Write initial content if provided
89
+ if content
90
+ File.write(full_path, content)
91
+ end
92
+
93
+ # Open in editor
94
+ edit_file(full_path, editor: editor)
95
+ end
96
+ end
97
+
98
+ # Widget management
99
+ class WidgetManager
100
+ def self.manage_pages(view)
101
+ pages_dir = view.dir/"pages"
102
+ files = []
103
+
104
+ if Dir.exist?(pages_dir)
105
+ files = Dir.glob("#{pages_dir}/*.html").map { |f| File.basename(f) }
106
+ end
107
+
108
+ selected = MenuSystem.select_file(
109
+ title: "Manage Pages:",
110
+ files: files,
111
+ allow_new: true,
112
+ allow_cancel: true
113
+ )
114
+
115
+ case selected
116
+ when :new
117
+ create_new_page(view)
118
+ when :cancel
119
+ nil
120
+ when :invalid
121
+ puts fx("Invalid choice.", :red)
122
+ else
123
+ edit_page(view, selected)
124
+ end
125
+ end
126
+
127
+ def self.manage_links(view)
128
+ links_dir = view.dir/"widgets"/"links"
129
+ list_file = links_dir/"list.txt" if Dir.exist?(links_dir)
130
+
131
+ if list_file && File.exist?(list_file)
132
+ Editor.edit_file(list_file)
133
+ else
134
+ puts "No links widget found. Create one first."
135
+ end
136
+ end
137
+
138
+ private
139
+
140
+ def self.create_new_page(view)
141
+ print "Page title: "
142
+ title = gets.chomp.strip
143
+ return if title.empty?
144
+
145
+ print "Page filename (without .html): "
146
+ filename = gets.chomp.strip
147
+ return if filename.empty?
148
+
149
+ filename += ".html" unless filename.end_with?(".html")
150
+ page_path = view.dir/"pages"/filename
151
+
152
+ content = <<~HTML
153
+ <!DOCTYPE html>
154
+ <html>
155
+ <head>
156
+ <title>#{title}</title>
157
+ </head>
158
+ <body>
159
+ <h1>#{title}</h1>
160
+ <p>Page content goes here.</p>
161
+ </body>
162
+ </html>
163
+ HTML
164
+
165
+ Editor.create_file(page_path, content: content)
166
+ puts fx("Page '#{filename}' created successfully!", :green)
167
+ end
168
+
169
+ def self.edit_page(view, filename)
170
+ page_path = view.dir/"pages"/filename
171
+ if File.exist?(page_path)
172
+ Editor.edit_file(page_path)
173
+ else
174
+ puts fx("Page '#{filename}' not found.", :red)
175
+ end
176
+ end
177
+ end
178
+
179
+ # Configuration management
180
+ class ConfigManager
181
+ def self.edit_config(repo)
182
+ config_items = {
183
+ "General Settings" => "config/general.txt",
184
+ "View Settings" => "config/view.txt",
185
+ "Theme Settings" => "config/theme.txt"
186
+ }
187
+
188
+ selected = MenuSystem.select_from_hash(
189
+ title: "Configuration files:",
190
+ items: config_items
191
+ )
192
+
193
+ if selected
194
+ full_path = File.expand_path(selected, repo.root)
195
+ Editor.edit_file(full_path)
196
+ else
197
+ puts fx("Invalid choice.", :red)
198
+ end
199
+ end
200
+ end
201
+
202
+ # View management
203
+ class ViewManager
204
+ def self.list_views(repo, current_view: nil)
205
+ views = repo.views
206
+ if views.empty?
207
+ puts "No views found."
208
+ else
209
+ puts fx("Available views:", :bold)
210
+ views.each do |view|
211
+ current = view == current_view ? " (current)" : ""
212
+ puts " #{view.name}#{current}"
213
+ end
214
+ end
215
+ end
216
+
217
+ def self.change_view(repo, current_view: nil)
218
+ views = repo.views
219
+ return puts("No views available.") if views.empty?
220
+
221
+ puts fx("Available views:", :bold)
222
+ views.each_with_index do |view, i|
223
+ current = view == current_view ? " (current)" : ""
224
+ puts " #{i + 1}. #{view.name}#{current}"
225
+ end
226
+
227
+ print "Select view (1-#{views.length}): "
228
+ choice = gets.chomp.strip.to_i
229
+
230
+ if choice >= 1 && choice <= views.length
231
+ selected_view = views[choice - 1]
232
+ repo.current_view = selected_view
233
+ puts fx("Switched to view: #{selected_view.name}", :green)
234
+ selected_view
235
+ else
236
+ puts fx("Invalid choice.", :red)
237
+ nil
238
+ end
239
+ end
240
+
241
+ def self.create_view(repo)
242
+ print "View name: "
243
+ view_name = gets.chomp.strip
244
+ return nil if view_name.empty?
245
+
246
+ print "View title: "
247
+ view_title = gets.chomp.strip
248
+ return nil if view_title.empty?
249
+
250
+ print "View subtitle (optional): "
251
+ view_subtitle = gets.chomp.strip
252
+
253
+ view = repo.create_view(view_name, view_title, view_subtitle)
254
+ puts fx("View '#{view_name}' created successfully!", :green)
255
+ view
256
+ end
257
+ end
258
+
259
+ # Post management
260
+ class PostManager
261
+ def self.list_posts(repo, view)
262
+ posts = repo.all_posts(view)
263
+ if posts.empty?
264
+ puts "No posts in current view."
265
+ else
266
+ puts fx("Posts in #{view.name}:", :bold)
267
+ posts.each do |post|
268
+ puts " #{post.id}: #{post.title}"
269
+ end
270
+ end
271
+ end
272
+
273
+ def self.create_post(repo, view)
274
+ print "Post title: "
275
+ title = gets.chomp.strip
276
+ return nil if title.empty?
277
+
278
+ print "Post body (optional): "
279
+ body = gets.chomp.strip
280
+
281
+ draft_name = repo.create_draft(title: title, body: body)
282
+ post_num = repo.finish_draft(draft_name)
283
+
284
+ puts fx("Post ##{post_num} created successfully!", :green)
285
+ post_num
286
+ end
287
+
288
+ def self.edit_post(repo, view, post_id)
289
+ post = repo.get_post(post_id)
290
+ if post
291
+ post_file = post.file_path
292
+ Editor.edit_file(post_file)
293
+ else
294
+ puts fx("Post ##{post_id} not found.", :red)
295
+ end
296
+ end
297
+ end
298
+ end
299
+ end
300
+