scriptorium 0.0.3 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. checksums.yaml +4 -4
  2. data/README.lt3 +324 -0
  3. data/README.md +3155 -1
  4. data/assets/.DS_Store +0 -0
  5. data/assets/README.md +44 -0
  6. data/assets/icons/social/reddit.png +0 -0
  7. data/assets/icons/social/x-logo.png +0 -0
  8. data/assets/icons/ui/.DS_Store +0 -0
  9. data/assets/icons/ui/back.png +0 -0
  10. data/assets/icons/ui/copy.png +0 -0
  11. data/assets/icons/ui/down.png +0 -0
  12. data/assets/icons/ui/end.png +0 -0
  13. data/assets/icons/ui/exit.png +0 -0
  14. data/assets/icons/ui/foo +10 -0
  15. data/assets/icons/ui/home.png +0 -0
  16. data/assets/icons/ui/left.png +0 -0
  17. data/assets/icons/ui/next.png +0 -0
  18. data/assets/icons/ui/right.png +0 -0
  19. data/assets/icons/ui/start.png +0 -0
  20. data/assets/icons/ui/up.png +0 -0
  21. data/assets/imagenotfound.jpg +0 -0
  22. data/assets/samples/placeholder.svg +9 -0
  23. data/assets/themes/standard/favicon.svg +6 -0
  24. data/bin/sblog +84 -5
  25. data/bin/scriptorium +1 -0
  26. data/doc/README.txt +6 -0
  27. data/doc/anti-amnesia/20250727-054000-scriptorium-overview.md +94 -0
  28. data/doc/anti-amnesia/20250727-123000-anti-amnesia-conventions.md +2 -0
  29. data/doc/anti-amnesia/20250727-172600-cursor-rbenv-ruby-version-mystery.md +45 -0
  30. data/doc/anti-amnesia/20250727-172900-ai-cognitive-assessment-capabilities.md +40 -0
  31. data/doc/anti-amnesia/20250728-124243-aaa-syntax-clarification.md +46 -0
  32. data/doc/anti-amnesia/20250729-210000-reddit-autopost-integration-complete.md +158 -0
  33. data/doc/anti-amnesia/20250804-190500-cognitive-loop-bug.md +35 -0
  34. data/doc/anti-amnesia/20250804-190700-anti-amnesia-timestamping-fix.md +27 -0
  35. data/doc/anti-amnesia/20250807-213025.md +116 -0
  36. data/doc/anti-amnesia/20250901-211714-codemirror-integration-and-web-tests.md +172 -0
  37. data/doc/anti-amnesia/20250902-002402-backup-restore-system.md +126 -0
  38. data/doc/anti-amnesia/20250907-203339-backup-metadata-implementation.md +66 -0
  39. data/doc/banner_svg_config.md +114 -0
  40. data/doc/contrib.lt3 +8 -0
  41. data/doc/dependencies.md +281 -0
  42. data/doc/hacker.lt3 +5 -0
  43. data/doc/imported/0001-elixir-conf-2014/metadata.txt +7 -0
  44. data/doc/imported/0001-elixir-conf-2014/post.html +37 -0
  45. data/doc/imported/0001-elixir-conf-2014/source.lt3 +22 -0
  46. data/doc/imported/0002-programmers-and-word-processing/metadata.txt +7 -0
  47. data/doc/imported/0002-programmers-and-word-processing/post.html +192 -0
  48. data/doc/imported/0002-programmers-and-word-processing/source.lt3 +146 -0
  49. data/doc/imported/0003-how-to-turn-your-brain-sideways/metadata.txt +7 -0
  50. data/doc/imported/0003-how-to-turn-your-brain-sideways/post.html +60 -0
  51. data/doc/imported/0003-how-to-turn-your-brain-sideways/source.lt3 +40 -0
  52. data/doc/imported/0004-upcoming-lone-star-ruby-conference/metadata.txt +7 -0
  53. data/doc/imported/0004-upcoming-lone-star-ruby-conference/post.html +42 -0
  54. data/doc/imported/0004-upcoming-lone-star-ruby-conference/source.lt3 +24 -0
  55. data/doc/imported/0005-elixir-conf-2015-announced/metadata.txt +7 -0
  56. data/doc/imported/0005-elixir-conf-2015-announced/post.html +30 -0
  57. data/doc/imported/0005-elixir-conf-2015-announced/source.lt3 +16 -0
  58. data/doc/imported/0006-ruby-for-dinosaurs/metadata.txt +7 -0
  59. data/doc/imported/0006-ruby-for-dinosaurs/post.html +43 -0
  60. data/doc/imported/0006-ruby-for-dinosaurs/source.lt3 +27 -0
  61. data/doc/imported/0007-phoenix-isnt-rails/metadata.txt +7 -0
  62. data/doc/imported/0007-phoenix-isnt-rails/post.html +116 -0
  63. data/doc/imported/0007-phoenix-isnt-rails/source.lt3 +87 -0
  64. data/doc/imported/0008-concerning-the-term-monkeypatching/metadata.txt +7 -0
  65. data/doc/imported/0008-concerning-the-term-monkeypatching/post.html +129 -0
  66. data/doc/imported/0008-concerning-the-term-monkeypatching/source.lt3 +92 -0
  67. data/doc/imported/0009-announcement-coming-soon/metadata.txt +7 -0
  68. data/doc/imported/0009-announcement-coming-soon/post.html +33 -0
  69. data/doc/imported/0009-announcement-coming-soon/source.lt3 +19 -0
  70. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/metadata.txt +7 -0
  71. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/post.html +175 -0
  72. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/source.lt3 +139 -0
  73. data/doc/imported/0011-computer-science-as-a-lost-art/metadata.txt +7 -0
  74. data/doc/imported/0011-computer-science-as-a-lost-art/post.html +139 -0
  75. data/doc/imported/0011-computer-science-as-a-lost-art/source.lt3 +104 -0
  76. data/doc/imported/0012-ruby-day-in-turin-italy/metadata.txt +7 -0
  77. data/doc/imported/0012-ruby-day-in-turin-italy/post.html +42 -0
  78. data/doc/imported/0012-ruby-day-in-turin-italy/source.lt3 +24 -0
  79. data/doc/imported/0013-rubyday-was-a-success/metadata.txt +7 -0
  80. data/doc/imported/0013-rubyday-was-a-success/post.html +44 -0
  81. data/doc/imported/0013-rubyday-was-a-success/source.lt3 +27 -0
  82. data/doc/imported/0014-working-on-the-blogging-software/metadata.txt +7 -0
  83. data/doc/imported/0014-working-on-the-blogging-software/post.html +63 -0
  84. data/doc/imported/0014-working-on-the-blogging-software/source.lt3 +41 -0
  85. data/doc/imported/0015-ok-its-not-really-a-lost-art/metadata.txt +7 -0
  86. data/doc/imported/0015-ok-its-not-really-a-lost-art/post.html +172 -0
  87. data/doc/imported/0015-ok-its-not-really-a-lost-art/source.lt3 +134 -0
  88. data/doc/imported/0016-an-in-operator-for-ruby/metadata.txt +7 -0
  89. data/doc/imported/0016-an-in-operator-for-ruby/post.html +155 -0
  90. data/doc/imported/0016-an-in-operator-for-ruby/source.lt3 +106 -0
  91. data/doc/imported/0017-the-forgotten-mathematician/metadata.txt +7 -0
  92. data/doc/imported/0017-the-forgotten-mathematician/post.html +161 -0
  93. data/doc/imported/0017-the-forgotten-mathematician/source.lt3 +119 -0
  94. data/doc/imported/0018-ruby-puns/metadata.txt +7 -0
  95. data/doc/imported/0018-ruby-puns/post.html +46 -0
  96. data/doc/imported/0018-ruby-puns/source.lt3 +28 -0
  97. data/doc/imported/0019-custom-exceptions-via-metaprogramming/metadata.txt +7 -0
  98. data/doc/imported/0019-custom-exceptions-via-metaprogramming/post.html +138 -0
  99. data/doc/imported/0019-custom-exceptions-via-metaprogramming/source.lt3 +101 -0
  100. data/doc/imported/0020-fffff/metadata.txt +7 -0
  101. data/doc/imported/0020-fffff/post.html +24 -0
  102. data/doc/imported/0020-fffff/source.lt3 +12 -0
  103. data/doc/imported/0021-trying-ror-yet-again/metadata.txt +7 -0
  104. data/doc/imported/0021-trying-ror-yet-again/post.html +26 -0
  105. data/doc/imported/0021-trying-ror-yet-again/source.lt3 +12 -0
  106. data/doc/imported/0023-doctor-sleep/metadata.txt +7 -0
  107. data/doc/imported/0023-doctor-sleep/post.html +63 -0
  108. data/doc/imported/0023-doctor-sleep/source.lt3 +44 -0
  109. data/doc/imported/0024-just-a-test/metadata.txt +7 -0
  110. data/doc/imported/0024-just-a-test/post.html +24 -0
  111. data/doc/imported/0024-just-a-test/source.lt3 +12 -0
  112. data/doc/imported/import_summary.txt +98 -0
  113. data/doc/livetext-informal-spec.txt +65 -0
  114. data/doc/myuserdoc/ch-0.lt3 +31 -0
  115. data/doc/myuserdoc/ch-1.lt3 +37 -0
  116. data/doc/myuserdoc/ch-10.lt3 +22 -0
  117. data/doc/myuserdoc/ch-2.lt3 +37 -0
  118. data/doc/myuserdoc/ch-3.lt3 +19 -0
  119. data/doc/myuserdoc/ch-4.lt3 +43 -0
  120. data/doc/myuserdoc/ch-5.lt3 +22 -0
  121. data/doc/myuserdoc/ch-6.lt3 +19 -0
  122. data/doc/myuserdoc/ch-7.lt3 +16 -0
  123. data/doc/myuserdoc/ch-8.lt3 +13 -0
  124. data/doc/myuserdoc/ch-9.lt3 +19 -0
  125. data/doc/myuserdoc/tweak.rb +18 -0
  126. data/doc/myuserdoc/userdoc-toc.txt +88 -0
  127. data/doc/old-posts/0001-elixir-conf-2014.lt3 +24 -0
  128. data/doc/old-posts/0002-programmers-and-word-processing.lt3 +150 -0
  129. data/doc/old-posts/0003-how-to-turn-your-brain-sideways.lt3 +43 -0
  130. data/doc/old-posts/0004-upcoming-lone-star-ruby-conference.lt3 +26 -0
  131. data/doc/old-posts/0005-elixir-conf-2015-announced.lt3 +17 -0
  132. data/doc/old-posts/0006-ruby-for-dinosaurs.lt3 +30 -0
  133. data/doc/old-posts/0007-phoenix-isnt-rails.lt3 +90 -0
  134. data/doc/old-posts/0008-concerning-the-term-monkeypatching.lt3 +105 -0
  135. data/doc/old-posts/0009-announcement-coming-soon.lt3 +20 -0
  136. data/doc/old-posts/0010-immutable-data-ditching-the-wax-tablet.lt3 +142 -0
  137. data/doc/old-posts/0011-computer-science-as-a-lost-art.lt3 +117 -0
  138. data/doc/old-posts/0012-ruby-day-in-turin-italy.lt3 +26 -0
  139. data/doc/old-posts/0013-rubyday-was-a-success.lt3 +28 -0
  140. data/doc/old-posts/0014-working-on-the-blogging-software.lt3 +42 -0
  141. data/doc/old-posts/0015-ok-its-not-really-a-lost-art.lt3 +137 -0
  142. data/doc/old-posts/0016-an-in-operator-for-ruby.lt3 +142 -0
  143. data/doc/old-posts/0017-the-forgotten-mathematician.lt3 +129 -0
  144. data/doc/old-posts/0018-ruby-puns.lt3 +31 -0
  145. data/doc/old-posts/0019-custom-exceptions-via-metaprogramming.lt3 +116 -0
  146. data/doc/old-posts/0021-trying-ror-yet-again.lt3 +35 -0
  147. data/doc/old-posts/0023-doctor-sleep.lt3 +43 -0
  148. data/doc/old-posts/0024-just-a-test.lt3 +12 -0
  149. data/doc/old-posts/0025-trying-another-post.lt3 +12 -0
  150. data/doc/old-repo +1 -0
  151. data/doc/reddit_credentials_template.json +8 -0
  152. data/doc/reddit_integration.md +207 -0
  153. data/doc/user.lt3 +35 -0
  154. data/doc/user_guide_section_1.md +137 -0
  155. data/doc/user_guide_section_10.md +515 -0
  156. data/doc/user_guide_section_11.md +708 -0
  157. data/doc/user_guide_section_2.md +233 -0
  158. data/doc/user_guide_section_3.md +5 -0
  159. data/doc/user_guide_section_4.md +221 -0
  160. data/doc/user_guide_section_5.md +243 -0
  161. data/doc/user_guide_section_6.md +147 -0
  162. data/doc/user_guide_section_7.md +311 -0
  163. data/doc/user_guide_section_8.md +224 -0
  164. data/doc/user_guide_section_9.md +375 -0
  165. data/lib/rouge/lexers/livetext.rb +74 -0
  166. data/lib/scriptorium/api.rb +2373 -0
  167. data/lib/scriptorium/banner_svg.rb +729 -0
  168. data/lib/scriptorium/contract.rb +34 -0
  169. data/lib/scriptorium/exceptions.rb +201 -1
  170. data/lib/scriptorium/helpers.rb +675 -0
  171. data/lib/scriptorium/post.rb +259 -0
  172. data/lib/scriptorium/reddit.rb +83 -0
  173. data/lib/scriptorium/repo.rb +938 -0
  174. data/lib/scriptorium/standard_files.rb +149 -0
  175. data/lib/scriptorium/support/bootstrap/css.txt +5 -0
  176. data/lib/scriptorium/support/bootstrap/js.txt +4 -0
  177. data/lib/scriptorium/support/common_js/clipboard.js +35 -0
  178. data/lib/scriptorium/support/common_js/content-loader.js +187 -0
  179. data/lib/scriptorium/support/common_js/navigation.js +52 -0
  180. data/lib/scriptorium/support/common_js/syntax-highlighting.js +27 -0
  181. data/lib/scriptorium/support/config/reddit.txt +10 -0
  182. data/lib/scriptorium/support/config/reddit_template.txt +17 -0
  183. data/lib/scriptorium/support/config/social.txt +8 -0
  184. data/lib/scriptorium/support/highlight/css.txt +2 -0
  185. data/lib/scriptorium/support/highlight/custom.css +119 -0
  186. data/lib/scriptorium/support/highlight/js.txt +1 -0
  187. data/lib/scriptorium/support/post_index/config.txt +15 -0
  188. data/lib/scriptorium/support/post_index/style.css +55 -0
  189. data/lib/scriptorium/support/templates/index_entry.lt3 +16 -0
  190. data/lib/scriptorium/support/templates/initial_post.lt3 +12 -0
  191. data/lib/scriptorium/support/templates/layout.txt +5 -0
  192. data/lib/scriptorium/support/templates/post.lt3 +104 -0
  193. data/lib/scriptorium/support/theme/footer.lt3 +2 -0
  194. data/lib/scriptorium/support/theme/header.lt3 +4 -0
  195. data/lib/scriptorium/support/theme/left.lt3 +3 -0
  196. data/lib/scriptorium/support/theme/main.lt3 +5 -0
  197. data/lib/scriptorium/support/theme/right.lt3 +3 -0
  198. data/lib/scriptorium/theme.rb +192 -0
  199. data/lib/scriptorium/version.rb +1 -1
  200. data/lib/scriptorium/view.rb +1021 -0
  201. data/lib/scriptorium/widgets/featured_posts.rb +149 -0
  202. data/lib/scriptorium/widgets/links.rb +112 -0
  203. data/lib/scriptorium/widgets/pages.rb +133 -0
  204. data/lib/scriptorium/widgets/widget.rb +133 -0
  205. data/lib/scriptorium.rb +38 -34
  206. data/lib/skeleton.rb +10 -1
  207. data/scriptorium.gemspec +17 -5
  208. data/test/README.md +69 -0
  209. data/test/WEB_INTEGRATION_README.md +196 -0
  210. data/test/all +83 -0
  211. data/test/api_demo.rb +99 -0
  212. data/test/assets/imagenotfound.jpg +0 -0
  213. data/test/assets/images/.DS_Store +0 -0
  214. data/test/assets/images/README.md +27 -0
  215. data/test/assets/images/odd_aspect.png +0 -0
  216. data/test/assets/images/perfect.png +0 -0
  217. data/test/assets/images/small.png +0 -0
  218. data/test/assets/images/tall.png +0 -0
  219. data/test/assets/images/very_tall.png +0 -0
  220. data/test/assets/images/very_wide.png +0 -0
  221. data/test/assets/images/wide.png +0 -0
  222. data/test/assets/testbanner.jpg +0 -0
  223. data/test/banner_svg/simple_helpers.rb +13 -0
  224. data/test/banner_svg/unit.rb +1000 -0
  225. data/test/config/deployment.txt +5 -0
  226. data/test/ed_test.rb +204 -0
  227. data/test/integration/cursor_banner_combinations.rb +193 -0
  228. data/test/integration/cursor_banner_features.rb +374 -0
  229. data/test/integration/integration_test.rb +326 -0
  230. data/test/integration/preview_flow_test.rb +94 -0
  231. data/test/livetext_plugin_test.rb +500 -0
  232. data/test/manual/asset_mgmt.rb +67 -0
  233. data/test/manual/banner-tests/index.html +45 -0
  234. data/test/manual/banner-tests/svg.txt +3 -0
  235. data/test/manual/banner-tests/test01.html +122 -0
  236. data/test/manual/banner-tests/test02.html +122 -0
  237. data/test/manual/banner-tests/test03.html +122 -0
  238. data/test/manual/banner-tests/test04.html +129 -0
  239. data/test/manual/banner-tests/test05.html +129 -0
  240. data/test/manual/banner-tests/test06.html +129 -0
  241. data/test/manual/banner-tests/test07.html +129 -0
  242. data/test/manual/banner-tests/test08.html +123 -0
  243. data/test/manual/banner-tests/test09.html +123 -0
  244. data/test/manual/banner-tests/test10.html +123 -0
  245. data/test/manual/banner-tests/test11.html +123 -0
  246. data/test/manual/banner-tests/test12.html +123 -0
  247. data/test/manual/banner-tests/test13.html +123 -0
  248. data/test/manual/banner-tests/test14.html +123 -0
  249. data/test/manual/banner-tests/test15.html +122 -0
  250. data/test/manual/banner-tests/test16.html +122 -0
  251. data/test/manual/banner-tests/test17.html +122 -0
  252. data/test/manual/banner-tests/test18.html +132 -0
  253. data/test/manual/banner-tests/test19.html +132 -0
  254. data/test/manual/banner-tests/test20.html +132 -0
  255. data/test/manual/banner-tests/test21.html +132 -0
  256. data/test/manual/banner-tests/test22.html +132 -0
  257. data/test/manual/banner-tests/test23.html +132 -0
  258. data/test/manual/banner-tests/test24.html +132 -0
  259. data/test/manual/banner-tests/test25.html +131 -0
  260. data/test/manual/banner_environment.rb +205 -0
  261. data/test/manual/codemirror_demo.html +773 -0
  262. data/test/manual/create_posts_for_web.rb +114 -0
  263. data/test/manual/environment.rb +67 -0
  264. data/test/manual/make_banner.rb +153 -0
  265. data/test/manual/preview_manual_test.rb +129 -0
  266. data/test/manual/sample_banner_config.txt +12 -0
  267. data/test/manual/test_advanced_widgets.rb +73 -0
  268. data/test/manual/test_banner_combinations.rb +120 -0
  269. data/test/manual/test_banner_features.rb +306 -0
  270. data/test/manual/test_banner_integration.rb +115 -0
  271. data/test/manual/test_banner_radial.rb +87 -0
  272. data/test/manual/test_basic_posts.rb +47 -0
  273. data/test/manual/test_layout_widgets.rb +40 -0
  274. data/test/manual/test_pagination.rb +24 -0
  275. data/test/manual/test_random_posts.rb +38 -0
  276. data/test/manual/test_syntax_highlighting.rb +167 -0
  277. data/test/rubytext/rubytext_comprehensive_test.rb +307 -0
  278. data/test/rubytext/rubytext_demo_test.rb +42 -0
  279. data/test/rubytext/rubytext_testing_guide.md +277 -0
  280. data/test/run_automated_tests.rb +45 -0
  281. data/test/staging/.DS_Store +0 -0
  282. data/test/support/preview_utils.rb +88 -0
  283. data/test/syntax_highlighting_test.lt3 +124 -0
  284. data/test/test_gem_assets.rb +48 -0
  285. data/test/test_helpers.rb +240 -0
  286. data/test/tui_editor_integration_test.rb +296 -0
  287. data/test/tui_integration_test.rb +883 -0
  288. data/test/unit/api.rb +1776 -0
  289. data/test/unit/asset_management.rb +219 -0
  290. data/test/unit/backup_test.rb +451 -0
  291. data/test/unit/clipboard_test.rb +60 -0
  292. data/test/unit/contract_test.rb +69 -0
  293. data/test/unit/core.rb +1211 -0
  294. data/test/unit/deploy_config_test.rb +248 -0
  295. data/test/unit/deploy_test.rb +478 -0
  296. data/test/unit/edit_post_test.rb +168 -0
  297. data/test/unit/gem_asset_management.rb +183 -0
  298. data/test/unit/livetext_basic.rb +57 -0
  299. data/test/unit/livetext_compatibility.rb +82 -0
  300. data/test/unit/parse_cmd_test.rb +260 -0
  301. data/test/unit/permalink_copy_test.rb +211 -0
  302. data/test/unit/post.rb +309 -0
  303. data/test/unit/post_index_config_test.rb +258 -0
  304. data/test/unit/post_state_helpers_test.rb +137 -0
  305. data/test/unit/read_commented_file_test.rb +278 -0
  306. data/test/unit/reddit_test.rb +235 -0
  307. data/test/unit/repo.rb +569 -0
  308. data/test/unit/social_test.rb +366 -0
  309. data/test/unit/syntax_highlighting.rb +70 -0
  310. data/test/unit/theme_management_test.rb +91 -0
  311. data/test/unit/view.rb +498 -0
  312. data/test/unit/widgets.rb +669 -0
  313. data/test/web_integration_test.rb +231 -0
  314. data/test/web_test_helper.rb +218 -0
  315. data/test/web_workflow_test.rb +527 -0
  316. data/test/wizard_test.rb +123 -0
  317. data/ui/README.md +67 -0
  318. data/ui/common/lib/ui_common.rb +8 -0
  319. data/ui/rubytext/README.md +191 -0
  320. data/ui/rubytext/bin/scriptorium-rubytext +402 -0
  321. data/ui/rubytext/lib/rubytext_ui.rb +300 -0
  322. data/ui/tui/bin/scriptorium +1890 -0
  323. data/ui/tui/test/tui_test.rb +23 -0
  324. data/ui/web/app/app.rb +2600 -0
  325. data/ui/web/app/assets/livetext_mode.js +244 -0
  326. data/ui/web/app/error_helpers.rb +150 -0
  327. data/ui/web/app/views/advanced_config.erb +196 -0
  328. data/ui/web/app/views/asset_management.erb +645 -0
  329. data/ui/web/app/views/backup_management.erb +238 -0
  330. data/ui/web/app/views/banner_config.erb +200 -0
  331. data/ui/web/app/views/config_widget.erb +232 -0
  332. data/ui/web/app/views/configure_view.erb +401 -0
  333. data/ui/web/app/views/dashboard.erb +154 -0
  334. data/ui/web/app/views/deploy_config.erb +149 -0
  335. data/ui/web/app/views/edit_pages.erb +363 -0
  336. data/ui/web/app/views/edit_post.erb +175 -0
  337. data/ui/web/app/views/edit_theme.erb +73 -0
  338. data/ui/web/app/views/edit_theme_file.erb +74 -0
  339. data/ui/web/app/views/error_page.erb +29 -0
  340. data/ui/web/app/views/header_config.erb +155 -0
  341. data/ui/web/app/views/layout_config.erb +147 -0
  342. data/ui/web/app/views/navbar_config.erb +411 -0
  343. data/ui/web/app/views/theme_management.erb +130 -0
  344. data/ui/web/app/views/view_dashboard.erb +779 -0
  345. data/ui/web/app/views/widgets.erb +249 -0
  346. data/ui/web/bin/scriptorium-web +164 -0
  347. data/ui/web/test/web_basic_test.rb +38 -0
  348. data/ui/web/test_navbar.txt +7 -0
  349. data/ui/web/tmp/timing.log +17 -0
  350. data/ui/web/tmp/web_server.log +0 -0
  351. metadata +434 -8
  352. data/lib/scriptorium/engine.rb +0 -22
  353. data/test/engine/unit.rb +0 -44
@@ -0,0 +1,183 @@
1
+ # Test file for gem asset management functionality
2
+ # Tests asset management when Scriptorium is installed as a gem
3
+
4
+ require 'minitest/autorun'
5
+ require_relative '../../lib/scriptorium'
6
+ require_relative '../test_helpers'
7
+ require_relative '../../lt3scriptor'
8
+ require 'fileutils'
9
+
10
+ class TestGemAssetManagement < Minitest::Test
11
+ include Scriptorium::Helpers
12
+ include TestHelpers
13
+
14
+ def setup
15
+ @test_dir = "test/gem_asset_management_test"
16
+ FileUtils.rm_rf(@test_dir) if File.exist?(@test_dir)
17
+ FileUtils.mkdir_p(@test_dir)
18
+ Scriptorium::Repo.testing = true
19
+
20
+ # Create test repository with unique view name
21
+ @repo = create_test_repo
22
+ @repo.create_view("gem_asset_test_view", "Testing gem assets")
23
+
24
+ # Set up LiveText variables for testing
25
+ setup_livetext_vars
26
+ end
27
+
28
+ def teardown
29
+ FileUtils.rm_rf(@test_dir) if File.exist?(@test_dir)
30
+ # Also clean up the test repository
31
+ FileUtils.rm_rf("test/scriptorium-TEST") if File.exist?("test/scriptorium-TEST")
32
+ end
33
+
34
+ def setup_livetext_vars
35
+ # Set up LiveText variables for testing
36
+ @vars = { View: "gem_asset_test_view", "post.id": "0001" }
37
+ end
38
+
39
+ def process_livetext(content)
40
+ # Create a temporary file with the content
41
+ temp_file = "#{@test_dir}/temp_livetext.lt3"
42
+ write_file(temp_file, content)
43
+
44
+ # Process with Livetext using the plugin
45
+ begin
46
+ live = Livetext.customize(mix: "lt3scriptor", call: ".nopara", vars: @vars)
47
+ body, vars = live.process(file: temp_file)
48
+ vars = vars.to_h
49
+ # Strip paragraph tags for asset function tests
50
+ body = body.gsub(/^<p>\n?(.*?)\n?<\/p>$/m, '\1')
51
+ return { vars: vars, body: body }
52
+ rescue => e
53
+ puts "Livetext error: #{e.message}"
54
+ puts "Backtrace: #{e.backtrace.first}"
55
+ return { error: e.message, backtrace: e.backtrace.first }
56
+ ensure
57
+ # Clean up
58
+ File.delete(temp_file) if File.exist?(temp_file)
59
+ end
60
+ end
61
+
62
+ def test_001_gem_asset_discovery_in_development
63
+ # Test that gem assets are found in development environment
64
+ content = "$$asset[icons/ui/back.png]"
65
+ result = process_livetext(content)
66
+
67
+ # Should find gem asset and return correct path
68
+ assert_equal "assets/icons/ui/back.png", result[:body].strip
69
+ end
70
+
71
+ def test_002_gem_asset_discovery_in_production
72
+ # Test that gem assets are found in production environment
73
+ # This would require simulating a gem installation
74
+ skip "Requires gem installation simulation"
75
+ end
76
+
77
+ def test_003_gem_assets_copied_to_theme_during_repo_creation
78
+ # Test that theme-specific gem assets are copied during repo creation
79
+ theme_assets_dir = @repo.root/"themes"/"standard"/"assets"
80
+
81
+ # Check that theme assets were copied
82
+ assert Dir.exist?(theme_assets_dir), "Theme assets directory should exist"
83
+
84
+ # Check for specific theme assets
85
+ expected_theme_assets = [
86
+ "standard/favicon.svg",
87
+ "icons/ui/back.png"
88
+ ]
89
+
90
+ expected_theme_assets.each do |asset|
91
+ asset_path = theme_assets_dir/asset
92
+ assert File.exist?(asset_path), "Theme asset #{asset} should be copied"
93
+ end
94
+ end
95
+
96
+ def test_004_gem_assets_copied_to_library_during_repo_creation
97
+ # Test that application-wide gem assets are copied to library
98
+ library_dir = @repo.root/"assets"/"library"
99
+
100
+ # Check that library directory exists
101
+ assert Dir.exist?(library_dir), "Library directory should exist"
102
+
103
+ # Check for specific library assets
104
+ expected_library_assets = [
105
+ "placeholder.svg"
106
+ ]
107
+
108
+ expected_library_assets.each do |asset|
109
+ asset_path = library_dir/asset
110
+ assert File.exist?(asset_path), "Library asset #{asset} should be copied"
111
+ end
112
+ end
113
+
114
+ def test_005_gem_assets_in_search_hierarchy
115
+ # Test that gem assets are found in the correct priority order
116
+ content = "$$asset[icons/ui/back.png]"
117
+ result = process_livetext(content)
118
+
119
+ # Should find gem asset and return correct path
120
+ assert_equal "assets/icons/ui/back.png", result[:body].strip
121
+ end
122
+
123
+ def test_006_gem_assets_override_by_user_assets
124
+ # Test that user assets override gem assets
125
+ # Create a user asset with the same name as a gem asset
126
+ user_asset_path = @repo.root/"assets"/"icons"/"ui"/"back.png"
127
+ FileUtils.mkdir_p(File.dirname(user_asset_path))
128
+ write_file(user_asset_path, "User asset content")
129
+
130
+ content = "$$asset[icons/ui/back.png]"
131
+ result = process_livetext(content)
132
+
133
+ # Should find user asset first (higher priority)
134
+ assert_equal "assets/icons/ui/back.png", result[:body].strip
135
+ end
136
+
137
+ def test_007_gem_asset_path_resolution
138
+ # Test that gem asset paths are resolved correctly
139
+ gem_spec = Gem.loaded_specs['scriptorium']
140
+ if gem_spec
141
+ # Production environment - gem is installed
142
+ gem_assets_dir = "#{gem_spec.full_gem_path}/assets"
143
+ assert Dir.exist?(gem_assets_dir), "Gem assets directory should exist"
144
+
145
+ # Check for specific gem assets
146
+ expected_gem_assets = [
147
+ "icons/ui/back.png",
148
+ "samples/placeholder.svg",
149
+ "themes/standard/favicon.svg"
150
+ ]
151
+
152
+ expected_gem_assets.each do |asset|
153
+ asset_path = "#{gem_assets_dir}/#{asset}"
154
+ assert File.exist?(asset_path), "Gem asset #{asset} should exist"
155
+ end
156
+ else
157
+ # Development environment - use working path
158
+ dev_assets_dir = File.expand_path("assets")
159
+ assert Dir.exist?(dev_assets_dir), "Development assets directory should exist"
160
+ end
161
+ rescue => e
162
+ # If gem lookup fails, that's expected in development
163
+ dev_assets_dir = File.expand_path("assets")
164
+ assert Dir.exist?(dev_assets_dir), "Development assets directory should exist"
165
+ end
166
+
167
+ def test_008_gem_assets_not_copied_to_output
168
+ # Test that gem assets are not copied to output directory during generation
169
+ # (they should only be referenced)
170
+ output_assets_dir = @repo.root/"views"/"gem_asset_test_view"/"output"/"assets"
171
+
172
+ # Generate a post that references gem assets
173
+ draft_body = "$$asset[icons/ui/back.png]"
174
+ name = @repo.create_draft(title: "Gem Asset Test", views: ["gem_asset_test_view"], body: draft_body)
175
+ num = @repo.finish_draft(name)
176
+ @repo.generate_post(num)
177
+
178
+ # Check that gem assets are not copied to output
179
+ gem_asset_in_output = output_assets_dir/"icons"/"ui"/"back.png"
180
+ refute File.exist?(gem_asset_in_output), "Gem assets should not be copied to output"
181
+ end
182
+
183
+ end
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'minitest/autorun'
4
+ require 'livetext'
5
+
6
+ class TestLivetextBasic < Minitest::Test
7
+
8
+ def test_001_basic_livetext_processing
9
+ # Test basic Livetext without any plugins
10
+ content = "Simple text content"
11
+
12
+ live = Livetext.customize(call: ".nopara")
13
+ body, vars = live.process(text: content)
14
+ assert_equal "<p>\nSimple text content\n</p>", body.strip
15
+ assert vars.is_a?(Hash)
16
+ rescue => e
17
+ flunk "Basic Livetext processing failed: #{e.message}"
18
+ end
19
+
20
+ def test_002_livetext_with_simple_dot_commands
21
+ # Test Livetext with simple dot commands
22
+ content = <<~CONTENT
23
+ .title Test Post
24
+ .views sample
25
+
26
+ This is the body.
27
+ CONTENT
28
+
29
+ live = Livetext.customize(mix: "lt3scriptor", call: ".nopara")
30
+ body, vars = live.process(text: content)
31
+ assert_includes body, "This is the body"
32
+ assert vars.is_a?(Hash)
33
+ rescue => e
34
+ flunk "Livetext with dot commands failed: #{e.message}"
35
+ end
36
+
37
+ def test_003_livetext_plugin_loading
38
+ # Test if the plugin can be loaded without processing
39
+ live = Livetext.customize(mix: "lt3scriptor")
40
+ assert live
41
+ rescue => e
42
+ flunk "Plugin loading failed: #{e.message}"
43
+ end
44
+
45
+ def test_004_livetext_plugin_processing_simple
46
+ # Test if the plugin can process very simple content
47
+ content = "Simple content"
48
+
49
+ live = Livetext.customize(mix: "lt3scriptor", call: ".nopara")
50
+ body, vars = live.process(text: content)
51
+ assert body.is_a?(String)
52
+ rescue => e
53
+ puts "✗ Plugin processing failed: #{e.message}"
54
+ puts e.backtrace.first(3)
55
+ skip "Plugin processing needs to be fixed"
56
+ end
57
+ end
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'minitest/autorun'
4
+ require 'livetext'
5
+
6
+ class TestLivetextCompatibility < Minitest::Test
7
+
8
+ def test_001_basic_livetext_processing
9
+ # Test basic Livetext without any plugins
10
+ content = "Simple text content"
11
+
12
+ begin
13
+ live = Livetext.customize(call: ".nopara")
14
+ body, vars = live.process(text: content)
15
+ vars = vars.to_h
16
+ assert_equal "<p>\nSimple text content\n</p>", body.strip
17
+ assert vars.is_a?(Hash)
18
+ rescue => e
19
+ flunk "Basic Livetext processing failed: #{e.message}"
20
+ end
21
+ end
22
+
23
+ def test_002_livetext_with_simple_dot_commands
24
+ # Test Livetext with simple dot commands
25
+ content = <<~CONTENT
26
+ .title Test Post
27
+ .views sample
28
+
29
+ This is the body.
30
+ CONTENT
31
+
32
+ begin
33
+ live = Livetext.customize(mix: "lt3scriptor", call: ".nopara")
34
+ body, vars = live.process(text: content)
35
+ vars = vars.to_h
36
+ assert_includes body, "This is the body"
37
+ assert vars.is_a?(Hash)
38
+ rescue => e
39
+ flunk "Livetext with dot commands failed: #{e.message}"
40
+ end
41
+ end
42
+
43
+ def test_003_livetext_plugin_loading
44
+ # Test if the plugin can be loaded without processing
45
+ live = Livetext.customize(mix: "lt3scriptor")
46
+ assert live
47
+ rescue => e
48
+ flunk "Plugin loading failed: #{e.message}"
49
+ end
50
+
51
+ def test_004_livetext_plugin_processing
52
+ # Test if the plugin can process simple content
53
+ content = "Simple content with $$asset[test.png]"
54
+
55
+ begin
56
+ live = Livetext.customize(mix: "lt3scriptor", call: ".nopara")
57
+ body, vars = live.process(text: content)
58
+ vars = vars.to_h
59
+ assert body.is_a?(String)
60
+ rescue => e
61
+ puts "✗ Plugin processing failed: #{e.message}"
62
+ puts e.backtrace.first(3)
63
+ skip "Plugin processing needs to be fixed"
64
+ end
65
+ end
66
+
67
+ def test_005_livetext_old_api
68
+ # Test the old Livetext API
69
+ content = "Simple content"
70
+
71
+ begin
72
+ live = Livetext.customize(call: ".nopara")
73
+ text, vars = live.process(text: content)
74
+ vars = vars.to_h
75
+ assert text.is_a?(String)
76
+ assert vars.is_a?(Hash)
77
+ rescue => e
78
+ puts "✗ Old Livetext API failed: #{e.message}"
79
+ skip "Old API needs to be fixed"
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,260 @@
1
+ require 'minitest/autorun'
2
+ require_relative '../../lib/scriptorium'
3
+ require_relative '../test_helpers'
4
+
5
+ class ParseCmdTest < Minitest::Test
6
+ # Extract the parse_cmd method from the TUI class for testing
7
+ private def parse_cmd(cmdstr)
8
+ parts = cmdstr.split
9
+ return [:unknown_command, ""] if parts.empty?
10
+
11
+ # Handle 2-word commands and single-word commands with arguments
12
+ if parts.length >= 2
13
+ # Check if first two words form a known 2-word command
14
+ two_word_cmd = parts[0..1].join(" ")
15
+ case two_word_cmd.downcase
16
+ when "list views", "list posts", "list drafts", "list assets", "list widgets", "list themes", "list backups",
17
+ "change view", "new view", "new post", "upload asset", "copy asset", "delete asset",
18
+ "delete theme", "delete backup", "asset info", "configure deployment", "add widget", "config widget",
19
+ "config social", "config reddit", "clone theme"
20
+ cmd = two_word_cmd
21
+ args = parts[2..-1]
22
+ else
23
+ # Check if first word is a single-word command that can take parameters
24
+ first_word = parts[0].downcase
25
+ case first_word
26
+ when "cv", "backup", "restore" # Single-word commands that take parameters
27
+ # First word can take parameters, rest are arguments
28
+ cmd = first_word
29
+ args = parts[1..-1]
30
+ else
31
+ # Unknown command - treat as unknown 2-word command
32
+ cmd = two_word_cmd
33
+ args = parts[2..-1]
34
+ end
35
+ end
36
+ else
37
+ cmd = parts[0]
38
+ args = []
39
+ end
40
+
41
+ cmd = cmd.downcase
42
+
43
+ case cmd
44
+ when "help", "h"
45
+ [:show_help]
46
+ when "view"
47
+ [:show_current_view]
48
+ when "cv"
49
+ [:change_view, args]
50
+ when "lsv"
51
+ [:list_views]
52
+ when "lsp"
53
+ [:list_posts]
54
+ when "lsd"
55
+ [:list_drafts]
56
+ when "version", "v"
57
+ [:show_version]
58
+ when "deploy"
59
+ [:deploy_current_view]
60
+ when "preview"
61
+ [:preview_current_view]
62
+ when "browse"
63
+ [:browse_deployed_view]
64
+ when "generate"
65
+ [:generate_current_view]
66
+ when "quit", "q"
67
+ [:exit, 0]
68
+ when "list views"
69
+ [:list_views]
70
+ when "list posts"
71
+ [:list_posts]
72
+ when "list drafts"
73
+ [:list_drafts]
74
+ when "list assets"
75
+ [:list_assets, args]
76
+ when "list widgets"
77
+ [:list_widgets]
78
+ when "list themes"
79
+ [:list_themes]
80
+ when "list backups"
81
+ [:list_backups]
82
+ when "change view"
83
+ [:change_view, args]
84
+ when "new view"
85
+ args.empty? ? [:create_view] : [:create_view, args]
86
+ when "new post"
87
+ [:create_post, args]
88
+ when "upload asset"
89
+ [:upload_asset, args]
90
+ when "copy asset"
91
+ [:copy_asset, args]
92
+ when "delete asset"
93
+ [:delete_asset, args]
94
+ when "delete theme"
95
+ [:delete_theme, args]
96
+ when "delete backup"
97
+ [:delete_backup, args]
98
+ when "asset info"
99
+ [:asset_info, args]
100
+ when "configure deployment"
101
+ [:configure_deployment, args]
102
+ when "add widget"
103
+ [:add_widget, args]
104
+ when "config widget"
105
+ [:config_widget, args]
106
+ when "config social"
107
+ [:config_social]
108
+ when "config reddit"
109
+ [:config_reddit]
110
+ when "clone theme"
111
+ [:clone_theme, args]
112
+ when "backup"
113
+ [:create_backup, args]
114
+ when "restore"
115
+ [:restore_backup, args]
116
+ else
117
+ [:unknown_command, cmd]
118
+ end
119
+ end
120
+
121
+ def test_001_parse_cmd_basic_commands
122
+ # Test basic single-word commands
123
+ assert_equal [:show_help], parse_cmd("help")
124
+ assert_equal [:show_help], parse_cmd("h")
125
+ assert_equal [:show_current_view], parse_cmd("view")
126
+ assert_equal [:list_views], parse_cmd("lsv")
127
+ assert_equal [:list_posts], parse_cmd("lsp")
128
+ assert_equal [:list_drafts], parse_cmd("lsd")
129
+ assert_equal [:show_version], parse_cmd("version")
130
+ assert_equal [:show_version], parse_cmd("v")
131
+ assert_equal [:deploy_current_view], parse_cmd("deploy")
132
+ assert_equal [:preview_current_view], parse_cmd("preview")
133
+ assert_equal [:browse_deployed_view], parse_cmd("browse")
134
+ assert_equal [:generate_current_view], parse_cmd("generate")
135
+ assert_equal [:exit, 0], parse_cmd("quit")
136
+ assert_equal [:exit, 0], parse_cmd("q")
137
+ end
138
+
139
+ def test_002_parse_cmd_list_commands
140
+ # Test list commands
141
+ assert_equal [:list_views], parse_cmd("list views")
142
+ assert_equal [:list_posts], parse_cmd("list posts")
143
+ assert_equal [:list_drafts], parse_cmd("list drafts")
144
+ assert_equal [:list_assets, ["assets", "global"]], parse_cmd("list assets assets global")
145
+ assert_equal [:list_widgets], parse_cmd("list widgets")
146
+ assert_equal [:list_themes], parse_cmd("list themes")
147
+ assert_equal [:list_backups], parse_cmd("list backups")
148
+ end
149
+
150
+ def test_003_parse_cmd_change_commands
151
+ # Test change commands
152
+ assert_equal [:change_view, ["myview"]], parse_cmd("change view myview")
153
+ end
154
+
155
+ def test_004_parse_cmd_new_commands
156
+ # Test new commands
157
+ assert_equal [:create_view], parse_cmd("new view")
158
+ assert_equal [:create_view, ["myview"]], parse_cmd("new view myview")
159
+ assert_equal [:create_post, ["mypost"]], parse_cmd("new post mypost")
160
+ end
161
+
162
+ def test_005_parse_cmd_asset_commands
163
+ # Test asset commands
164
+ assert_equal [:upload_asset, ["myfile", "global"]], parse_cmd("upload asset myfile global")
165
+ assert_equal [:copy_asset, ["myfile", "from", "to"]], parse_cmd("copy asset myfile from to")
166
+ assert_equal [:delete_asset, ["myfile", "global"]], parse_cmd("delete asset myfile global")
167
+ assert_equal [:asset_info, ["myfile", "global"]], parse_cmd("asset info myfile global")
168
+ end
169
+
170
+ def test_006_parse_cmd_configure_commands
171
+ # Test configure commands
172
+ assert_equal [:configure_deployment, ["myview"]], parse_cmd("configure deployment myview")
173
+ end
174
+
175
+ def test_007_parse_cmd_widget_commands
176
+ # Test widget commands
177
+ assert_equal [:add_widget, ["mywidget"]], parse_cmd("add widget mywidget")
178
+ assert_equal [:config_widget, ["mywidget"]], parse_cmd("config widget mywidget")
179
+ end
180
+
181
+ def test_008_parse_cmd_config_commands
182
+ # Test config commands
183
+ assert_equal [:config_social], parse_cmd("config social")
184
+ assert_equal [:config_reddit], parse_cmd("config reddit")
185
+ end
186
+
187
+ def test_009_parse_cmd_clone_commands
188
+ # Test clone commands
189
+ assert_equal [:clone_theme, ["mytheme"]], parse_cmd("clone theme mytheme")
190
+ end
191
+
192
+ def test_010_parse_cmd_theme_commands
193
+ # Test theme commands
194
+ assert_equal [:delete_theme, ["mytheme"]], parse_cmd("delete theme mytheme")
195
+ end
196
+
197
+ def test_015_parse_cmd_backup_commands
198
+ # Test backup commands
199
+ assert_equal [:create_backup, []], parse_cmd("backup")
200
+ assert_equal [:create_backup, ["full"]], parse_cmd("backup full")
201
+ assert_equal [:create_backup, ["incr"]], parse_cmd("backup incr")
202
+ assert_equal [:create_backup, ["full", "Before", "major", "changes"]], parse_cmd("backup full Before major changes")
203
+ assert_equal [:create_backup, ["incr", "Added", "new", "post"]], parse_cmd("backup incr Added new post")
204
+ end
205
+
206
+ def test_016_parse_cmd_restore_commands
207
+ # Test restore commands
208
+ assert_equal [:restore_backup, []], parse_cmd("restore")
209
+ assert_equal [:restore_backup, ["20250902-120000-full"]], parse_cmd("restore 20250902-120000-full")
210
+ assert_equal [:restore_backup, ["20250902-120000-full", "safe"]], parse_cmd("restore 20250902-120000-full safe")
211
+ assert_equal [:restore_backup, ["20250902-130000-incr", "merge"]], parse_cmd("restore 20250902-130000-incr merge")
212
+ assert_equal [:restore_backup, ["20250902-140000-incr", "destroy"]], parse_cmd("restore 20250902-140000-incr destroy")
213
+ end
214
+
215
+ def test_017_parse_cmd_delete_backup_commands
216
+ # Test delete backup commands
217
+ assert_equal [:delete_backup, []], parse_cmd("delete backup")
218
+ assert_equal [:delete_backup, ["20250902-120000-full"]], parse_cmd("delete backup 20250902-120000-full")
219
+ assert_equal [:delete_backup, ["20250902-130000-incr"]], parse_cmd("delete backup 20250902-130000-incr")
220
+ end
221
+
222
+ def test_011_parse_cmd_edge_cases
223
+ # Test edge cases
224
+ assert_equal [:unknown_command, "invalid"], parse_cmd("invalid")
225
+ assert_equal [:unknown_command, ""], parse_cmd("")
226
+ assert_equal [:unknown_command, ""], parse_cmd(" ") # split() removes whitespace
227
+ assert_equal [:show_help], parse_cmd("help")
228
+ assert_equal [:show_help], parse_cmd(" help ")
229
+ end
230
+
231
+ def test_012_parse_cmd_complex_args
232
+ # Test commands with complex arguments
233
+ assert_equal [:change_view, ["my", "view", "with", "spaces"]], parse_cmd("change view my view with spaces")
234
+ assert_equal [:create_view, ["my", "view", "with", "spaces"]], parse_cmd("new view my view with spaces")
235
+ assert_equal [:list_assets, ["global", "with", "spaces"]], parse_cmd("list assets global with spaces")
236
+ end
237
+
238
+ def test_013_parse_cmd_abbreviated_commands
239
+ # Test abbreviated commands
240
+ assert_equal [:change_view, ["myview"]], parse_cmd("cv myview")
241
+ assert_equal [:list_views], parse_cmd("lsv")
242
+ assert_equal [:list_posts], parse_cmd("lsp")
243
+ assert_equal [:list_drafts], parse_cmd("lsd")
244
+ end
245
+
246
+ def test_014_parse_cmd_unknown_commands
247
+ # Test unknown commands
248
+ assert_equal [:unknown_command, "list invalid"], parse_cmd("list invalid")
249
+ assert_equal [:unknown_command, "change invalid"], parse_cmd("change invalid")
250
+ assert_equal [:unknown_command, "new invalid"], parse_cmd("new invalid")
251
+ assert_equal [:unknown_command, "upload invalid"], parse_cmd("upload invalid")
252
+ assert_equal [:unknown_command, "copy invalid"], parse_cmd("copy invalid")
253
+ assert_equal [:unknown_command, "delete invalid"], parse_cmd("delete invalid")
254
+ assert_equal [:unknown_command, "asset invalid"], parse_cmd("asset invalid")
255
+ assert_equal [:unknown_command, "configure invalid"], parse_cmd("configure invalid")
256
+ assert_equal [:unknown_command, "add invalid"], parse_cmd("add invalid")
257
+ assert_equal [:unknown_command, "config invalid"], parse_cmd("config invalid")
258
+ assert_equal [:unknown_command, "clone invalid"], parse_cmd("clone invalid")
259
+ end
260
+ end