scriptorium 0.0.2 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.lt3 +324 -0
- data/README.md +3155 -1
- data/assets/.DS_Store +0 -0
- data/assets/README.md +44 -0
- data/assets/back-icon.png +0 -0
- data/assets/icons/facebook.svg +1 -0
- data/assets/icons/github.svg +1 -0
- data/assets/icons/instagram.svg +1 -0
- data/assets/icons/reddit.svg +1 -0
- data/assets/icons/ui/.DS_Store +0 -0
- data/assets/icons/ui/back.png +0 -0
- data/assets/icons/ui/copy.png +0 -0
- data/assets/icons/ui/down.png +0 -0
- data/assets/icons/ui/end.png +0 -0
- data/assets/icons/ui/exit.png +0 -0
- data/assets/icons/ui/foo +10 -0
- data/assets/icons/ui/home.png +0 -0
- data/assets/icons/ui/left.png +0 -0
- data/assets/icons/ui/next.png +0 -0
- data/assets/icons/ui/right.png +0 -0
- data/assets/icons/ui/start.png +0 -0
- data/assets/icons/ui/up.png +0 -0
- data/assets/icons/x.svg +1 -0
- data/assets/icons/youtube.svg +1 -0
- data/assets/samples/placeholder.svg +9 -0
- data/assets/themes/standard/favicon.svg +6 -0
- data/bin/scriptorium +1511 -0
- data/doc/README.txt +6 -0
- data/doc/anti-amnesia/20250727-054000-scriptorium-overview.md +95 -0
- data/doc/anti-amnesia/20250727-060000-api-design-tui-planning.md +34 -0
- data/doc/anti-amnesia/20250727-061000-runeblog-tui-analysis.md +50 -0
- data/doc/anti-amnesia/20250727-123000-anti-amnesia-conventions.md +31 -0
- data/doc/anti-amnesia/20250727-154000-livetext-plugin-file-stats.md +73 -0
- data/doc/anti-amnesia/20250727-172600-cursor-rbenv-ruby-version-mystery.md +64 -0
- data/doc/anti-amnesia/20250727-172600-unified-minitest-framework.md +70 -0
- data/doc/anti-amnesia/20250727-172900-ai-cognitive-assessment-capabilities.md +40 -0
- data/doc/anti-amnesia/20250727-173000-widget-testing-achievement.md +110 -0
- data/doc/anti-amnesia/20250727-180000-post-id-num-refactoring.md +73 -0
- data/doc/anti-amnesia/20250728-124243-aaa-syntax-clarification.md +46 -0
- data/doc/anti-amnesia/20250728-124421-conversation-summary-concise.md +124 -0
- data/doc/anti-amnesia/20250729-190000-scriptorium-tui-testing-complete.md +46 -0
- data/doc/anti-amnesia/20250729-200000-scriptorium-tui-testing-edit-file-workflow.md +97 -0
- data/doc/anti-amnesia/20250729-210000-reddit-autopost-integration-complete.md +158 -0
- data/doc/anti-amnesia/20250729-211500-dependency-management-system.md +211 -0
- data/doc/anti-amnesia/20250729-213000-python-virtual-environment-setup.md +141 -0
- data/doc/anti-amnesia/20250729-214500-theme-management-commands.md +211 -0
- data/doc/anti-amnesia/20250729-215000-version-update-to-0.6.0.md +134 -0
- data/doc/anti-amnesia/20250729-220000-user-guide-complete.md +41 -0
- data/doc/anti-amnesia/20250804-190500-cognitive-loop-bug.md +45 -0
- data/doc/anti-amnesia/20250804-190700-anti-amnesia-timestamping-fix.md +30 -0
- data/doc/anti-amnesia/20250804-213700-publishing-test-fix.md +49 -0
- data/doc/anti-amnesia/20250804-214400-additional-test-fixes.md +46 -0
- data/doc/anti-amnesia/20250804-220000-asset-function-logic-clarification.md +41 -0
- data/doc/anti-amnesia/20250806-202032-asset-function-logic-clarification.md +41 -0
- data/doc/anti-amnesia/20250807-213025.md +116 -0
- data/doc/anti-amnesia/20250813-082428-syntax-highlighting-and-navigation-improvements.md +256 -0
- data/doc/banner_svg_config.md +114 -0
- data/doc/contrib.lt3 +8 -0
- data/doc/dependencies.md +281 -0
- data/doc/hacker.lt3 +5 -0
- data/doc/reddit_credentials_template.json +8 -0
- data/doc/reddit_integration.md +207 -0
- data/doc/user.lt3 +38 -0
- data/doc/user_guide_section_1.md +137 -0
- data/doc/user_guide_section_10.md +515 -0
- data/doc/user_guide_section_11.md +708 -0
- data/doc/user_guide_section_2.md +233 -0
- data/doc/user_guide_section_3.md +5 -0
- data/doc/user_guide_section_4.md +221 -0
- data/doc/user_guide_section_5.md +243 -0
- data/doc/user_guide_section_6.md +147 -0
- data/doc/user_guide_section_7.md +311 -0
- data/doc/user_guide_section_8.md +224 -0
- data/doc/user_guide_section_9.md +375 -0
- data/doc/userdoc-toc.txt +88 -0
- data/lib/rouge/lexers/livetext.rb +74 -0
- data/lib/scriptorium/api.rb +640 -0
- data/lib/scriptorium/banner_svg.rb +742 -0
- data/lib/scriptorium/contract.rb +33 -0
- data/lib/scriptorium/exceptions.rb +174 -0
- data/lib/scriptorium/helpers.rb +475 -0
- data/lib/scriptorium/post.rb +195 -0
- data/lib/scriptorium/reddit.rb +83 -0
- data/lib/scriptorium/repo.rb +624 -0
- data/lib/scriptorium/standard_files.rb +515 -0
- data/lib/scriptorium/syntax_highlighter.rb +234 -0
- data/lib/scriptorium/theme.rb +179 -0
- data/lib/scriptorium/version.rb +2 -2
- data/lib/scriptorium/view.rb +976 -0
- data/lib/scriptorium/widgets/featured_posts.rb +149 -0
- data/lib/scriptorium/widgets/links.rb +112 -0
- data/lib/scriptorium/widgets/pages.rb +133 -0
- data/lib/scriptorium/widgets/widget.rb +133 -0
- data/lib/scriptorium.rb +22 -9
- data/lib/skeleton.rb +11 -2
- data/scriptorium.gemspec +15 -4
- data/test/README.md +69 -0
- data/test/all +43 -0
- data/test/api_demo.rb +99 -0
- data/test/assets/imagenotfound.jpg +0 -0
- data/test/assets/images/.DS_Store +0 -0
- data/test/assets/images/README.md +27 -0
- data/test/assets/images/odd_aspect.png +0 -0
- data/test/assets/images/perfect.png +0 -0
- data/test/assets/images/small.png +0 -0
- data/test/assets/images/tall.png +0 -0
- data/test/assets/images/very_tall.png +0 -0
- data/test/assets/images/very_wide.png +0 -0
- data/test/assets/images/wide.png +0 -0
- data/test/assets/testbanner.jpg +0 -0
- data/test/banner_svg/simple_helpers.rb +13 -0
- data/test/banner_svg/unit.rb +768 -0
- data/test/ed_test.rb +204 -0
- data/test/integration/cursor_banner_combinations.rb +193 -0
- data/test/integration/cursor_banner_features.rb +374 -0
- data/test/integration/integration_test.rb +326 -0
- data/test/livetext_plugin_test.rb +229 -0
- data/test/manual/asset_mgmt.rb +67 -0
- data/test/manual/banner-tests/config.txt +3 -0
- data/test/manual/banner-tests/index.html +45 -0
- data/test/manual/banner-tests/test01.html +58 -0
- data/test/manual/banner-tests/test02.html +58 -0
- data/test/manual/banner-tests/test03.html +58 -0
- data/test/manual/banner-tests/test04.html +65 -0
- data/test/manual/banner-tests/test05.html +65 -0
- data/test/manual/banner-tests/test06.html +65 -0
- data/test/manual/banner-tests/test07.html +65 -0
- data/test/manual/banner-tests/test08.html +59 -0
- data/test/manual/banner-tests/test09.html +59 -0
- data/test/manual/banner-tests/test10.html +59 -0
- data/test/manual/banner-tests/test11.html +59 -0
- data/test/manual/banner-tests/test12.html +59 -0
- data/test/manual/banner-tests/test13.html +59 -0
- data/test/manual/banner-tests/test14.html +59 -0
- data/test/manual/banner-tests/test15.html +58 -0
- data/test/manual/banner-tests/test16.html +58 -0
- data/test/manual/banner-tests/test17.html +58 -0
- data/test/manual/banner-tests/test18.html +68 -0
- data/test/manual/banner-tests/test19.html +68 -0
- data/test/manual/banner-tests/test20.html +68 -0
- data/test/manual/banner-tests/test21.html +68 -0
- data/test/manual/banner-tests/test22.html +68 -0
- data/test/manual/banner-tests/test23.html +68 -0
- data/test/manual/banner-tests/test24.html +68 -0
- data/test/manual/banner-tests/test25.html +67 -0
- data/test/manual/banner_environment.rb +192 -0
- data/test/manual/deploy_symlink_demo.rb +142 -0
- data/test/manual/environment.rb +67 -0
- data/test/manual/make_banner.rb +153 -0
- data/test/manual/sample_banner_config.txt +12 -0
- data/test/manual/symlink_demo.rb +117 -0
- data/test/manual/test1.rb +47 -0
- data/test/manual/test2.rb +12 -0
- data/test/manual/test3.rb +38 -0
- data/test/manual/test4.rb +40 -0
- data/test/manual/test5.rb +24 -0
- data/test/manual/test6.rb +73 -0
- data/test/manual/test_banner_combinations.rb +120 -0
- data/test/manual/test_banner_features.rb +306 -0
- data/test/manual/test_banner_from_file.rb +150 -0
- data/test/manual/test_banner_in_header.rb +35 -0
- data/test/manual/test_code_highlighting.rb +68 -0
- data/test/manual/test_complex_header.rb +74 -0
- data/test/manual/test_empty_header.rb +32 -0
- data/test/manual/test_radial_custom.rb +58 -0
- data/test/manual/test_radial_large_radius.rb +52 -0
- data/test/manual/test_svg_debug.rb +47 -0
- data/test/manual/test_syntax_highlighting.rb +147 -0
- data/test/pages-demo/config/currentview.txt +1 -0
- data/test/pages-demo/views/demo/config/bootstrap_css.txt +5 -0
- data/test/pages-demo/views/demo/config/bootstrap_js.txt +4 -0
- data/test/pages-demo/views/demo/config/common.js +57 -0
- data/test/pages-demo/views/demo/config/footer.txt +1 -0
- data/test/pages-demo/views/demo/config/global-head.txt +8 -0
- data/test/pages-demo/views/demo/config/header.txt +1 -0
- data/test/pages-demo/views/demo/config/layout.txt +1 -0
- data/test/pages-demo/views/demo/config/left.txt +1 -0
- data/test/pages-demo/views/demo/config/main.txt +1 -0
- data/test/pages-demo/views/demo/config/right.txt +1 -0
- data/test/pages-demo/views/demo/config.txt +3 -0
- data/test/pages-demo/views/demo/output/panes/footer.html +1 -0
- data/test/pages-demo/views/demo/output/panes/header.html +1 -0
- data/test/pages-demo/views/demo/output/panes/left.html +1 -0
- data/test/pages-demo/views/demo/output/panes/main.html +1 -0
- data/test/pages-demo/views/demo/output/panes/right.html +1 -0
- data/test/rubytext/rubytext_comprehensive_test.rb +307 -0
- data/test/rubytext/rubytext_demo_test.rb +42 -0
- data/test/rubytext/rubytext_testing_guide.md +277 -0
- data/test/run_automated_tests.rb +45 -0
- data/test/scriptorium-TEST-1754622690-146/config/bootstrap_css.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/config/bootstrap_js.txt +4 -0
- data/test/scriptorium-TEST-1754622690-146/config/common.js +57 -0
- data/test/scriptorium-TEST-1754622690-146/config/currentview.txt +1 -0
- data/test/scriptorium-TEST-1754622690-146/config/global-head.txt +9 -0
- data/test/scriptorium-TEST-1754622690-146/config/last_post_num.txt +1 -0
- data/test/scriptorium-TEST-1754622690-146/config/os_helpers.rb +4 -0
- data/test/scriptorium-TEST-1754622690-146/config/widgets.txt +3 -0
- data/test/scriptorium-TEST-1754622690-146/posts/0001/meta.txt +8 -0
- data/test/scriptorium-TEST-1754622690-146/posts/0001/source.lt3 +6 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/README.txt +1 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/config.txt +1 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/initial/post.lt3 +12 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/footer.txt +2 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/header.txt +4 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/left.txt +3 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/main.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/right.txt +3 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/gen/text.css +1 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/layout.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index.lt3 +1 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index_entry.lt3 +14 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/post.lt3 +13 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/widget.lt3 +1 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_css.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_js.txt +4 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/common.js +57 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/deploy.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/footer.txt +2 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/global-head.txt +9 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/header.txt +4 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/layout.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/left.txt +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/main.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/reddit.txt +10 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/right.txt +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/social.txt +7 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/status.txt +7 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config.txt +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/footer.html +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/header.html +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/left.html +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/main.html +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/right.html +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/footer.html +1 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/header.html +1 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/left.html +1 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/main.html +1 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/right.html +1 -0
- data/test/staging/.DS_Store +0 -0
- data/test/syntax_highlighting_test.lt3 +124 -0
- data/test/test_helpers.rb +230 -0
- data/test/tui_editor_integration_test.rb +296 -0
- data/test/tui_integration_test.rb +637 -0
- data/test/unit/api.rb +1056 -0
- data/test/unit/asset_management.rb +245 -0
- data/test/unit/clipboard_test.rb +60 -0
- data/test/unit/contract_test.rb +91 -0
- data/test/unit/core.rb +857 -0
- data/test/unit/deploy_test.rb +187 -0
- data/test/unit/gem_asset_management.rb +189 -0
- data/test/unit/livetext_basic.rb +69 -0
- data/test/unit/livetext_compatibility.rb +89 -0
- data/test/unit/post.rb +244 -0
- data/test/unit/read_commented_file_test.rb +276 -0
- data/test/unit/reddit_test.rb +235 -0
- data/test/unit/repo.rb +548 -0
- data/test/unit/social_test.rb +369 -0
- data/test/unit/symlink_test.rb +213 -0
- data/test/unit/view.rb +431 -0
- data/test/unit/widgets.rb +669 -0
- data/test/wizard_test.rb +123 -0
- data/ui/README.md +67 -0
- data/ui/common/lib/ui_common.rb +8 -0
- data/ui/rubytext/README.md +191 -0
- data/ui/rubytext/bin/scriptorium-rubytext +402 -0
- data/ui/rubytext/lib/rubytext_ui.rb +300 -0
- data/ui/tui/bin/scriptorium +1420 -0
- data/ui/tui/test/tui_test.rb +23 -0
- data/ui/web/app/app.rb +1378 -0
- data/ui/web/app/error_helpers.rb +150 -0
- data/ui/web/app/views/advanced_config.erb +190 -0
- data/ui/web/app/views/asset_management.erb +589 -0
- data/ui/web/app/views/banner_config.erb +200 -0
- data/ui/web/app/views/configure_view.erb +401 -0
- data/ui/web/app/views/dashboard.erb +162 -0
- data/ui/web/app/views/deploy_config.erb +146 -0
- data/ui/web/app/views/edit_pages.erb +195 -0
- data/ui/web/app/views/edit_post.erb +54 -0
- data/ui/web/app/views/error_page.erb +29 -0
- data/ui/web/app/views/header_config.erb +155 -0
- data/ui/web/app/views/layout_config.erb +147 -0
- data/ui/web/app/views/navbar_config.erb +411 -0
- data/ui/web/app/views/view_dashboard.erb +138 -0
- data/ui/web/bin/scriptorium-web +153 -0
- data/ui/web/test/web_basic_test.rb +38 -0
- data/ui/web/test_navbar.txt +7 -0
- data/ui/web/tmp/web_server.log +5 -0
- data/ui/web/tmp/web_server.pid +1 -0
- metadata +360 -5
@@ -0,0 +1,149 @@
|
|
1
|
+
class Scriptorium::Widget::FeaturedPosts < Scriptorium::Widget::ListWidget
|
2
|
+
include Scriptorium::Contract
|
3
|
+
|
4
|
+
Title = "Featured Posts"
|
5
|
+
|
6
|
+
# Invariants
|
7
|
+
def define_invariants
|
8
|
+
invariant { @lines.is_a?(Array) }
|
9
|
+
invariant { Title.is_a?(String) && !Title.empty? }
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(repo, view)
|
13
|
+
assume { repo.is_a?(Scriptorium::Repo) }
|
14
|
+
assume { view.is_a?(Scriptorium::View) }
|
15
|
+
|
16
|
+
super(repo, view)
|
17
|
+
@lines = @data
|
18
|
+
|
19
|
+
define_invariants
|
20
|
+
verify { @lines == @data }
|
21
|
+
check_invariants
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_config
|
25
|
+
check_invariants
|
26
|
+
# No configuration needed for this widget
|
27
|
+
check_invariants
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate
|
31
|
+
check_invariants
|
32
|
+
assume { @view.is_a?(Scriptorium::View) }
|
33
|
+
|
34
|
+
write_main
|
35
|
+
write_card
|
36
|
+
result = true
|
37
|
+
|
38
|
+
verify { result == true }
|
39
|
+
check_invariants
|
40
|
+
result
|
41
|
+
end
|
42
|
+
|
43
|
+
def widget_title
|
44
|
+
check_invariants
|
45
|
+
result = Title
|
46
|
+
verify { result.is_a?(String) && !result.empty? }
|
47
|
+
check_invariants
|
48
|
+
result
|
49
|
+
end
|
50
|
+
|
51
|
+
def card
|
52
|
+
check_invariants
|
53
|
+
assume { @view.is_a?(Scriptorium::View) }
|
54
|
+
assume { @name.is_a?(String) && !@name.empty? }
|
55
|
+
|
56
|
+
file = "#{@view.dir}/widgets/#@name/#@name-card.html"
|
57
|
+
result = read_file(file)
|
58
|
+
|
59
|
+
verify { result.is_a?(String) }
|
60
|
+
check_invariants
|
61
|
+
result
|
62
|
+
end
|
63
|
+
|
64
|
+
def write_main
|
65
|
+
check_invariants
|
66
|
+
# Nothing in this case
|
67
|
+
check_invariants
|
68
|
+
end
|
69
|
+
|
70
|
+
def parse_featured_line(line)
|
71
|
+
check_invariants
|
72
|
+
assume { line.is_a?(String) }
|
73
|
+
|
74
|
+
# Parse line in format: <id> <title>
|
75
|
+
# Title is optional, so we need to handle both cases
|
76
|
+
parts = line.strip.split(/\s+/, 2)
|
77
|
+
if parts.length >= 2
|
78
|
+
result = [parts[0], parts[1]]
|
79
|
+
else
|
80
|
+
result = [parts[0], nil]
|
81
|
+
end
|
82
|
+
|
83
|
+
verify { result.is_a?(Array) && result.length == 2 }
|
84
|
+
check_invariants
|
85
|
+
result
|
86
|
+
end
|
87
|
+
|
88
|
+
def get_post_title(post_id)
|
89
|
+
check_invariants
|
90
|
+
assume { post_id.is_a?(String) && !post_id.empty? }
|
91
|
+
assume { @repo.is_a?(Scriptorium::Repo) }
|
92
|
+
|
93
|
+
# Get the actual post title from metadata
|
94
|
+
post = Scriptorium::Post.new(@repo, post_id)
|
95
|
+
|
96
|
+
# Check if the post actually exists by looking for the meta file
|
97
|
+
if File.exist?(post.meta_file)
|
98
|
+
result = post.title || "Untitled Post"
|
99
|
+
else
|
100
|
+
result = "Error: Post #{post_id} not found"
|
101
|
+
end
|
102
|
+
rescue => e
|
103
|
+
# If post can't be created (invalid ID, etc.), return error message
|
104
|
+
result = "Error: Post #{post_id} not found"
|
105
|
+
ensure
|
106
|
+
verify { result.is_a?(String) && !result.empty? }
|
107
|
+
check_invariants
|
108
|
+
result
|
109
|
+
end
|
110
|
+
|
111
|
+
def featured_post_item(post_id, list_title = nil)
|
112
|
+
check_invariants
|
113
|
+
assume { post_id.is_a?(String) && !post_id.empty? }
|
114
|
+
assume { list_title.nil? || list_title.is_a?(String) }
|
115
|
+
|
116
|
+
# Use actual post title from metadata, show error if post doesn't exist
|
117
|
+
display_title = get_post_title(post_id)
|
118
|
+
|
119
|
+
# Create link to the post (or error message)
|
120
|
+
anchor = %[<a href="javascript:void(0)" onclick="load_main('posts/#{post_id}.html')" style="text-decoration: none;">#{display_title}</a>]
|
121
|
+
result = %[<li class="list-group-item">#{anchor}</li>]
|
122
|
+
|
123
|
+
verify { result.is_a?(String) && result.include?("list-group-item") }
|
124
|
+
check_invariants
|
125
|
+
result
|
126
|
+
end
|
127
|
+
|
128
|
+
def write_card
|
129
|
+
check_invariants
|
130
|
+
assume { @view.is_a?(Scriptorium::View) }
|
131
|
+
assume { @name.is_a?(String) && !@name.empty? }
|
132
|
+
assume { @data.is_a?(Array) }
|
133
|
+
|
134
|
+
tag = @name
|
135
|
+
cardout = "#{@view.dir}/widgets/#@name/#@name-card.html"
|
136
|
+
card_title = Title
|
137
|
+
content = ""
|
138
|
+
|
139
|
+
@data.each do |line|
|
140
|
+
next if line.strip.empty?
|
141
|
+
|
142
|
+
post_id, list_title = parse_featured_line(line)
|
143
|
+
content << featured_post_item(post_id, list_title)
|
144
|
+
end
|
145
|
+
|
146
|
+
write_file(cardout, html_card(card_title, tag, content))
|
147
|
+
check_invariants
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
class Scriptorium::Widget::Links < Scriptorium::Widget::ListWidget
|
2
|
+
include Scriptorium::Contract
|
3
|
+
|
4
|
+
Title = "External links"
|
5
|
+
|
6
|
+
# Invariants
|
7
|
+
def define_invariants
|
8
|
+
invariant { @lines.is_a?(Array) }
|
9
|
+
invariant { Title.is_a?(String) && !Title.empty? }
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(repo, view)
|
13
|
+
assume { repo.is_a?(Scriptorium::Repo) }
|
14
|
+
assume { view.is_a?(Scriptorium::View) }
|
15
|
+
|
16
|
+
super(repo, view)
|
17
|
+
@lines = @data
|
18
|
+
|
19
|
+
define_invariants
|
20
|
+
verify { @lines == @data }
|
21
|
+
check_invariants
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_list
|
25
|
+
check_invariants
|
26
|
+
assume { @lines.is_a?(Array) }
|
27
|
+
|
28
|
+
result = @lines.map { |line| line.chomp.split(/, */, 2) }
|
29
|
+
|
30
|
+
verify { result.is_a?(Array) }
|
31
|
+
check_invariants
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
def load_config
|
36
|
+
check_invariants
|
37
|
+
# No configuration needed for this widget
|
38
|
+
check_invariants
|
39
|
+
end
|
40
|
+
|
41
|
+
def generate
|
42
|
+
check_invariants
|
43
|
+
assume { @view.is_a?(Scriptorium::View) }
|
44
|
+
|
45
|
+
write_main
|
46
|
+
write_card
|
47
|
+
result = true
|
48
|
+
|
49
|
+
verify { result == true }
|
50
|
+
check_invariants
|
51
|
+
result
|
52
|
+
end
|
53
|
+
|
54
|
+
def widget_title
|
55
|
+
check_invariants
|
56
|
+
result = Title
|
57
|
+
verify { result.is_a?(String) && !result.empty? }
|
58
|
+
check_invariants
|
59
|
+
result
|
60
|
+
end
|
61
|
+
|
62
|
+
def card
|
63
|
+
check_invariants
|
64
|
+
assume { @view.is_a?(Scriptorium::View) }
|
65
|
+
assume { @name.is_a?(String) && !@name.empty? }
|
66
|
+
|
67
|
+
file = "#{@view.dir}/widgets/#@name/#@name-card.html"
|
68
|
+
result = read_file(file)
|
69
|
+
|
70
|
+
verify { result.is_a?(String) }
|
71
|
+
check_invariants
|
72
|
+
result
|
73
|
+
end
|
74
|
+
|
75
|
+
def write_main
|
76
|
+
check_invariants
|
77
|
+
# Nothing in this case
|
78
|
+
check_invariants
|
79
|
+
end
|
80
|
+
|
81
|
+
def link_item(url, title)
|
82
|
+
check_invariants
|
83
|
+
assume { url.is_a?(String) && !url.empty? }
|
84
|
+
assume { title.is_a?(String) && !title.empty? }
|
85
|
+
|
86
|
+
anchor = %[<a href="#{url}" target="_blank" style="text-decoration: none;">#{title}</a>]
|
87
|
+
result = %[<li class="list-group-item">#{anchor}</li>]
|
88
|
+
|
89
|
+
verify { result.is_a?(String) && result.include?("list-group-item") }
|
90
|
+
check_invariants
|
91
|
+
result
|
92
|
+
end
|
93
|
+
|
94
|
+
def write_card
|
95
|
+
check_invariants
|
96
|
+
assume { @view.is_a?(Scriptorium::View) }
|
97
|
+
assume { @name.is_a?(String) && !@name.empty? }
|
98
|
+
assume { @data.is_a?(Array) }
|
99
|
+
|
100
|
+
tag = @name
|
101
|
+
cardout = "#{@view.dir}/widgets/#@name/#@name-card.html"
|
102
|
+
# url = "../widgets/#@name/#@name-card.html"
|
103
|
+
card_title = Title
|
104
|
+
content = ""
|
105
|
+
@data.each do |line|
|
106
|
+
url2, title = line.chomp.split(",")
|
107
|
+
content << link_item(url2, title)
|
108
|
+
end
|
109
|
+
write_file(cardout, html_card(card_title, tag, content))
|
110
|
+
check_invariants
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
class Scriptorium::Widget::Pages < Scriptorium::Widget::ListWidget
|
2
|
+
include Scriptorium::Contract
|
3
|
+
|
4
|
+
Title = "Pages"
|
5
|
+
attr_reader :data
|
6
|
+
|
7
|
+
# Invariants
|
8
|
+
def define_invariants
|
9
|
+
invariant { @pages_dir.is_a?(String) && !@pages_dir.empty? }
|
10
|
+
invariant { @data.is_a?(Array) }
|
11
|
+
invariant { Title.is_a?(String) && !Title.empty? }
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(repo, view)
|
15
|
+
assume { repo.is_a?(Scriptorium::Repo) }
|
16
|
+
assume { view.is_a?(Scriptorium::View) }
|
17
|
+
|
18
|
+
super(repo, view)
|
19
|
+
@pages_dir = "#{@view.dir}/pages"
|
20
|
+
|
21
|
+
define_invariants
|
22
|
+
verify { @pages_dir.include?("pages") }
|
23
|
+
check_invariants
|
24
|
+
end
|
25
|
+
|
26
|
+
def load_config
|
27
|
+
check_invariants
|
28
|
+
# No configuration needed for this widget
|
29
|
+
check_invariants
|
30
|
+
end
|
31
|
+
|
32
|
+
def generate
|
33
|
+
check_invariants
|
34
|
+
assume { @view.is_a?(Scriptorium::View) }
|
35
|
+
|
36
|
+
write_main
|
37
|
+
write_card
|
38
|
+
result = true
|
39
|
+
|
40
|
+
verify { result == true }
|
41
|
+
check_invariants
|
42
|
+
result
|
43
|
+
end
|
44
|
+
|
45
|
+
def widget_title
|
46
|
+
check_invariants
|
47
|
+
result = Title
|
48
|
+
verify { result.is_a?(String) && !result.empty? }
|
49
|
+
check_invariants
|
50
|
+
result
|
51
|
+
end
|
52
|
+
|
53
|
+
def card
|
54
|
+
check_invariants
|
55
|
+
assume { @view.is_a?(Scriptorium::View) }
|
56
|
+
assume { @name.is_a?(String) && !@name.empty? }
|
57
|
+
|
58
|
+
file = "#{@view.dir}/widgets/#@name/#@name-card.html"
|
59
|
+
result = read_file(file)
|
60
|
+
|
61
|
+
verify { result.is_a?(String) }
|
62
|
+
check_invariants
|
63
|
+
result
|
64
|
+
end
|
65
|
+
|
66
|
+
def write_main
|
67
|
+
check_invariants
|
68
|
+
# Nothing in this case
|
69
|
+
check_invariants
|
70
|
+
end
|
71
|
+
|
72
|
+
def extract_title_from_html(html_content)
|
73
|
+
check_invariants
|
74
|
+
assume { html_content.is_a?(String) }
|
75
|
+
|
76
|
+
# Try to extract title from <title> tag first
|
77
|
+
if match = html_content.match(/<title[^>]*>(.*?)<\/title>/i)
|
78
|
+
result = match[1].strip
|
79
|
+
elsif match = html_content.match(/<h1[^>]*>(.*?)<\/h1>/i)
|
80
|
+
# Fall back to first <h1> tag
|
81
|
+
result = match[1].strip
|
82
|
+
else
|
83
|
+
# Last resort: use filename
|
84
|
+
result = "Untitled"
|
85
|
+
end
|
86
|
+
|
87
|
+
verify { result.is_a?(String) && !result.empty? }
|
88
|
+
check_invariants
|
89
|
+
result
|
90
|
+
end
|
91
|
+
|
92
|
+
def page_item(filename, title)
|
93
|
+
check_invariants
|
94
|
+
assume { filename.is_a?(String) && !filename.empty? }
|
95
|
+
assume { title.is_a?(String) && !title.empty? }
|
96
|
+
|
97
|
+
anchor = %[<a href="javascript:void(0)" onclick="load_main('pages/#{filename}.html')" style="text-decoration: none;">#{title}</a>]
|
98
|
+
result = %[<li class="list-group-item">#{anchor}</li>]
|
99
|
+
|
100
|
+
verify { result.is_a?(String) && result.include?("list-group-item") }
|
101
|
+
check_invariants
|
102
|
+
result
|
103
|
+
end
|
104
|
+
|
105
|
+
def write_card
|
106
|
+
check_invariants
|
107
|
+
assume { @view.is_a?(Scriptorium::View) }
|
108
|
+
assume { @name.is_a?(String) && !@name.empty? }
|
109
|
+
assume { @data.is_a?(Array) }
|
110
|
+
assume { @pages_dir.is_a?(String) && !@pages_dir.empty? }
|
111
|
+
|
112
|
+
tag = @name
|
113
|
+
cardout = "#{@view.dir}/widgets/#@name/#@name-card.html"
|
114
|
+
card_title = Title
|
115
|
+
content = ""
|
116
|
+
|
117
|
+
@data.each do |filename|
|
118
|
+
html_file = "#{@pages_dir}/#{filename}.html"
|
119
|
+
|
120
|
+
if File.exist?(html_file)
|
121
|
+
html_content = read_file(html_file)
|
122
|
+
title = extract_title_from_html(html_content)
|
123
|
+
content << page_item(filename, title)
|
124
|
+
else
|
125
|
+
# Page doesn't exist, skip it
|
126
|
+
next
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
write_file(cardout, html_card(card_title, tag, content))
|
131
|
+
check_invariants
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
class Scriptorium::Widget
|
2
|
+
include Scriptorium::Helpers
|
3
|
+
include Scriptorium::Contract
|
4
|
+
attr_reader :repo, :view, :config, :name, :path
|
5
|
+
|
6
|
+
# Invariants
|
7
|
+
def define_invariants
|
8
|
+
invariant { @repo.is_a?(Scriptorium::Repo) }
|
9
|
+
invariant { @view.is_a?(Scriptorium::View) }
|
10
|
+
invariant { @name.is_a?(String) && !@name.empty? }
|
11
|
+
invariant { @path.is_a?(String) && !@path.empty? }
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(repo, view)
|
15
|
+
assume { repo.is_a?(Scriptorium::Repo) }
|
16
|
+
assume { view.is_a?(Scriptorium::View) }
|
17
|
+
|
18
|
+
@repo = repo
|
19
|
+
@view = view
|
20
|
+
@config = load_config
|
21
|
+
@name = self.class.to_s.split("::").last.downcase
|
22
|
+
@path = "#{@view.dir}/widgets/#@name"
|
23
|
+
|
24
|
+
define_invariants
|
25
|
+
verify { @repo == repo }
|
26
|
+
verify { @view == view }
|
27
|
+
check_invariants
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate
|
31
|
+
raise NotImplementedError, "Subclasses must implement the 'generate' method"
|
32
|
+
end
|
33
|
+
|
34
|
+
def load_config
|
35
|
+
raise NotImplementedError, "Subclasses must implement 'load_config'"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Common HTML body wrapper for widgets
|
39
|
+
def html_body(css = nil)
|
40
|
+
check_invariants
|
41
|
+
assume { css.nil? || css.is_a?(String) }
|
42
|
+
|
43
|
+
result = "<html>" +
|
44
|
+
(css ? "<head><style>#{css}</style></head>" : "") +
|
45
|
+
"<body>" + yield + "</body></html>"
|
46
|
+
|
47
|
+
verify { result.is_a?(String) && result.include?("<html>") }
|
48
|
+
check_invariants
|
49
|
+
result
|
50
|
+
end
|
51
|
+
|
52
|
+
# Common HTML card structure for widgets like Links
|
53
|
+
def html_card(card_title = "Widget Card", tag = "widget", content)
|
54
|
+
check_invariants
|
55
|
+
assume { card_title.is_a?(String) }
|
56
|
+
assume { tag.is_a?(String) }
|
57
|
+
assume { content.is_a?(String) }
|
58
|
+
|
59
|
+
result = <<~EOS
|
60
|
+
<div class="card mb-3">
|
61
|
+
<div class="card-body">
|
62
|
+
<h5 class="card-title">
|
63
|
+
<button type="button" class="btn btn-primary" data-bs-toggle="collapse" data-bs-target="##{tag}">+</button>
|
64
|
+
<a href="javascript:void(0)" onclick="javascript:load_main('#{tag}-main.html')" style="text-decoration: none; color: black">#{card_title}</a>
|
65
|
+
</h5>
|
66
|
+
<div class="collapse" id="#{tag}">
|
67
|
+
#{content}
|
68
|
+
</div>
|
69
|
+
</div>
|
70
|
+
</div>
|
71
|
+
EOS
|
72
|
+
|
73
|
+
verify { result.is_a?(String) && result.include?("card") }
|
74
|
+
check_invariants
|
75
|
+
result
|
76
|
+
end
|
77
|
+
|
78
|
+
# A generic container for widget content (for those that don't use cards)
|
79
|
+
def html_container(content)
|
80
|
+
check_invariants
|
81
|
+
assume { content.is_a?(String) }
|
82
|
+
|
83
|
+
result = <<~HTML
|
84
|
+
<div class="widget-container">
|
85
|
+
#{content}
|
86
|
+
</div>
|
87
|
+
HTML
|
88
|
+
|
89
|
+
verify { result.is_a?(String) && result.include?("widget-container") }
|
90
|
+
check_invariants
|
91
|
+
result
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
####
|
97
|
+
|
98
|
+
|
99
|
+
class Scriptorium::Widget::ListWidget < Scriptorium::Widget
|
100
|
+
include Scriptorium::Contract
|
101
|
+
|
102
|
+
# Invariants
|
103
|
+
def define_invariants
|
104
|
+
invariant { @list.is_a?(String) && !@list.empty? }
|
105
|
+
invariant { @data.is_a?(Array) }
|
106
|
+
end
|
107
|
+
|
108
|
+
def initialize(repo, view)
|
109
|
+
assume { repo.is_a?(Scriptorium::Repo) }
|
110
|
+
assume { view.is_a?(Scriptorium::View) }
|
111
|
+
|
112
|
+
super(repo, view)
|
113
|
+
@list = "#{@path}/list.txt"
|
114
|
+
@data = load_data
|
115
|
+
|
116
|
+
define_invariants
|
117
|
+
verify { @list.include?("list.txt") }
|
118
|
+
verify { @data.is_a?(Array) }
|
119
|
+
check_invariants
|
120
|
+
end
|
121
|
+
|
122
|
+
# Method to load the list data from the list.txt file
|
123
|
+
def load_data
|
124
|
+
check_invariants
|
125
|
+
assume { @list.is_a?(String) && !@list.empty? }
|
126
|
+
|
127
|
+
result = read_file(@list, lines: true, chomp: true).map(&:strip)
|
128
|
+
|
129
|
+
verify { result.is_a?(Array) }
|
130
|
+
check_invariants
|
131
|
+
result
|
132
|
+
end
|
133
|
+
end
|
data/lib/scriptorium.rb
CHANGED
@@ -1,10 +1,23 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
require 'livetext'
|
2
|
+
require 'find'
|
3
|
+
require 'htmlbeautifier'
|
4
|
+
require 'pp'
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
require_relative "skeleton"
|
7
|
+
require_relative "scriptorium/version"
|
8
|
+
require_relative "scriptorium/contract"
|
9
|
+
require_relative "scriptorium/repo"
|
10
|
+
require_relative "scriptorium/post"
|
11
|
+
require_relative "scriptorium/theme"
|
12
|
+
require_relative "scriptorium/view"
|
13
|
+
require_relative "scriptorium/exceptions"
|
14
|
+
require_relative "scriptorium/helpers"
|
15
|
+
require_relative "scriptorium/standard_files"
|
16
|
+
require_relative "scriptorium/banner_svg"
|
17
|
+
require_relative "scriptorium/reddit"
|
18
|
+
require_relative "scriptorium/widgets/widget"
|
19
|
+
require_relative "scriptorium/widgets/links"
|
20
|
+
require_relative "scriptorium/widgets/pages"
|
21
|
+
require_relative "scriptorium/widgets/featured_posts"
|
22
|
+
require_relative "scriptorium/api"
|
23
|
+
require_relative "scriptorium/syntax_highlighter"
|
data/lib/skeleton.rb
CHANGED
data/scriptorium.gemspec
CHANGED
@@ -21,23 +21,34 @@ spec = Gem::Specification.new do |s|
|
|
21
21
|
s.authors = ["Hal Fulton"]
|
22
22
|
s.email = 'rubyhacker@gmail.com'
|
23
23
|
s.executables << "sblog"
|
24
|
+
s.executables << "scriptorium"
|
24
25
|
|
25
26
|
s.add_runtime_dependency 'livetext', '~> 0.9', '>= 0.9.41'
|
26
27
|
s.add_runtime_dependency 'rubytext', '~> 0.1', '>= 0.1.26'
|
27
28
|
s.add_runtime_dependency 'rouge', '~> 3.25', '>= 3.25.0'
|
29
|
+
s.add_runtime_dependency 'sinatra', '~> 3.0', '>= 3.0.0'
|
30
|
+
s.add_runtime_dependency 'redd', '~> 0.8.8'
|
31
|
+
s.add_runtime_dependency 'htmlbeautifier', '~> 1.4', '>= 1.4.0'
|
32
|
+
s.add_runtime_dependency 'clipboard', '~> 1.3', '>= 1.3.6'
|
28
33
|
|
29
34
|
s.add_development_dependency 'minitest', '~> 5.10', '>= 5.10.0'
|
30
35
|
|
31
36
|
# Files...
|
32
37
|
|
33
|
-
|
34
|
-
|
35
|
-
Find.find("doc").to_a
|
38
|
+
# Core library files
|
39
|
+
main = Find.find("lib").to_a +
|
40
|
+
Find.find("doc").to_a +
|
41
|
+
Find.find("assets").to_a
|
42
|
+
|
43
|
+
# UI-specific files
|
44
|
+
ui_files = Find.find("ui").to_a
|
45
|
+
|
46
|
+
# Test files
|
36
47
|
test = Find.find("test").to_a
|
37
48
|
|
38
49
|
misc = %w[./README.lt3 ./README.md ./scriptorium.gemspec]
|
39
50
|
|
40
|
-
s.files = main + misc + test
|
51
|
+
s.files = main + ui_files + misc + test
|
41
52
|
s.homepage = 'https://github.com/Hal9000/scriptorium'
|
42
53
|
s.license = "Ruby"
|
43
54
|
s.post_install_message =
|
data/test/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# Scriptorium Test Suite
|
2
|
+
|
3
|
+
## Quick Start
|
4
|
+
|
5
|
+
### Run All Tests (Unit + Automated Manual Tests)
|
6
|
+
```bash
|
7
|
+
ruby test/run_automated_tests.rb
|
8
|
+
```
|
9
|
+
|
10
|
+
### Run Unit Tests Only
|
11
|
+
```bash
|
12
|
+
ruby test/all
|
13
|
+
```
|
14
|
+
|
15
|
+
### Run Individual Manual Tests
|
16
|
+
```bash
|
17
|
+
# Interactive mode (opens browser)
|
18
|
+
ruby test/manual/test1.rb
|
19
|
+
|
20
|
+
# Automated mode (no browser, just validation)
|
21
|
+
ruby test/manual/test1.rb --automated
|
22
|
+
```
|
23
|
+
|
24
|
+
## Test Types
|
25
|
+
|
26
|
+
### Unit Tests (`test/unit/`)
|
27
|
+
- **Core functionality tests** - Basic helper methods, file operations
|
28
|
+
- **Repo tests** - Repository creation, management, post handling
|
29
|
+
- **Post tests** - Individual post functionality
|
30
|
+
- **View tests** - View generation and management
|
31
|
+
- **Banner SVG tests** - SVG banner generation and validation
|
32
|
+
|
33
|
+
### Manual Tests (`test/manual/`)
|
34
|
+
- **Integration tests** - End-to-end workflows
|
35
|
+
- **Banner feature tests** - Visual verification of SVG banners
|
36
|
+
- **Widget tests** - Sidebar widgets and functionality
|
37
|
+
- **Layout tests** - Page layout and styling
|
38
|
+
|
39
|
+
## Automated Testing
|
40
|
+
|
41
|
+
All manual tests support `--automated` mode for CI/CD:
|
42
|
+
- No browser interaction required
|
43
|
+
- Validates file generation
|
44
|
+
- Returns exit codes for automation
|
45
|
+
- Use `--automated` as the **last** parameter
|
46
|
+
|
47
|
+
## Environment Setup
|
48
|
+
|
49
|
+
The test environment is automatically configured:
|
50
|
+
- PATH is set to include rbenv shims
|
51
|
+
- Test repositories are created in `test/scriptorium-TEST/`
|
52
|
+
- Webrick server starts on port 8000 for manual tests
|
53
|
+
- Cleanup happens automatically
|
54
|
+
|
55
|
+
## Examples
|
56
|
+
|
57
|
+
```bash
|
58
|
+
# Run all tests
|
59
|
+
ruby test/run_automated_tests.rb
|
60
|
+
|
61
|
+
# Run specific manual test interactively
|
62
|
+
ruby test/manual/test_banner_combinations.rb
|
63
|
+
|
64
|
+
# Run specific manual test in automated mode
|
65
|
+
ruby test/manual/test_banner_combinations.rb --automated
|
66
|
+
|
67
|
+
# Run test3 with specific view
|
68
|
+
ruby test/manual/test3.rb blog1 --automated
|
69
|
+
```
|