diff --git a/lib/posts/json_feed.rb b/lib/posts/json_feed.rb index 920c49e..345ffe7 100644 --- a/lib/posts/json_feed.rb +++ b/lib/posts/json_feed.rb @@ -60,7 +60,8 @@ module Pressa permalink = @site.url_for(post.path) item = {} - item[:url] = post.link_post? ? post.link : permalink + item[:url] = permalink + item[:external_url] = post.link if post.link_post? item[:tags] = post.tags unless post.tags.empty? item[:content_html] = content_html item[:title] = post.link_post? ? "→ #{post.title}" : post.title diff --git a/lib/posts/writer.rb b/lib/posts/writer.rb index 70c9f92..e934a5b 100644 --- a/lib/posts/writer.rb +++ b/lib/posts/writer.rb @@ -132,12 +132,11 @@ module Pressa page_scripts:, page_styles:, page_description:, - page_type: + page_type:, + content: ) - layout.call do - content.call - end + layout.call end end end diff --git a/lib/projects/plugin.rb b/lib/projects/plugin.rb index 3c1650d..2e9c1bd 100644 --- a/lib/projects/plugin.rb +++ b/lib/projects/plugin.rb @@ -75,12 +75,11 @@ module Pressa canonical_url:, page_scripts:, page_styles:, - page_description: + page_description:, + content: ) - layout.call do - content.call - end + layout.call end end end diff --git a/lib/site_generator.rb b/lib/site_generator.rb index babc92d..c45d451 100644 --- a/lib/site_generator.rb +++ b/lib/site_generator.rb @@ -10,6 +10,8 @@ module Pressa end def generate(source_path:, target_path:) + validate_paths!(source_path:, target_path:) + FileUtils.rm_rf(target_path) FileUtils.mkdir_p(target_path) @@ -23,6 +25,22 @@ module Pressa private + def validate_paths!(source_path:, target_path:) + source_abs = absolute_path(source_path) + target_abs = absolute_path(target_path) + return unless contains_path?(container: target_abs, path: source_abs) + + raise ArgumentError, 'target_path must not be the same as or contain source_path' + end + + def absolute_path(path) + File.exist?(path) ? File.realpath(path) : File.expand_path(path) + end + + def contains_path?(container:, path:) + path == container || path.start_with?("#{container}#{File::SEPARATOR}") + end + def copy_static_files(source_path, target_path) public_dir = File.join(source_path, 'public') return unless Dir.exist?(public_dir) diff --git a/lib/utils/markdown_renderer.rb b/lib/utils/markdown_renderer.rb index c4aaad6..b30c27a 100644 --- a/lib/utils/markdown_renderer.rb +++ b/lib/utils/markdown_renderer.rb @@ -5,10 +5,6 @@ require_relative '../site' require_relative '../views/layout' require_relative '../views/icons' -class String - include Phlex::SGML::SafeObject -end - module Pressa module Utils class MarkdownRenderer @@ -90,13 +86,11 @@ module Pressa page_subtitle:, canonical_url:, page_description:, - page_type: + page_type:, + content: PageView.new(page_title: page_subtitle, body:) ) - content_view = PageView.new(page_title: page_subtitle, body:) - layout.call do - content_view.call - end + layout.call end class PageView < Phlex::HTML @@ -108,12 +102,12 @@ module Pressa def view_template article(class: 'container') do h1 { @page_title } - raw(@body) + raw(safe(@body)) end div(class: 'row clearfix') do p(class: 'fin') do - raw(Views::Icons.code) + raw(safe(Views::Icons.code)) end end end diff --git a/lib/views/feed_post_view.rb b/lib/views/feed_post_view.rb index 57ba351..653e414 100644 --- a/lib/views/feed_post_view.rb +++ b/lib/views/feed_post_view.rb @@ -11,7 +11,7 @@ module Pressa def view_template div do p(class: 'time') { @post.formatted_date } - raw(@post.body) + raw(safe(@post.body)) p do a(class: 'permalink', href: @site.url_for(@post.path)) { '∞' } end diff --git a/lib/views/layout.rb b/lib/views/layout.rb index df5e39a..483359a 100644 --- a/lib/views/layout.rb +++ b/lib/views/layout.rb @@ -12,7 +12,8 @@ module Pressa :page_type, :canonical_url, :page_scripts, - :page_styles + :page_styles, + :content def initialize( site:, @@ -21,7 +22,8 @@ module Pressa page_description: nil, page_type: 'website', page_scripts: [], - page_styles: [] + page_styles: [], + content: nil ) @site = site @page_subtitle = page_subtitle @@ -30,13 +32,14 @@ module Pressa @canonical_url = canonical_url @page_scripts = page_scripts @page_styles = page_styles + @content = content end def format_output? true end - def view_template(&block) + def view_template doctype html(lang: 'en') do @@ -92,7 +95,7 @@ module Pressa body do render_header - instance_exec(&block) if block + render(content) if content render_footer render_scripts end @@ -140,17 +143,17 @@ module Pressa ul do li(class: 'mastodon') do a(rel: 'me', 'aria-label': 'Mastodon', href: 'https://techhub.social/@sjs') do - raw(Icons.mastodon) + raw(safe(Icons.mastodon)) end end li(class: 'github') do a('aria-label': 'GitHub', href: 'https://github.com/samsonjs') do - raw(Icons.github) + raw(safe(Icons.github)) end end li(class: 'rss') do a('aria-label': 'RSS', href: site.url_for('/feed.xml')) do - raw(Icons.rss) + raw(safe(Icons.rss)) end end end diff --git a/lib/views/post_view.rb b/lib/views/post_view.rb index 6a27879..d6cc155 100644 --- a/lib/views/post_view.rb +++ b/lib/views/post_view.rb @@ -1,10 +1,6 @@ require 'phlex' require_relative 'icons' -class String - include Phlex::SGML::SafeObject -end - module Pressa module Views class PostView < Phlex::HTML @@ -28,12 +24,12 @@ module Pressa a(href: @post.path, class: 'permalink') { '∞' } end - raw(@post.body) + raw(safe(@post.body)) end div(class: 'row clearfix') do p(class: 'fin') do - raw(Icons.code) + raw(safe(Icons.code)) end end end diff --git a/lib/views/project_view.rb b/lib/views/project_view.rb index 6a8e495..458a6a4 100644 --- a/lib/views/project_view.rb +++ b/lib/views/project_view.rb @@ -44,7 +44,7 @@ module Pressa div(class: 'row clearfix') do p(class: 'fin') do - raw(Icons.code) + raw(safe(Icons.code)) end end diff --git a/lib/views/projects_view.rb b/lib/views/projects_view.rb index d1d221c..3481a73 100644 --- a/lib/views/projects_view.rb +++ b/lib/views/projects_view.rb @@ -25,7 +25,7 @@ module Pressa div(class: 'row clearfix') do p(class: 'fin') do - raw(Icons.code) + raw(safe(Icons.code)) end end end diff --git a/spec/posts/json_feed_spec.rb b/spec/posts/json_feed_spec.rb new file mode 100644 index 0000000..4944deb --- /dev/null +++ b/spec/posts/json_feed_spec.rb @@ -0,0 +1,73 @@ +require 'spec_helper' +require 'json' +require 'tmpdir' + +RSpec.describe Pressa::Posts::JSONFeedWriter do + let(:site) do + Pressa::Site.new( + author: 'Sami Samhuri', + email: 'sami@samhuri.net', + title: 'samhuri.net', + description: 'blog', + url: 'https://samhuri.net', + image_url: 'https://samhuri.net/images/me.jpg' + ) + end + + let(:posts_by_year) { double('posts_by_year', recent_posts: [post]) } + let(:writer) { described_class.new(site:, posts_by_year:) } + + context 'for link posts' do + let(:post) do + Pressa::Posts::Post.new( + slug: 'github-flow-like-a-pro', + title: 'GitHub Flow Like a Pro', + author: 'Sami Samhuri', + date: DateTime.parse('2015-05-28T07:42:27-07:00'), + formatted_date: '28th May, 2015', + link: 'http://haacked.com/archive/2014/07/28/github-flow-aliases/', + body: '
hello
', + excerpt: 'hello...', + path: '/posts/2015/05/github-flow-like-a-pro' + ) + end + + it 'uses permalink as url and keeps external_url for destination links' do + Dir.mktmpdir do |dir| + writer.write_feed(target_path: dir, limit: 30) + feed = JSON.parse(File.read(File.join(dir, 'feed.json'))) + item = feed.fetch('items').first + + expect(item.fetch('id')).to eq('https://samhuri.net/posts/2015/05/github-flow-like-a-pro') + expect(item.fetch('url')).to eq('https://samhuri.net/posts/2015/05/github-flow-like-a-pro') + expect(item.fetch('external_url')).to eq('http://haacked.com/archive/2014/07/28/github-flow-aliases/') + end + end + end + + context 'for regular posts' do + let(:post) do + Pressa::Posts::Post.new( + slug: 'swift-optional-or', + title: 'Swift Optional OR', + author: 'Sami Samhuri', + date: DateTime.parse('2017-10-01T10:00:00-07:00'), + formatted_date: '1st October, 2017', + body: 'hello
', + excerpt: 'hello...', + path: '/posts/2017/10/swift-optional-or' + ) + end + + it 'omits external_url' do + Dir.mktmpdir do |dir| + writer.write_feed(target_path: dir, limit: 30) + feed = JSON.parse(File.read(File.join(dir, 'feed.json'))) + item = feed.fetch('items').first + + expect(item.fetch('url')).to eq('https://samhuri.net/posts/2017/10/swift-optional-or') + expect(item).not_to have_key('external_url') + end + end + end +end diff --git a/spec/site_generator_spec.rb b/spec/site_generator_spec.rb new file mode 100644 index 0000000..786e312 --- /dev/null +++ b/spec/site_generator_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' +require 'fileutils' +require 'tmpdir' + +RSpec.describe Pressa::SiteGenerator do + let(:site) do + Pressa::Site.new( + author: 'Sami Samhuri', + email: 'sami@samhuri.net', + title: 'samhuri.net', + description: 'blog', + url: 'https://samhuri.net', + plugins: [], + renderers: [] + ) + end + + it 'rejects a target path that matches the source path' do + Dir.mktmpdir do |dir| + FileUtils.mkdir_p(File.join(dir, 'public')) + source_file = File.join(dir, 'public', 'keep.txt') + File.write(source_file, 'safe') + + generator = described_class.new(site:) + + expect { + generator.generate(source_path: dir, target_path: dir) + }.to raise_error(ArgumentError, /must not be the same as or contain source_path/) + + expect(File.read(source_file)).to eq('safe') + end + end +end diff --git a/spec/views/layout_spec.rb b/spec/views/layout_spec.rb new file mode 100644 index 0000000..3325ab5 --- /dev/null +++ b/spec/views/layout_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + +RSpec.describe Pressa::Views::Layout do + class TestContentView < Phlex::HTML + def view_template + article do + h1 { 'Hello' } + end + end + end + + let(:site) do + Pressa::Site.new( + author: 'Sami Samhuri', + email: 'sami@samhuri.net', + title: 'samhuri.net', + description: 'blog', + url: 'https://samhuri.net' + ) + end + + it 'renders child components as HTML instead of escaped text' do + html = described_class.new( + site:, + canonical_url: 'https://samhuri.net/posts/', + content: TestContentView.new + ).call + + expect(html).to include('