diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 58b0017..ce8fe4e 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
ruby:
- - '3.2.0'
+ - '3.4.0'
steps:
- uses: actions/checkout@v3
- name: Set up Ruby
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d32b747..ba69557 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,72 +5,76 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-* `Added` for new features.
-* `Changed` for changes in existing functionality.
-* `Deprecated` for soon-to-be removed features.
-* `Removed` for now removed features.
-* `Fixed` for any bug fixes.
+- `Added` for new features.
+- `Changed` for changes in existing functionality.
+- `Deprecated` for soon-to-be removed features.
+- `Removed` for now removed features.
+- `Fixed` for any bug fixes.
+
+## [1.4.0] - 2025-08-23
-## [Unreleased]
+### Added
+
+- RSS
## [1.3.0] - 2024-01-05
### Added
-[#3](https://github.com/carlwiedemann/foresite/pull/3) version & watch commands (not sure how to test these best right now).
+- version & watch commands (not sure how to test these best right now).
## [1.2.0] - 2023-02-11
### Added
-[#2](https://github.com/carlwiedemann/foresite/pull/2) Dynamic `
` tag based on post title
+- Dynamic `` tag based on post title
### Fixed
-* Typo in README.
-* Link to blog post in README.
+- Typo in README.
+- Link to blog post in README.
## [1.1.3] - 2023-01-16
### Fixed
-* Use the top-level directory for the index.html file because you can't set subdirectories for github pages.
+- Use the top-level directory for the index.html file because you can't set subdirectories for github pages.
## [1.1.2] - 2023-01-16
### Fixed
-* DRYing things up.
+- DRYing things up.
## [1.1.1] - 2023-01-15
### Fixed
-* Small typo in gemspec.
+- Small typo in gemspec.
## [1.1.0] - 2023-01-15
### Added
-* Can use templates for both post markdown and links list on index page.
+- Can use templates for both post markdown and links list on index page.
### Fixed
-* Reverse chronological order for posts on index page.
-* Fleshed-out README and fixed typos.
+- Reverse chronological order for posts on index page.
+- Fleshed-out README and fixed typos.
## [1.0.1] - 2023-01-15
### Changed
-* Fleshed-out README and CHANGELOG files from default boilerplate.
+- Fleshed-out README and CHANGELOG files from default boilerplate.
### Fixed
-* Bug with trailing non-alphanumeric characters in post titles.
+- Bug with trailing non-alphanumeric characters in post titles.
## [1.0.0] - 2023-01-14
### Added
-* Initial stable release, a global executable that provides the ability to generate HTML from markdown.
+- Initial stable release, a global executable that provides the ability to generate HTML from markdown.
diff --git a/README.md b/README.md
index 73a8b7c..6bf44ee 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,7 @@ Create a project directory for your site and run `foresite init` from within it:
Created erb/post.md.erb
Created erb/wrapper.html.erb
Created erb/_list.html.erb
+ Created erb/feed.xml.erb
Three subdirectories are created, along with three [ERB](https://docs.ruby-lang.org/en/3.2/ERB.html) template files.
@@ -52,6 +53,7 @@ Some facts:
* `post.md.erb` is the default markdown file for every post.
* `wrapper.html.erb` is a HTML wrapper template for every generated HTML file.
* `_list.html.erb` is a HTML template partial for the list of posts on the `index.html` page.
+ * `feed.xml.erb` is a XML template for an RSS feed.
### 2. Write your first post
@@ -69,8 +71,8 @@ A single markdown file is created in the `md` subdirectory. **This file is meant
Some facts:
-* The title is the first line formatted as H1 (mandatory).
-* Current date in YYYY-MM-DD format is the first markdown paragraph (optional).
+* The title is the first line formatted as H1.
+* Current date in YYYY-MM-DD format is the first markdown paragraph.
* Current date and title are "slugified" for filename.
### 3. Modify templates as desired
@@ -81,6 +83,8 @@ Some facts:
`_list.html.erb` is used to generate the `
` list of posts on the `index.html` file. Modify to show posts in a different way.
+`feed.xml.erb` is an RSS feed, it will require a `title` as well as a `base_url` for where you host your site.
+
### 4. Generate HTML from markdown
Run `foresite build` to create HTML in the `post` subdirectory and the `index.html` file:
@@ -88,8 +92,9 @@ Run `foresite build` to create HTML in the `post` subdirectory and the `index.ht
$ foresite build
Created post/2023-01-15-welcome-to-my-site.html
Created index.html
+ Created feed.xml
-In this example, two HTML files are created.
+In this example, two HTML files and an XML file are created.
Some facts:
@@ -97,7 +102,8 @@ Some facts:
* A single `index.html` file shows a list of links to all posts in reverse-chronological order, prefixed with post date.
* Post titles are parsed from the first H1 tag in each post markdown file.
* Post dates are parsed from the post markdown filename.
-* Re-running `foresite build` removes and recreates all HTML files in the `post` subdirectory as well as the `index.html` file.
+* The `feed.xml` file reflects the list posts in RSS 2.0 format.
+* Re-running `foresite build` removes and recreates all HTML files in the `post` subdirectory as well as the `index.html` file and `feed.xml` file.
In this example, the `index.html` will contain:
diff --git a/foresite.gemspec b/foresite.gemspec
index eb1f27f..5317f04 100644
--- a/foresite.gemspec
+++ b/foresite.gemspec
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
spec.summary = "An extremely minimal static site generator."
spec.homepage = "https://github.com/carlwiedemann/foresite"
spec.license = "MIT"
- spec.required_ruby_version = ">= 2.7.0"
+ spec.required_ruby_version = ">= 3.3.0"
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = spec.homepage
@@ -28,19 +28,20 @@ Gem::Specification.new do |spec|
"lib/foresite/version.rb",
"lib/skeleton/_list.html.erb",
"lib/skeleton/post.md.erb",
- "lib/skeleton/wrapper.html.erb"
+ "lib/skeleton/wrapper.html.erb",
+ "lib/skeleton/feed.xml.erb"
]
spec.bindir = "bin"
spec.executables = spec.files.grep(%r{\Abin/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
- spec.add_dependency "kramdown", "~> 2.4"
- spec.add_dependency "thor", "~> 1.2"
- spec.add_dependency "zeitwerk", "~> 2.6"
+ spec.add_dependency "kramdown", "~> 2.5"
+ spec.add_dependency "thor", "~> 1.4"
+ spec.add_dependency "zeitwerk", "~> 2.7"
spec.add_dependency "filewatcher", "~> 2.1"
- spec.add_development_dependency "rspec", "~> 3.2"
- spec.add_development_dependency "standard", "~> 1.3"
+ spec.add_development_dependency "rspec", "~> 3.13"
+ spec.add_development_dependency "standard", "~> 1.50"
spec.add_development_dependency "rake", "~> 13"
end
diff --git a/lib/foresite.rb b/lib/foresite.rb
index 50c6e1a..7c78bd3 100644
--- a/lib/foresite.rb
+++ b/lib/foresite.rb
@@ -17,6 +17,7 @@ module Foresite
FILENAME_POST_MD = "post.md.erb"
FILENAME_WRAPPER_HTML = "wrapper.html.erb"
FILENAME_LIST_HTML = "_list.html.erb"
+ FILENAME_FEED_XML = "feed.xml.erb"
ENV_ROOT = "FORESITE_ROOT"
@@ -68,6 +69,10 @@ def self.get_path_to_index_file
File.join(get_path_to_root, "index.html")
end
+ def self.get_path_to_feed_file
+ File.join(get_path_to_root, "feed.xml")
+ end
+
def self.relative_path(full_path)
full_path.gsub(get_path_to_root, "").gsub(Regexp.new("^#{File::SEPARATOR}"), "")
end
@@ -98,6 +103,13 @@ def self.render_wrapped_index(links)
})
end
+ def self.render_feed(items, date_build_822)
+ render_erb_file(FILENAME_FEED_XML, {
+ items: items.reverse,
+ date_build_822: date_build_822
+ })
+ end
+
def self.touch_directories
[get_path_to_md, get_path_to_out, get_path_to_erb].map do |path|
if Dir.exist?(path)
@@ -110,7 +122,7 @@ def self.touch_directories
end
def self.copy_templates
- [FILENAME_POST_MD, FILENAME_WRAPPER_HTML, FILENAME_LIST_HTML].map do |filename|
+ [FILENAME_POST_MD, FILENAME_WRAPPER_HTML, FILENAME_LIST_HTML, FILENAME_FEED_XML].map do |filename|
full_file_path = File.join(get_path_to_erb, filename)
if File.exist?(full_file_path)
"#{relative_path(full_file_path)} already exists"
diff --git a/lib/foresite/cli.rb b/lib/foresite/cli.rb
index ae0b7d4..cff03ed 100644
--- a/lib/foresite/cli.rb
+++ b/lib/foresite/cli.rb
@@ -1,3 +1,5 @@
+require "date"
+
module Foresite
##
# Cli class.
@@ -88,6 +90,7 @@ def build
# Wipe all output files.
Dir.glob(File.join(Foresite.get_path_to_out, "*.html")).each { File.delete(_1) }
File.delete(Foresite.get_path_to_index_file) if File.exist?(Foresite.get_path_to_index_file)
+ File.delete(Foresite.get_path_to_feed_file) if File.exist?(Foresite.get_path_to_feed_file)
markdown_paths = Dir.glob(File.join(Foresite.get_path_to_md, "*.md"))
@@ -106,11 +109,14 @@ def build
File.write(html_path, Foresite.render_wrapped(title, markdown_content))
$stdout.puts("Created #{Foresite.relative_path(html_path)}")
- # Extract date if it exists.
+ # Extract date.
match_data = /\d{4}-\d{2}-\d{2}/.match(filename_markdown)
+ date_ymd = match_data[0]
+ date_822 = DateTime.strptime(date_ymd, "%F").strftime("%a, %d %b %Y %H:%M:%S %z")
{
- date_ymd: match_data.nil? ? "" : match_data[0],
+ date_ymd: date_ymd,
+ date_822: date_822,
href: Foresite.relative_path(html_path),
title: title
}
@@ -119,8 +125,11 @@ def build
# Generate index file.
index_html_path = Foresite.get_path_to_index_file
File.write(index_html_path, Foresite.render_wrapped_index(links))
-
$stdout.puts("Created #{Foresite.relative_path(index_html_path)}")
+
+ feed_xml_path = Foresite.get_path_to_feed_file
+ File.write(feed_xml_path, Foresite.render_feed(links, links.last[:date_822]))
+ $stdout.puts("Created #{Foresite.relative_path(feed_xml_path)}")
end
desc "watch", "Watches markdown and templates files and runs `build` when they change"
diff --git a/lib/foresite/renderer.rb b/lib/foresite/renderer.rb
index 4f06509..16f1a4f 100644
--- a/lib/foresite/renderer.rb
+++ b/lib/foresite/renderer.rb
@@ -12,6 +12,7 @@ class Renderer
# @param [Hash] vars Variables for template.
def initialize(path, vars)
@path = path
+ @vars_original = @vars
vars.each do |k, v|
if k.is_a?(Symbol)
instance_variable_set(:"@#{k}", v)
diff --git a/lib/foresite/version.rb b/lib/foresite/version.rb
index 6de3f37..1591f6f 100644
--- a/lib/foresite/version.rb
+++ b/lib/foresite/version.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
module Foresite
- VERSION = "1.3.0"
+ VERSION = "1.4.0"
end
diff --git a/lib/skeleton/feed.xml.erb b/lib/skeleton/feed.xml.erb
new file mode 100644
index 0000000..28830b6
--- /dev/null
+++ b/lib/skeleton/feed.xml.erb
@@ -0,0 +1,23 @@
+<%
+ title = 'Another Foresite Blog'
+ base_url = 'https://example.com'
+-%>
+
+
+ <%= title %>
+ <%= base_url %>/feed.xml
+ en-us
+ <%= @date_build_822 %>
+ <%= @date_build_822 %>
+ Foresite
+
+ <% @items.each do |item| -%>
+
+ <%= item[:title] %>
+ <%= "#{base_url}/#{item[:href]}" %>
+ <%= "#{base_url}/#{item[:href]}" %>
+ <%= item[:date_822] %>
+
+ <%- end %>
+
+
diff --git a/lib/skeleton/wrapper.html.erb b/lib/skeleton/wrapper.html.erb
index b76ba6f..c81e939 100644
--- a/lib/skeleton/wrapper.html.erb
+++ b/lib/skeleton/wrapper.html.erb
@@ -6,6 +6,7 @@
<%= @title ? "#{@title} - #{index_title}" : index_title %>
+
diff --git a/spec/foresite/cli_spec.rb b/spec/foresite/cli_spec.rb
index 5f10325..9984bba 100644
--- a/spec/foresite/cli_spec.rb
+++ b/spec/foresite/cli_spec.rb
@@ -33,7 +33,8 @@
"Created erb/",
"Created erb/post.md.erb",
"Created erb/wrapper.html.erb",
- "Created erb/_list.html.erb"
+ "Created erb/_list.html.erb",
+ "Created erb/feed.xml.erb"
])
expect { Foresite::Cli.new.invoke(:init) }.to output(expected_stdout).to_stdout
@@ -60,7 +61,8 @@
"erb/ already exists",
"erb/post.md.erb already exists",
"erb/wrapper.html.erb already exists",
- "erb/_list.html.erb already exists"
+ "erb/_list.html.erb already exists",
+ "erb/feed.xml.erb already exists"
])
# Invoke first time.
@@ -154,7 +156,9 @@
Foresite::Cli.new.invoke(:touch, ["Jackdaws Love my Big Sphinx of Quartz!"])
Foresite::Cli.new.invoke(:touch, ["When Zombies Arrive, Quickly Fax Judge Pat"])
- ymd = Time.now.strftime("%F")
+ now = Time.now
+ ymd = now.strftime("%F")
+ rfc822 = now.strftime("%a, %d %b %Y 00:00:00 +0000")
path_to_first = "#{tmpdir}/md/#{ymd}-jackdaws-love-my-big-sphinx-of-quartz.md"
# Simulate the first file being written previously.
@@ -164,11 +168,13 @@
expected_path_first = "#{tmpdir}/post/2022-12-25-jackdaws-love-my-big-sphinx-of-quartz.html"
expected_path_second = "#{tmpdir}/post/#{ymd}-when-zombies-arrive-quickly-fax-judge-pat.html"
expected_path_index = "#{tmpdir}/index.html"
+ expected_path_feed = "#{tmpdir}/feed.xml"
expected_stdout = ForesiteRSpec.cli_lines([
"Created post/2022-12-25-jackdaws-love-my-big-sphinx-of-quartz.html",
"Created post/#{ymd}-when-zombies-arrive-quickly-fax-judge-pat.html",
- "Created index.html"
+ "Created index.html",
+ "Created feed.xml"
])
# Run build
@@ -198,10 +204,26 @@
EOF
+ expected_content_feed = <<~EOF
+
+ When Zombies Arrive, Quickly Fax Judge Pat
+ https://example.com/post/#{ymd}-when-zombies-arrive-quickly-fax-judge-pat.html
+ https://example.com/post/#{ymd}-when-zombies-arrive-quickly-fax-judge-pat.html
+ #{rfc822}
+
+
+ Jackdaws Love my Big Sphinx of Quartz!
+ https://example.com/post/2022-12-25-jackdaws-love-my-big-sphinx-of-quartz.html
+ https://example.com/post/2022-12-25-jackdaws-love-my-big-sphinx-of-quartz.html
+ Sun, 25 Dec 2022 00:00:00 +0000
+
+ EOF
+
# HTML file contents should contain generated markdown.
expect(File.read(expected_path_first)).to include(expected_content_first)
expect(File.read(expected_path_second)).to include(expected_content_second)
expect(File.read(expected_path_index)).to include(expected_content_index)
+ expect(File.read(expected_path_feed)).to include(expected_content_feed)
# They should also use the top-level HTML template, we can just use a dummy string to confirm.
expected_title_index = "Another Foresite Blog"
@@ -210,6 +232,7 @@
expect(File.read(expected_path_first)).to include(expected_title_first)
expect(File.read(expected_path_second)).to include(expected_title_second)
expect(File.read(expected_path_index)).to include(expected_title_index)
+ expect(File.read(expected_path_feed)).to include(expected_title_index)
end
end