scriptorium 0.6.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/assets/icons/social/reddit.png +0 -0
- data/assets/icons/social/x-logo.png +0 -0
- data/assets/imagenotfound.jpg +0 -0
- data/bin/sblog +84 -5
- data/bin/scriptorium +1 -0
- data/doc/anti-amnesia/20250727-054000-scriptorium-overview.md +0 -1
- data/doc/anti-amnesia/20250727-123000-anti-amnesia-conventions.md +0 -29
- data/doc/anti-amnesia/20250727-172600-cursor-rbenv-ruby-version-mystery.md +0 -19
- data/doc/anti-amnesia/20250727-172900-ai-cognitive-assessment-capabilities.md +1 -1
- data/doc/anti-amnesia/20250728-124243-aaa-syntax-clarification.md +1 -1
- data/doc/anti-amnesia/20250729-210000-reddit-autopost-integration-complete.md +1 -1
- data/doc/anti-amnesia/20250804-190500-cognitive-loop-bug.md +0 -10
- data/doc/anti-amnesia/20250804-190700-anti-amnesia-timestamping-fix.md +1 -4
- data/doc/anti-amnesia/20250901-211714-codemirror-integration-and-web-tests.md +172 -0
- data/doc/anti-amnesia/20250902-002402-backup-restore-system.md +126 -0
- data/doc/anti-amnesia/20250907-203339-backup-metadata-implementation.md +66 -0
- data/doc/imported/0001-elixir-conf-2014/metadata.txt +7 -0
- data/doc/imported/0001-elixir-conf-2014/post.html +37 -0
- data/doc/imported/0001-elixir-conf-2014/source.lt3 +22 -0
- data/doc/imported/0002-programmers-and-word-processing/metadata.txt +7 -0
- data/doc/imported/0002-programmers-and-word-processing/post.html +192 -0
- data/doc/imported/0002-programmers-and-word-processing/source.lt3 +146 -0
- data/doc/imported/0003-how-to-turn-your-brain-sideways/metadata.txt +7 -0
- data/doc/imported/0003-how-to-turn-your-brain-sideways/post.html +60 -0
- data/doc/imported/0003-how-to-turn-your-brain-sideways/source.lt3 +40 -0
- data/doc/imported/0004-upcoming-lone-star-ruby-conference/metadata.txt +7 -0
- data/doc/imported/0004-upcoming-lone-star-ruby-conference/post.html +42 -0
- data/doc/imported/0004-upcoming-lone-star-ruby-conference/source.lt3 +24 -0
- data/doc/imported/0005-elixir-conf-2015-announced/metadata.txt +7 -0
- data/doc/imported/0005-elixir-conf-2015-announced/post.html +30 -0
- data/doc/imported/0005-elixir-conf-2015-announced/source.lt3 +16 -0
- data/doc/imported/0006-ruby-for-dinosaurs/metadata.txt +7 -0
- data/doc/imported/0006-ruby-for-dinosaurs/post.html +43 -0
- data/doc/imported/0006-ruby-for-dinosaurs/source.lt3 +27 -0
- data/doc/imported/0007-phoenix-isnt-rails/metadata.txt +7 -0
- data/doc/imported/0007-phoenix-isnt-rails/post.html +116 -0
- data/doc/imported/0007-phoenix-isnt-rails/source.lt3 +87 -0
- data/doc/imported/0008-concerning-the-term-monkeypatching/metadata.txt +7 -0
- data/doc/imported/0008-concerning-the-term-monkeypatching/post.html +129 -0
- data/doc/imported/0008-concerning-the-term-monkeypatching/source.lt3 +92 -0
- data/doc/imported/0009-announcement-coming-soon/metadata.txt +7 -0
- data/doc/imported/0009-announcement-coming-soon/post.html +33 -0
- data/doc/imported/0009-announcement-coming-soon/source.lt3 +19 -0
- data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/metadata.txt +7 -0
- data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/post.html +175 -0
- data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/source.lt3 +139 -0
- data/doc/imported/0011-computer-science-as-a-lost-art/metadata.txt +7 -0
- data/doc/imported/0011-computer-science-as-a-lost-art/post.html +139 -0
- data/doc/imported/0011-computer-science-as-a-lost-art/source.lt3 +104 -0
- data/doc/imported/0012-ruby-day-in-turin-italy/metadata.txt +7 -0
- data/doc/imported/0012-ruby-day-in-turin-italy/post.html +42 -0
- data/doc/imported/0012-ruby-day-in-turin-italy/source.lt3 +24 -0
- data/doc/imported/0013-rubyday-was-a-success/metadata.txt +7 -0
- data/doc/imported/0013-rubyday-was-a-success/post.html +44 -0
- data/doc/imported/0013-rubyday-was-a-success/source.lt3 +27 -0
- data/doc/imported/0014-working-on-the-blogging-software/metadata.txt +7 -0
- data/doc/imported/0014-working-on-the-blogging-software/post.html +63 -0
- data/doc/imported/0014-working-on-the-blogging-software/source.lt3 +41 -0
- data/doc/imported/0015-ok-its-not-really-a-lost-art/metadata.txt +7 -0
- data/doc/imported/0015-ok-its-not-really-a-lost-art/post.html +172 -0
- data/doc/imported/0015-ok-its-not-really-a-lost-art/source.lt3 +134 -0
- data/doc/imported/0016-an-in-operator-for-ruby/metadata.txt +7 -0
- data/doc/imported/0016-an-in-operator-for-ruby/post.html +155 -0
- data/doc/imported/0016-an-in-operator-for-ruby/source.lt3 +106 -0
- data/doc/imported/0017-the-forgotten-mathematician/metadata.txt +7 -0
- data/doc/imported/0017-the-forgotten-mathematician/post.html +161 -0
- data/doc/imported/0017-the-forgotten-mathematician/source.lt3 +119 -0
- data/doc/imported/0018-ruby-puns/metadata.txt +7 -0
- data/doc/imported/0018-ruby-puns/post.html +46 -0
- data/doc/imported/0018-ruby-puns/source.lt3 +28 -0
- data/doc/imported/0019-custom-exceptions-via-metaprogramming/metadata.txt +7 -0
- data/doc/imported/0019-custom-exceptions-via-metaprogramming/post.html +138 -0
- data/doc/imported/0019-custom-exceptions-via-metaprogramming/source.lt3 +101 -0
- data/doc/imported/0020-fffff/metadata.txt +7 -0
- data/doc/imported/0020-fffff/post.html +24 -0
- data/doc/imported/0020-fffff/source.lt3 +12 -0
- data/doc/imported/0021-trying-ror-yet-again/metadata.txt +7 -0
- data/doc/imported/0021-trying-ror-yet-again/post.html +26 -0
- data/doc/imported/0021-trying-ror-yet-again/source.lt3 +12 -0
- data/doc/imported/0023-doctor-sleep/metadata.txt +7 -0
- data/doc/imported/0023-doctor-sleep/post.html +63 -0
- data/doc/imported/0023-doctor-sleep/source.lt3 +44 -0
- data/doc/imported/0024-just-a-test/metadata.txt +7 -0
- data/doc/imported/0024-just-a-test/post.html +24 -0
- data/doc/imported/0024-just-a-test/source.lt3 +12 -0
- data/doc/imported/import_summary.txt +98 -0
- data/doc/livetext-informal-spec.txt +65 -0
- data/doc/myuserdoc/ch-0.lt3 +31 -0
- data/doc/myuserdoc/ch-1.lt3 +37 -0
- data/doc/myuserdoc/ch-10.lt3 +22 -0
- data/doc/myuserdoc/ch-2.lt3 +37 -0
- data/doc/myuserdoc/ch-3.lt3 +19 -0
- data/doc/myuserdoc/ch-4.lt3 +43 -0
- data/doc/myuserdoc/ch-5.lt3 +22 -0
- data/doc/myuserdoc/ch-6.lt3 +19 -0
- data/doc/myuserdoc/ch-7.lt3 +16 -0
- data/doc/myuserdoc/ch-8.lt3 +13 -0
- data/doc/myuserdoc/ch-9.lt3 +19 -0
- data/doc/myuserdoc/tweak.rb +18 -0
- data/doc/{userdoc-toc.txt → myuserdoc/userdoc-toc.txt} +27 -27
- data/doc/old-posts/0001-elixir-conf-2014.lt3 +24 -0
- data/doc/old-posts/0002-programmers-and-word-processing.lt3 +150 -0
- data/doc/old-posts/0003-how-to-turn-your-brain-sideways.lt3 +43 -0
- data/doc/old-posts/0004-upcoming-lone-star-ruby-conference.lt3 +26 -0
- data/doc/old-posts/0005-elixir-conf-2015-announced.lt3 +17 -0
- data/doc/old-posts/0006-ruby-for-dinosaurs.lt3 +30 -0
- data/doc/old-posts/0007-phoenix-isnt-rails.lt3 +90 -0
- data/doc/old-posts/0008-concerning-the-term-monkeypatching.lt3 +105 -0
- data/doc/old-posts/0009-announcement-coming-soon.lt3 +20 -0
- data/doc/old-posts/0010-immutable-data-ditching-the-wax-tablet.lt3 +142 -0
- data/doc/old-posts/0011-computer-science-as-a-lost-art.lt3 +117 -0
- data/doc/old-posts/0012-ruby-day-in-turin-italy.lt3 +26 -0
- data/doc/old-posts/0013-rubyday-was-a-success.lt3 +28 -0
- data/doc/old-posts/0014-working-on-the-blogging-software.lt3 +42 -0
- data/doc/old-posts/0015-ok-its-not-really-a-lost-art.lt3 +137 -0
- data/doc/old-posts/0016-an-in-operator-for-ruby.lt3 +142 -0
- data/doc/old-posts/0017-the-forgotten-mathematician.lt3 +129 -0
- data/doc/old-posts/0018-ruby-puns.lt3 +31 -0
- data/doc/old-posts/0019-custom-exceptions-via-metaprogramming.lt3 +116 -0
- data/doc/old-posts/0021-trying-ror-yet-again.lt3 +35 -0
- data/doc/old-posts/0023-doctor-sleep.lt3 +43 -0
- data/doc/old-posts/0024-just-a-test.lt3 +12 -0
- data/doc/old-posts/0025-trying-another-post.lt3 +12 -0
- data/doc/old-repo +1 -0
- data/doc/reddit_integration.md +2 -2
- data/doc/user.lt3 +0 -3
- data/lib/scriptorium/api.rb +1811 -78
- data/lib/scriptorium/banner_svg.rb +55 -68
- data/lib/scriptorium/contract.rb +3 -2
- data/lib/scriptorium/exceptions.rb +133 -102
- data/lib/scriptorium/helpers.rb +282 -82
- data/lib/scriptorium/post.rb +81 -17
- data/lib/scriptorium/reddit.rb +1 -1
- data/lib/scriptorium/repo.rb +478 -164
- data/lib/scriptorium/standard_files.rb +30 -396
- data/lib/scriptorium/support/common_js/clipboard.js +35 -0
- data/lib/scriptorium/support/common_js/content-loader.js +187 -0
- data/lib/scriptorium/support/common_js/navigation.js +52 -0
- data/lib/scriptorium/support/common_js/syntax-highlighting.js +27 -0
- data/lib/scriptorium/support/config/reddit_template.txt +17 -0
- data/{test/scriptorium-TEST-1754622690-146/views/sample → lib/scriptorium/support}/config/social.txt +1 -0
- data/lib/scriptorium/support/highlight/css.txt +2 -0
- data/lib/scriptorium/support/highlight/custom.css +119 -0
- data/lib/scriptorium/support/highlight/js.txt +1 -0
- data/lib/scriptorium/support/post_index/config.txt +15 -0
- data/lib/scriptorium/support/post_index/style.css +55 -0
- data/lib/scriptorium/support/templates/index_entry.lt3 +16 -0
- data/{test/scriptorium-TEST-1754622690-146/themes/standard/initial/post.lt3 → lib/scriptorium/support/templates/initial_post.lt3} +5 -5
- data/lib/scriptorium/support/templates/post.lt3 +104 -0
- data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/header.txt → lib/scriptorium/support/theme/header.lt3} +1 -1
- data/lib/scriptorium/theme.rb +83 -70
- data/lib/scriptorium/version.rb +2 -2
- data/lib/scriptorium/view.rb +194 -149
- data/lib/scriptorium.rb +24 -1
- data/lib/skeleton.rb +4 -1
- data/scriptorium.gemspec +2 -1
- data/test/WEB_INTEGRATION_README.md +196 -0
- data/test/all +40 -0
- data/test/banner_svg/unit.rb +267 -35
- data/test/config/deployment.txt +5 -0
- data/test/integration/integration_test.rb +7 -7
- data/test/integration/preview_flow_test.rb +94 -0
- data/test/livetext_plugin_test.rb +453 -182
- data/test/manual/banner-tests/test01.html +82 -18
- data/test/manual/banner-tests/test02.html +82 -18
- data/test/manual/banner-tests/test03.html +82 -18
- data/test/manual/banner-tests/test04.html +89 -25
- data/test/manual/banner-tests/test05.html +89 -25
- data/test/manual/banner-tests/test06.html +89 -25
- data/test/manual/banner-tests/test07.html +89 -25
- data/test/manual/banner-tests/test08.html +82 -18
- data/test/manual/banner-tests/test09.html +82 -18
- data/test/manual/banner-tests/test10.html +82 -18
- data/test/manual/banner-tests/test11.html +82 -18
- data/test/manual/banner-tests/test12.html +82 -18
- data/test/manual/banner-tests/test13.html +82 -18
- data/test/manual/banner-tests/test14.html +82 -18
- data/test/manual/banner-tests/test15.html +82 -18
- data/test/manual/banner-tests/test16.html +82 -18
- data/test/manual/banner-tests/test17.html +82 -18
- data/test/manual/banner-tests/test18.html +90 -26
- data/test/manual/banner-tests/test19.html +90 -26
- data/test/manual/banner-tests/test20.html +90 -26
- data/test/manual/banner-tests/test21.html +90 -26
- data/test/manual/banner-tests/test22.html +90 -26
- data/test/manual/banner-tests/test23.html +90 -26
- data/test/manual/banner-tests/test24.html +90 -26
- data/test/manual/banner-tests/test25.html +89 -25
- data/test/manual/banner_environment.rb +15 -2
- data/test/manual/codemirror_demo.html +773 -0
- data/test/manual/create_posts_for_web.rb +114 -0
- data/test/manual/preview_manual_test.rb +129 -0
- data/test/manual/test_banner_features.rb +14 -14
- data/test/manual/test_banner_integration.rb +115 -0
- data/test/manual/test_banner_radial.rb +87 -0
- data/test/manual/test_syntax_highlighting.rb +60 -40
- data/test/support/preview_utils.rb +88 -0
- data/test/test_gem_assets.rb +48 -0
- data/test/test_helpers.rb +10 -0
- data/test/tui_editor_integration_test.rb +15 -15
- data/test/tui_integration_test.rb +687 -441
- data/test/unit/api.rb +757 -37
- data/test/unit/asset_management.rb +195 -221
- data/test/unit/backup_test.rb +451 -0
- data/test/unit/contract_test.rb +1 -23
- data/test/unit/core.rb +415 -61
- data/test/unit/deploy_config_test.rb +248 -0
- data/test/unit/deploy_test.rb +312 -21
- data/test/unit/edit_post_test.rb +168 -0
- data/test/unit/gem_asset_management.rb +36 -42
- data/test/unit/livetext_basic.rb +23 -35
- data/test/unit/livetext_compatibility.rb +7 -14
- data/test/unit/parse_cmd_test.rb +260 -0
- data/test/unit/{symlink_test.rb → permalink_copy_test.rb} +47 -49
- data/test/unit/post.rb +91 -26
- data/test/unit/post_index_config_test.rb +258 -0
- data/test/unit/post_state_helpers_test.rb +137 -0
- data/test/unit/read_commented_file_test.rb +8 -6
- data/test/unit/repo.rb +75 -54
- data/test/unit/social_test.rb +41 -44
- data/test/unit/syntax_highlighting.rb +70 -0
- data/test/unit/theme_management_test.rb +91 -0
- data/test/unit/view.rb +79 -12
- data/test/unit/widgets.rb +8 -8
- data/test/web_integration_test.rb +231 -0
- data/test/web_test_helper.rb +218 -0
- data/test/web_workflow_test.rb +527 -0
- data/ui/tui/bin/scriptorium +885 -415
- data/ui/web/app/app.rb +1398 -176
- data/ui/web/app/assets/livetext_mode.js +244 -0
- data/ui/web/app/error_helpers.rb +16 -16
- data/ui/web/app/views/advanced_config.erb +8 -2
- data/ui/web/app/views/asset_management.erb +56 -0
- data/ui/web/app/views/backup_management.erb +238 -0
- data/ui/web/app/views/config_widget.erb +232 -0
- data/ui/web/app/views/dashboard.erb +64 -72
- data/ui/web/app/views/deploy_config.erb +3 -0
- data/ui/web/app/views/edit_pages.erb +170 -2
- data/ui/web/app/views/edit_post.erb +130 -9
- data/ui/web/app/views/edit_theme.erb +73 -0
- data/ui/web/app/views/edit_theme_file.erb +74 -0
- data/ui/web/app/views/theme_management.erb +130 -0
- data/ui/web/app/views/view_dashboard.erb +666 -25
- data/ui/web/app/views/widgets.erb +249 -0
- data/ui/web/bin/scriptorium-web +35 -24
- data/ui/web/tmp/timing.log +17 -0
- data/ui/web/tmp/web_server.log +0 -5
- metadata +190 -116
- data/assets/back-icon.png +0 -0
- data/assets/icons/facebook.svg +0 -1
- data/assets/icons/github.svg +0 -1
- data/assets/icons/instagram.svg +0 -1
- data/assets/icons/reddit.svg +0 -1
- data/assets/icons/x.svg +0 -1
- data/assets/icons/youtube.svg +0 -1
- data/bin/scriptorium +0 -1511
- data/doc/anti-amnesia/20250727-060000-api-design-tui-planning.md +0 -34
- data/doc/anti-amnesia/20250727-061000-runeblog-tui-analysis.md +0 -50
- data/doc/anti-amnesia/20250727-154000-livetext-plugin-file-stats.md +0 -73
- data/doc/anti-amnesia/20250727-172600-unified-minitest-framework.md +0 -70
- data/doc/anti-amnesia/20250727-173000-widget-testing-achievement.md +0 -110
- data/doc/anti-amnesia/20250727-180000-post-id-num-refactoring.md +0 -73
- data/doc/anti-amnesia/20250728-124421-conversation-summary-concise.md +0 -124
- data/doc/anti-amnesia/20250729-190000-scriptorium-tui-testing-complete.md +0 -46
- data/doc/anti-amnesia/20250729-200000-scriptorium-tui-testing-edit-file-workflow.md +0 -97
- data/doc/anti-amnesia/20250729-211500-dependency-management-system.md +0 -211
- data/doc/anti-amnesia/20250729-213000-python-virtual-environment-setup.md +0 -141
- data/doc/anti-amnesia/20250729-214500-theme-management-commands.md +0 -211
- data/doc/anti-amnesia/20250729-215000-version-update-to-0.6.0.md +0 -134
- data/doc/anti-amnesia/20250729-220000-user-guide-complete.md +0 -41
- data/doc/anti-amnesia/20250804-213700-publishing-test-fix.md +0 -49
- data/doc/anti-amnesia/20250804-214400-additional-test-fixes.md +0 -46
- data/doc/anti-amnesia/20250804-220000-asset-function-logic-clarification.md +0 -41
- data/doc/anti-amnesia/20250806-202032-asset-function-logic-clarification.md +0 -41
- data/doc/anti-amnesia/20250813-082428-syntax-highlighting-and-navigation-improvements.md +0 -256
- data/lib/scriptorium/syntax_highlighter.rb +0 -234
- data/test/manual/deploy_symlink_demo.rb +0 -142
- data/test/manual/symlink_demo.rb +0 -117
- data/test/manual/test2.rb +0 -12
- data/test/manual/test_banner_from_file.rb +0 -150
- data/test/manual/test_banner_in_header.rb +0 -35
- data/test/manual/test_code_highlighting.rb +0 -68
- data/test/manual/test_complex_header.rb +0 -74
- data/test/manual/test_empty_header.rb +0 -32
- data/test/manual/test_radial_custom.rb +0 -58
- data/test/manual/test_radial_large_radius.rb +0 -52
- data/test/manual/test_svg_debug.rb +0 -47
- data/test/pages-demo/config/currentview.txt +0 -1
- data/test/pages-demo/views/demo/config/common.js +0 -57
- data/test/pages-demo/views/demo/config/footer.txt +0 -1
- data/test/pages-demo/views/demo/config/global-head.txt +0 -8
- data/test/pages-demo/views/demo/config/header.txt +0 -1
- data/test/pages-demo/views/demo/config/layout.txt +0 -1
- data/test/pages-demo/views/demo/config/left.txt +0 -1
- data/test/pages-demo/views/demo/config/main.txt +0 -1
- data/test/pages-demo/views/demo/config/right.txt +0 -1
- data/test/pages-demo/views/demo/config.txt +0 -3
- data/test/pages-demo/views/demo/output/panes/footer.html +0 -1
- data/test/pages-demo/views/demo/output/panes/header.html +0 -1
- data/test/pages-demo/views/demo/output/panes/left.html +0 -1
- data/test/pages-demo/views/demo/output/panes/main.html +0 -1
- data/test/pages-demo/views/demo/output/panes/right.html +0 -1
- data/test/scriptorium-TEST-1754622690-146/config/bootstrap_css.txt +0 -5
- data/test/scriptorium-TEST-1754622690-146/config/bootstrap_js.txt +0 -4
- data/test/scriptorium-TEST-1754622690-146/config/common.js +0 -57
- data/test/scriptorium-TEST-1754622690-146/config/currentview.txt +0 -1
- data/test/scriptorium-TEST-1754622690-146/config/global-head.txt +0 -9
- data/test/scriptorium-TEST-1754622690-146/config/last_post_num.txt +0 -1
- data/test/scriptorium-TEST-1754622690-146/config/os_helpers.rb +0 -4
- data/test/scriptorium-TEST-1754622690-146/config/widgets.txt +0 -3
- data/test/scriptorium-TEST-1754622690-146/posts/0001/meta.txt +0 -8
- data/test/scriptorium-TEST-1754622690-146/posts/0001/source.lt3 +0 -6
- data/test/scriptorium-TEST-1754622690-146/themes/standard/README.txt +0 -1
- data/test/scriptorium-TEST-1754622690-146/themes/standard/config.txt +0 -1
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/gen/text.css +0 -1
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index.lt3 +0 -1
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index_entry.lt3 +0 -14
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/post.lt3 +0 -13
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/widget.lt3 +0 -1
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_css.txt +0 -5
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_js.txt +0 -4
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/common.js +0 -57
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/deploy.txt +0 -5
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/footer.txt +0 -2
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/global-head.txt +0 -9
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/header.txt +0 -4
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/layout.txt +0 -5
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/left.txt +0 -3
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/main.txt +0 -5
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/right.txt +0 -3
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/status.txt +0 -7
- data/test/scriptorium-TEST-1754622690-146/views/sample/config.txt +0 -3
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/footer.html +0 -3
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/header.html +0 -3
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/left.html +0 -3
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/main.html +0 -3
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/right.html +0 -3
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/footer.html +0 -1
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/header.html +0 -1
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/left.html +0 -1
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/main.html +0 -1
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/right.html +0 -1
- data/ui/web/tmp/web_server.pid +0 -1
- /data/{test/pages-demo/views/demo/config/bootstrap_css.txt → lib/scriptorium/support/bootstrap/css.txt} +0 -0
- /data/{test/pages-demo/views/demo/config/bootstrap_js.txt → lib/scriptorium/support/bootstrap/js.txt} +0 -0
- /data/{test/scriptorium-TEST-1754622690-146/views/sample → lib/scriptorium/support}/config/reddit.txt +0 -0
- /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout → lib/scriptorium/support/templates}/layout.txt +0 -0
- /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/footer.txt → lib/scriptorium/support/theme/footer.lt3} +0 -0
- /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/left.txt → lib/scriptorium/support/theme/left.lt3} +0 -0
- /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/main.txt → lib/scriptorium/support/theme/main.lt3} +0 -0
- /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/right.txt → lib/scriptorium/support/theme/right.lt3} +0 -0
- /data/test/manual/banner-tests/{config.txt → svg.txt} +0 -0
- /data/test/manual/{test6.rb → test_advanced_widgets.rb} +0 -0
- /data/test/manual/{test1.rb → test_basic_posts.rb} +0 -0
- /data/test/manual/{test4.rb → test_layout_widgets.rb} +0 -0
- /data/test/manual/{test5.rb → test_pagination.rb} +0 -0
- /data/test/manual/{test3.rb → test_random_posts.rb} +0 -0
data/lib/scriptorium/helpers.rb
CHANGED
@@ -18,6 +18,17 @@ Symbol.include(PathSep)
|
|
18
18
|
|
19
19
|
module Scriptorium::Helpers
|
20
20
|
include Scriptorium::Exceptions
|
21
|
+
|
22
|
+
# Post comparison for sorting (uses post.date which handles fallback)
|
23
|
+
def post_compare(a, b)
|
24
|
+
# Primary sort: date (newest first)
|
25
|
+
date_compare = b.date <=> a.date
|
26
|
+
return date_compare unless date_compare == 0
|
27
|
+
|
28
|
+
# Secondary sort: post number (ascending) for stable ordering when dates are identical
|
29
|
+
a.num <=> b.num
|
30
|
+
end
|
31
|
+
|
21
32
|
def getvars(file)
|
22
33
|
lines = read_file(file, lines: true)
|
23
34
|
lines.map! {|line| line.sub(/# .*$/, "").strip }
|
@@ -25,6 +36,8 @@ module Scriptorium::Helpers
|
|
25
36
|
vhash = Hash.new("")
|
26
37
|
lines.each do |line|
|
27
38
|
var, val = line.split(" ", 2)
|
39
|
+
# Fix: treat nil values as empty strings
|
40
|
+
val = "" if val.nil?
|
28
41
|
vhash[var.to_sym] = val
|
29
42
|
end
|
30
43
|
vhash
|
@@ -38,11 +51,21 @@ module Scriptorium::Helpers
|
|
38
51
|
@root/:views/name
|
39
52
|
end
|
40
53
|
|
41
|
-
def write_file(file, content)
|
54
|
+
def write_file(file, content, empty: false)
|
42
55
|
# Input validation
|
43
|
-
|
56
|
+
raise FilePathNil if file.nil?
|
57
|
+
|
58
|
+
raise FilePathEmpty if file.to_s.strip.empty?
|
44
59
|
|
45
|
-
|
60
|
+
# Handle empty content if empty: true is specified
|
61
|
+
if empty && (content.nil? || content.to_s.strip.empty?)
|
62
|
+
# If file exists, do nothing; if it doesn't exist, touch it
|
63
|
+
unless File.exist?(file)
|
64
|
+
FileUtils.mkdir_p(File.dirname(file))
|
65
|
+
FileUtils.touch(file)
|
66
|
+
end
|
67
|
+
return
|
68
|
+
end
|
46
69
|
|
47
70
|
# Ensure parent directory exists
|
48
71
|
FileUtils.mkdir_p(File.dirname(file))
|
@@ -53,30 +76,30 @@ module Scriptorium::Helpers
|
|
53
76
|
f.puts content
|
54
77
|
end
|
55
78
|
rescue Errno::ENOSPC => e
|
56
|
-
|
79
|
+
raise FileDiskFull(file, e.message)
|
57
80
|
rescue Errno::EACCES => e
|
58
|
-
|
81
|
+
raise FilePermissionDenied(file, e.message)
|
59
82
|
rescue Errno::ENOENT => e
|
60
|
-
|
83
|
+
raise FileDirectoryNotFound(file, e.message)
|
61
84
|
rescue => e
|
62
|
-
raise
|
85
|
+
raise WriteFileError(file, e.message)
|
63
86
|
end
|
64
87
|
end
|
65
88
|
|
66
|
-
def write_file!(file, *lines)
|
89
|
+
def write_file!(file, *lines, empty: false)
|
67
90
|
# Convert nil values to empty strings for proper joining
|
68
91
|
processed_lines = lines.map { |line| line.nil? ? "" : line.to_s }
|
69
92
|
content = processed_lines.join("\n")
|
70
93
|
# Always add a newline at the end to ensure there's an empty line
|
71
94
|
content += "\n"
|
72
|
-
write_file(file, content)
|
95
|
+
write_file(file, content, empty: empty)
|
73
96
|
end
|
74
97
|
|
75
98
|
def make_dir(dir, create_parents = false)
|
76
99
|
# Input validation
|
77
|
-
|
78
|
-
|
79
|
-
raise
|
100
|
+
raise DirectoryPathNil if dir.nil?
|
101
|
+
|
102
|
+
raise DirectoryPathEmpty if dir.to_s.strip.empty?
|
80
103
|
|
81
104
|
# Create parent directories if requested
|
82
105
|
if create_parents
|
@@ -86,25 +109,25 @@ module Scriptorium::Helpers
|
|
86
109
|
begin
|
87
110
|
Dir.mkdir(dir)
|
88
111
|
rescue Errno::ENOSPC => e
|
89
|
-
raise
|
112
|
+
raise DirectoryDiskFull(dir, e.message)
|
90
113
|
rescue Errno::EACCES => e
|
91
|
-
raise
|
114
|
+
raise DirectoryPermissionDenied(dir, e.message)
|
92
115
|
rescue Errno::ENOENT => e
|
93
|
-
raise
|
116
|
+
raise DirectoryParentNotFound(dir, e.message)
|
94
117
|
rescue Errno::EEXIST => e
|
95
118
|
# Directory already exists - this is usually not an error
|
96
119
|
# But we could make this configurable if needed
|
97
120
|
rescue => e
|
98
|
-
raise
|
121
|
+
raise CreateDirectoryError(dir, e.message)
|
99
122
|
end
|
100
123
|
end
|
101
124
|
end
|
102
125
|
|
103
126
|
def system!(command, description = nil)
|
104
127
|
# Input validation
|
105
|
-
|
106
|
-
|
107
|
-
raise
|
128
|
+
raise CommandNil if command.nil?
|
129
|
+
|
130
|
+
raise CommandEmpty if command.to_s.strip.empty?
|
108
131
|
|
109
132
|
# Execute command with error handling
|
110
133
|
success = system(command)
|
@@ -119,9 +142,9 @@ module Scriptorium::Helpers
|
|
119
142
|
|
120
143
|
def need(type, path, exception_class = RuntimeError)
|
121
144
|
# Input validation
|
122
|
-
|
123
|
-
|
124
|
-
raise
|
145
|
+
raise RequirePathNil(type) if path.nil?
|
146
|
+
|
147
|
+
raise RequirePathEmpty(type) if path.to_s.strip.empty?
|
125
148
|
|
126
149
|
# Check if file/directory exists
|
127
150
|
exists = case type
|
@@ -146,9 +169,9 @@ module Scriptorium::Helpers
|
|
146
169
|
|
147
170
|
def read_file(file, options = {})
|
148
171
|
# Input validation
|
149
|
-
|
150
|
-
|
151
|
-
raise
|
172
|
+
raise ReadFilePathNil if file.nil?
|
173
|
+
|
174
|
+
raise ReadFilePathEmpty if file.to_s.strip.empty?
|
152
175
|
|
153
176
|
# Handle missing file with fallback
|
154
177
|
if options[:missing_fallback]
|
@@ -172,12 +195,12 @@ module Scriptorium::Helpers
|
|
172
195
|
if options[:missing_fallback]
|
173
196
|
return options[:missing_fallback]
|
174
197
|
else
|
175
|
-
raise
|
198
|
+
raise ReadFileNotFound(file, e.message)
|
176
199
|
end
|
177
200
|
rescue Errno::EACCES => e
|
178
|
-
|
201
|
+
raise ReadFilePermissionDenied(file, e.message)
|
179
202
|
rescue => e
|
180
|
-
|
203
|
+
raise ReadFileError(file, e.message)
|
181
204
|
end
|
182
205
|
end
|
183
206
|
|
@@ -207,6 +230,8 @@ module Scriptorium::Helpers
|
|
207
230
|
end
|
208
231
|
|
209
232
|
def slugify(id, title)
|
233
|
+
return "#{d4(id)}-untitled" if title.nil? || title.strip.empty?
|
234
|
+
|
210
235
|
slug = title.downcase.strip
|
211
236
|
.gsub(/[^a-z0-9\s_-]/, '') # remove punctuation
|
212
237
|
.gsub(/[\s_-]+/, '-') # replace spaces and underscores with hyphen
|
@@ -225,7 +250,11 @@ module Scriptorium::Helpers
|
|
225
250
|
end
|
226
251
|
|
227
252
|
def ymdhms
|
228
|
-
Time.now.strftime("%Y-%m-%d
|
253
|
+
Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
254
|
+
end
|
255
|
+
|
256
|
+
def ymdhms_filename
|
257
|
+
Time.now.strftime("%Y%m%d-%H%M%S")
|
229
258
|
end
|
230
259
|
|
231
260
|
def see_file(file) # Really from TestHelpers
|
@@ -243,16 +272,14 @@ module Scriptorium::Helpers
|
|
243
272
|
lines.each {|line| line.gsub!(/ *#.*$/, "") }
|
244
273
|
entries = []
|
245
274
|
|
246
|
-
#
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
make_dir(
|
251
|
-
|
252
|
-
# Prepare stack starting from root
|
253
|
-
stack = [root_path]
|
275
|
+
# Always throw away the first line (for backward compatibility)
|
276
|
+
lines.shift
|
277
|
+
|
278
|
+
# Create the base directory and start stack there
|
279
|
+
make_dir(base) unless File.exist?(base)
|
280
|
+
stack = [base]
|
254
281
|
|
255
|
-
# Parse
|
282
|
+
# Parse all lines as structure
|
256
283
|
lines.each do |line|
|
257
284
|
if (i = line.index(/ [a-zA-Z0-9_.]/))
|
258
285
|
name = line[(i + 1)..-1]
|
@@ -301,6 +328,17 @@ module Scriptorium::Helpers
|
|
301
328
|
end
|
302
329
|
lines # Return cleaned lines
|
303
330
|
end
|
331
|
+
|
332
|
+
def parse_commented_file(file_path)
|
333
|
+
config = {}
|
334
|
+
read_commented_file(file_path).each do |line|
|
335
|
+
if line.include?(' ')
|
336
|
+
key, value = line.split(/\s+/, 2)
|
337
|
+
config[key] = config[key.to_sym] = value
|
338
|
+
end
|
339
|
+
end
|
340
|
+
config
|
341
|
+
end
|
304
342
|
|
305
343
|
def cf_time(t1, t2)
|
306
344
|
t1 = t1.split(/- :/, 6)
|
@@ -310,32 +348,82 @@ module Scriptorium::Helpers
|
|
310
348
|
t1 <=> t2
|
311
349
|
end
|
312
350
|
|
351
|
+
# Post state management helpers
|
352
|
+
|
353
|
+
def read_post_state_file(file_path)
|
354
|
+
return [] unless File.exist?(file_path)
|
355
|
+
content = read_file(file_path)
|
356
|
+
return [] if content.strip.empty?
|
357
|
+
content.lines.map { |line| line.strip.to_i }.reject { |id| id == 0 }
|
358
|
+
end
|
359
|
+
|
360
|
+
def write_post_state_file(file_path, post_ids)
|
361
|
+
if post_ids.empty?
|
362
|
+
# Write empty file (empty = all posts are in that state)
|
363
|
+
# For state files, empty means "all posts are in this state", so we need to clear the file
|
364
|
+
write_file(file_path, "")
|
365
|
+
else
|
366
|
+
content = post_ids.sort.uniq.join("\n") + "\n"
|
367
|
+
write_file(file_path, content)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
def add_post_to_state_file(file_path, post_id)
|
372
|
+
post_ids = read_post_state_file(file_path)
|
373
|
+
post_ids << post_id unless post_ids.include?(post_id)
|
374
|
+
write_post_state_file(file_path, post_ids)
|
375
|
+
end
|
376
|
+
|
377
|
+
def remove_post_from_state_file(file_path, post_id)
|
378
|
+
post_ids = read_post_state_file(file_path)
|
379
|
+
post_ids.delete(post_id)
|
380
|
+
write_post_state_file(file_path, post_ids)
|
381
|
+
end
|
382
|
+
|
383
|
+
def post_in_state_file?(file_path, post_id)
|
384
|
+
post_ids = read_post_state_file(file_path)
|
385
|
+
post_ids.include?(post_id)
|
386
|
+
end
|
387
|
+
|
388
|
+
# Helper method to find asset recursively in a directory
|
389
|
+
def find_asset(base_dir, asset_name)
|
390
|
+
# First try exact path
|
391
|
+
exact_path = "#{base_dir}/#{asset_name}"
|
392
|
+
return exact_path if File.exist?(exact_path)
|
393
|
+
|
394
|
+
# Then search recursively
|
395
|
+
Dir.glob("#{base_dir}/**/*").each do |file|
|
396
|
+
next unless File.file?(file)
|
397
|
+
next unless File.basename(file) == File.basename(asset_name)
|
398
|
+
return file
|
399
|
+
end
|
400
|
+
|
401
|
+
nil
|
402
|
+
end
|
403
|
+
|
313
404
|
def get_asset_path(name)
|
314
405
|
if Scriptorium::Repo.testing
|
315
406
|
# Development/testing: Check dev_assets first, then local assets
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
407
|
+
dev_asset = find_asset("dev_assets", name)
|
408
|
+
return dev_asset if dev_asset
|
409
|
+
|
410
|
+
local_asset = find_asset("assets", name)
|
411
|
+
return local_asset if local_asset
|
412
|
+
|
413
|
+
raise AssetNotFound(name)
|
323
414
|
else # Production
|
324
415
|
# Production: Check user assets first, then gem assets
|
325
416
|
|
326
|
-
# Check user assets first (highest priority)
|
327
|
-
|
328
|
-
|
329
|
-
end
|
417
|
+
# Check user assets first (highest priority) - recursively
|
418
|
+
user_asset = find_asset("assets", name)
|
419
|
+
return user_asset if user_asset
|
330
420
|
|
331
|
-
# Then check gem assets (fallback)
|
421
|
+
# Then check gem assets (fallback) - recursively
|
332
422
|
begin
|
333
423
|
gem_spec = Gem.loaded_specs['scriptorium']
|
334
424
|
if gem_spec
|
335
|
-
|
336
|
-
if
|
337
|
-
return gem_asset_path
|
338
|
-
end
|
425
|
+
gem_asset = find_asset("#{gem_spec.full_gem_path}/assets", name)
|
426
|
+
return gem_asset if gem_asset
|
339
427
|
end
|
340
428
|
rescue => e
|
341
429
|
# If gem lookup fails, continue without gem assets
|
@@ -385,43 +473,41 @@ module Scriptorium::Helpers
|
|
385
473
|
|
386
474
|
def list_gem_assets
|
387
475
|
assets = []
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
assets << relative_path
|
397
|
-
end
|
476
|
+
gem_spec = Gem.loaded_specs['scriptorium']
|
477
|
+
if gem_spec
|
478
|
+
gem_assets_dir = "#{gem_spec.full_gem_path}/assets"
|
479
|
+
if Dir.exist?(gem_assets_dir)
|
480
|
+
Dir.glob("#{gem_assets_dir}/**/*").each do |file|
|
481
|
+
next unless File.file?(file)
|
482
|
+
relative_path = file.sub("#{gem_assets_dir}/", "")
|
483
|
+
assets << relative_path
|
398
484
|
end
|
399
485
|
end
|
400
|
-
rescue => e
|
401
|
-
# If gem lookup fails, return empty array
|
402
486
|
end
|
403
487
|
assets.sort
|
488
|
+
rescue => e
|
489
|
+
# If gem lookup fails, return empty array
|
490
|
+
[]
|
404
491
|
end
|
405
492
|
|
406
493
|
def copy_gem_asset_to_user(asset_name, target_dir = "assets")
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
if
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
return target_path
|
419
|
-
end
|
494
|
+
gem_spec = Gem.loaded_specs['scriptorium']
|
495
|
+
if gem_spec
|
496
|
+
gem_asset_path = "#{gem_spec.full_gem_path}/assets/#{asset_name}"
|
497
|
+
if File.exist?(gem_asset_path)
|
498
|
+
# Create target directory if it doesn't exist
|
499
|
+
FileUtils.mkdir_p(target_dir) unless Dir.exist?(target_dir)
|
500
|
+
|
501
|
+
# Copy the asset
|
502
|
+
target_path = "#{target_dir}/#{File.basename(asset_name)}"
|
503
|
+
FileUtils.cp(gem_asset_path, target_path)
|
504
|
+
return target_path
|
420
505
|
end
|
421
|
-
rescue => e
|
422
|
-
# If gem lookup fails, return nil
|
423
506
|
end
|
424
507
|
nil
|
508
|
+
rescue => e
|
509
|
+
# If gem lookup fails, return nil
|
510
|
+
nil
|
425
511
|
end
|
426
512
|
|
427
513
|
# Clipboard helper methods
|
@@ -471,5 +557,119 @@ module Scriptorium::Helpers
|
|
471
557
|
nil
|
472
558
|
end
|
473
559
|
end
|
474
|
-
end
|
475
560
|
|
561
|
+
def tty(str)
|
562
|
+
File.open('/dev/tty', 'w') { |f| f.puts str }
|
563
|
+
end
|
564
|
+
|
565
|
+
def format_date(format, day = Time.now.to_date)
|
566
|
+
# Handle nil dates
|
567
|
+
return format if day.nil?
|
568
|
+
|
569
|
+
# Handle string dates by parsing them
|
570
|
+
if day.is_a?(String)
|
571
|
+
begin
|
572
|
+
day = Date.parse(day)
|
573
|
+
rescue Date::Error
|
574
|
+
return format # Return original format if date is invalid
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
# Parse the format string and replace tokens
|
579
|
+
result = format.dup
|
580
|
+
|
581
|
+
# Month name vs number
|
582
|
+
result.gsub!(/\bmonth\b/, day.strftime("%B"))
|
583
|
+
result.gsub!(/\bmm\b/, day.strftime("%m"))
|
584
|
+
|
585
|
+
# Day ordinal vs number - use smart padding
|
586
|
+
result.gsub!(/\bday\b/, ordinalize(day.day))
|
587
|
+
result.gsub!(/\bdd\b/) { smart_pad_day(day.day, result, $~.offset(0)[0]) }
|
588
|
+
|
589
|
+
# Year formats
|
590
|
+
result.gsub!(/\byyyy\b/, day.strftime("%Y"))
|
591
|
+
result.gsub!(/\byy\b/, day.strftime("%y"))
|
592
|
+
|
593
|
+
# Line breaks - handle spaces around break
|
594
|
+
result.gsub!(/\s+break\s+/, "<br>")
|
595
|
+
result.gsub!(/\s+break\b/, "<br>")
|
596
|
+
result.gsub!(/\bbreak\s+/, "<br>")
|
597
|
+
result.gsub!(/\bbreak\b/, "<br>")
|
598
|
+
|
599
|
+
result
|
600
|
+
end
|
601
|
+
|
602
|
+
private
|
603
|
+
|
604
|
+
def smart_pad_day(day, format_string, position)
|
605
|
+
# Look at the context around the dd token to determine padding
|
606
|
+
before = format_string[0...position]
|
607
|
+
after = format_string[position + 2..-1] || ""
|
608
|
+
|
609
|
+
# Check if we should pad based on context
|
610
|
+
should_pad = false
|
611
|
+
|
612
|
+
# Rule 1: Initial number followed immediately by punctuation
|
613
|
+
if position == 0 && after.match?(/^[^a-zA-Z\s]/)
|
614
|
+
should_pad = true
|
615
|
+
end
|
616
|
+
|
617
|
+
# Rule 2: Number enclosed in punctuation (before and after)
|
618
|
+
if before.match?(/[^a-zA-Z\s]$/) && after.match?(/^[^a-zA-Z\s]/)
|
619
|
+
should_pad = true
|
620
|
+
end
|
621
|
+
|
622
|
+
# Rule 3: Terminal number preceded by punctuation
|
623
|
+
if after.empty? && before.match?(/[^a-zA-Z\s]$/)
|
624
|
+
should_pad = true
|
625
|
+
end
|
626
|
+
|
627
|
+
# Rule 4: Middle number with spaces - don't pad
|
628
|
+
if before.match?(/\s$/) || after.match?(/^\s/)
|
629
|
+
should_pad = false
|
630
|
+
end
|
631
|
+
|
632
|
+
should_pad ? day.to_s.rjust(2, '0') : day.to_s
|
633
|
+
end
|
634
|
+
|
635
|
+
def ordinalize(number)
|
636
|
+
case number % 100
|
637
|
+
when 11, 12, 13
|
638
|
+
"#{number}th"
|
639
|
+
else
|
640
|
+
case number % 10
|
641
|
+
when 1 then "#{number}st"
|
642
|
+
when 2 then "#{number}nd"
|
643
|
+
when 3 then "#{number}rd"
|
644
|
+
else "#{number}th"
|
645
|
+
end
|
646
|
+
end
|
647
|
+
end
|
648
|
+
|
649
|
+
def support_file(relative_path)
|
650
|
+
# Get the path to support files, handling both dev and gem environments
|
651
|
+
begin
|
652
|
+
gem_spec = Gem.loaded_specs['scriptorium']
|
653
|
+
if gem_spec
|
654
|
+
# Production: use gem path
|
655
|
+
"#{gem_spec.full_gem_path}/lib/scriptorium/support/#{relative_path}"
|
656
|
+
else
|
657
|
+
# Development: use project root (not current working directory)
|
658
|
+
project_root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
|
659
|
+
File.expand_path("lib/scriptorium/support/#{relative_path}", project_root)
|
660
|
+
end
|
661
|
+
rescue => e
|
662
|
+
# Fallback to development path if gem lookup fails
|
663
|
+
project_root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
|
664
|
+
File.expand_path("lib/scriptorium/support/#{relative_path}", project_root)
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
def support_data(relative_path)
|
669
|
+
read_file(support_file(relative_path))
|
670
|
+
end
|
671
|
+
|
672
|
+
def copy_support_file(relative_path, target_path)
|
673
|
+
FileUtils.cp(support_file(relative_path), target_path)
|
674
|
+
end
|
675
|
+
end
|
data/lib/scriptorium/post.rb
CHANGED
@@ -22,10 +22,10 @@ class Scriptorium::Post
|
|
22
22
|
end
|
23
23
|
|
24
24
|
private def validate_initialization(repo, num)
|
25
|
-
raise
|
26
|
-
raise
|
27
|
-
raise
|
28
|
-
raise
|
25
|
+
raise PostRepoNil if repo.nil?
|
26
|
+
raise PostNumNil if num.nil?
|
27
|
+
raise PostNumEmpty if num.to_s.strip.empty?
|
28
|
+
raise PostNumInvalid(num) unless num.to_s.match?(/^\d+$/)
|
29
29
|
end
|
30
30
|
|
31
31
|
def dir
|
@@ -33,16 +33,40 @@ class Scriptorium::Post
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def meta_file
|
36
|
-
# Check
|
37
|
-
|
38
|
-
|
36
|
+
# Check what directory actually exists
|
37
|
+
normal_dir = @repo.root/:posts/@num
|
38
|
+
deleted_dir = @repo.root/:posts/"_#{@num}"
|
39
39
|
|
40
|
-
|
41
|
-
|
40
|
+
if Dir.exist?(normal_dir)
|
41
|
+
# Normal post directory exists
|
42
|
+
normal_dir/"meta.txt"
|
43
|
+
elsif Dir.exist?(deleted_dir)
|
44
|
+
# Deleted post directory exists
|
45
|
+
deleted_dir/"meta.txt"
|
46
|
+
else
|
47
|
+
# Neither exists - post never existed
|
48
|
+
raise "Post directory for #{@num} not found"
|
49
|
+
end
|
42
50
|
end
|
43
|
-
|
44
|
-
|
45
51
|
|
52
|
+
def validate_metadata_consistency
|
53
|
+
return unless File.exist?(meta_file)
|
54
|
+
|
55
|
+
normal_dir = @repo.root/:posts/@num
|
56
|
+
deleted_dir = @repo.root/:posts/"_#{@num}"
|
57
|
+
metadata_deleted = meta["post.deleted"] == "true"
|
58
|
+
|
59
|
+
if Dir.exist?(normal_dir) && metadata_deleted
|
60
|
+
raise "Inconsistency: Post #{@num} has normal directory but metadata shows deleted=true"
|
61
|
+
elsif Dir.exist?(deleted_dir) && !metadata_deleted
|
62
|
+
raise "Inconsistency: Post #{@num} has deleted directory but metadata shows deleted=false"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def blurb_text
|
67
|
+
meta["post.blurb"]
|
68
|
+
end
|
69
|
+
|
46
70
|
def blurb
|
47
71
|
meta["post.blurb"]
|
48
72
|
end
|
@@ -58,6 +82,14 @@ class Scriptorium::Post
|
|
58
82
|
def pubdate
|
59
83
|
meta["post.pubdate"]
|
60
84
|
end
|
85
|
+
|
86
|
+
def created
|
87
|
+
meta["post.created"]
|
88
|
+
end
|
89
|
+
|
90
|
+
def date
|
91
|
+
pubdate || created
|
92
|
+
end
|
61
93
|
|
62
94
|
def set_pubdate(ymd, seconds: 0)
|
63
95
|
check_invariants
|
@@ -80,12 +112,12 @@ class Scriptorium::Post
|
|
80
112
|
end
|
81
113
|
|
82
114
|
private def validate_date_format(date)
|
83
|
-
raise
|
84
|
-
raise
|
85
|
-
raise
|
115
|
+
raise PubdateYmdNil if date.nil?
|
116
|
+
raise PubdateYmdEmpty if date.to_s.strip.empty?
|
117
|
+
raise PubdateInvalidFormat(date) unless date.to_s.match?(/^\d{4}-\d{2}-\d{2}$/)
|
86
118
|
end
|
87
119
|
|
88
|
-
|
120
|
+
def update_pubdate_metadata(time)
|
89
121
|
meta["post.pubdate"] = time.strftime("%Y-%m-%d %H:%M:%S")
|
90
122
|
meta["post.pubdate.month"] = time.strftime("%B")
|
91
123
|
meta["post.pubdate.day"] = time.strftime("%e")
|
@@ -123,8 +155,25 @@ class Scriptorium::Post
|
|
123
155
|
meta["post.tags"]
|
124
156
|
end
|
125
157
|
|
158
|
+
def tags_array
|
159
|
+
tags_str = meta["post.tags"]
|
160
|
+
return [] if tags_str.nil? || tags_str.strip.empty?
|
161
|
+
tags_str.strip.split(/,\s*/)
|
162
|
+
end
|
163
|
+
|
126
164
|
def deleted
|
127
|
-
|
165
|
+
# Check what directory actually exists
|
166
|
+
normal_dir = @repo.root/:posts/@num
|
167
|
+
deleted_dir = @repo.root/:posts/"_#{@num}"
|
168
|
+
|
169
|
+
if Dir.exist?(deleted_dir)
|
170
|
+
true
|
171
|
+
elsif Dir.exist?(normal_dir)
|
172
|
+
false
|
173
|
+
else
|
174
|
+
# Neither exists - post never existed
|
175
|
+
raise "Post directory for #{@num} not found"
|
176
|
+
end
|
128
177
|
end
|
129
178
|
|
130
179
|
def deleted=(value)
|
@@ -144,9 +193,10 @@ class Scriptorium::Post
|
|
144
193
|
end
|
145
194
|
|
146
195
|
# New class method to read metadata and initialize the Post
|
147
|
-
def self.read(repo, num)
|
196
|
+
def self.read(repo, num, deleted: false)
|
148
197
|
post = new(repo, num)
|
149
198
|
post.load_metadata
|
199
|
+
post.validate_metadata_consistency
|
150
200
|
post
|
151
201
|
end
|
152
202
|
|
@@ -159,6 +209,20 @@ class Scriptorium::Post
|
|
159
209
|
return @vars if defined?(@vars)
|
160
210
|
@vars = Hash.new("")
|
161
211
|
meta.each_pair {|k,v| @vars[k.to_sym] = v }
|
212
|
+
|
213
|
+
# Add computed date components for template access
|
214
|
+
date_value = pubdate || created
|
215
|
+
if date_value
|
216
|
+
begin
|
217
|
+
date_obj = Date.parse(date_value)
|
218
|
+
@vars[:"post.pubdate.month"] = date_obj.strftime("%B")
|
219
|
+
@vars[:"post.pubdate.day"] = date_obj.strftime("%e").strip
|
220
|
+
@vars[:"post.pubdate.year"] = date_obj.strftime("%Y")
|
221
|
+
rescue Date::Error
|
222
|
+
# If date is invalid, leave the computed fields empty
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
162
226
|
@vars
|
163
227
|
end
|
164
228
|
|