mirror of
https://github.com/samsonjs/samhuri.net.git
synced 2026-03-31 09:56:03 +00:00
Ruby 4.0.1, update gems, fix lint warnings
This commit is contained in:
parent
fb07e8abba
commit
8b1e5e4349
37 changed files with 583 additions and 580 deletions
|
|
@ -1 +1 @@
|
|||
3.4.1
|
||||
4.0.1
|
||||
|
|
|
|||
28
Gemfile
28
Gemfile
|
|
@ -1,19 +1,19 @@
|
|||
source 'https://rubygems.org'
|
||||
source "https://rubygems.org"
|
||||
|
||||
ruby '~> 3.4.0'
|
||||
ruby "~> 4.0.1"
|
||||
|
||||
gem 'phlex', '~> 2.3'
|
||||
gem 'kramdown', '~> 2.5'
|
||||
gem 'kramdown-parser-gfm', '~> 1.1'
|
||||
gem 'rouge', '~> 4.6'
|
||||
gem 'dry-struct', '~> 1.8'
|
||||
gem 'builder', '~> 3.3'
|
||||
gem 'bake', '~> 0.20'
|
||||
gem 'nokogiri', '~> 1.18'
|
||||
gem "phlex", "~> 2.3"
|
||||
gem "kramdown", "~> 2.5"
|
||||
gem "kramdown-parser-gfm", "~> 1.1"
|
||||
gem "rouge", "~> 4.6"
|
||||
gem "dry-struct", "~> 1.8"
|
||||
gem "builder", "~> 3.3"
|
||||
gem "bake", "~> 0.20"
|
||||
gem "nokogiri", "~> 1.18"
|
||||
|
||||
group :development, :test do
|
||||
gem 'rspec', '~> 3.13'
|
||||
gem 'guard', '~> 2.18'
|
||||
gem 'guard-rspec', '~> 4.7'
|
||||
gem 'standard', '~> 1.43'
|
||||
gem "rspec", "~> 3.13"
|
||||
gem "guard", "~> 2.18"
|
||||
gem "guard-rspec", "~> 4.7"
|
||||
gem "standard", "~> 1.43"
|
||||
end
|
||||
|
|
|
|||
114
Gemfile.lock
114
Gemfile.lock
|
|
@ -5,20 +5,20 @@ GEM
|
|||
bake (0.24.1)
|
||||
bigdecimal
|
||||
samovar (~> 2.1)
|
||||
bigdecimal (3.3.1)
|
||||
bigdecimal (4.0.1)
|
||||
builder (3.3.0)
|
||||
coderay (1.1.3)
|
||||
concurrent-ruby (1.3.5)
|
||||
concurrent-ruby (1.3.6)
|
||||
console (1.34.2)
|
||||
fiber-annotation
|
||||
fiber-local (~> 1.1)
|
||||
json
|
||||
diff-lcs (1.6.2)
|
||||
dry-core (1.1.0)
|
||||
dry-core (1.2.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
logger
|
||||
zeitwerk (~> 2.6)
|
||||
dry-inflector (1.2.0)
|
||||
dry-inflector (1.3.1)
|
||||
dry-logic (1.6.0)
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0)
|
||||
|
|
@ -29,38 +29,37 @@ GEM
|
|||
dry-types (~> 1.8, >= 1.8.2)
|
||||
ice_nine (~> 0.11)
|
||||
zeitwerk (~> 2.6)
|
||||
dry-types (1.8.3)
|
||||
bigdecimal (~> 3.0)
|
||||
dry-types (1.9.1)
|
||||
bigdecimal (>= 3.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
dry-core (~> 1.0)
|
||||
dry-inflector (~> 1.0)
|
||||
dry-logic (~> 1.4)
|
||||
zeitwerk (~> 2.6)
|
||||
ffi (1.17.2)
|
||||
ffi (1.17.2-aarch64-linux-gnu)
|
||||
ffi (1.17.2-aarch64-linux-musl)
|
||||
ffi (1.17.2-arm-linux-gnu)
|
||||
ffi (1.17.2-arm-linux-musl)
|
||||
ffi (1.17.2-arm64-darwin)
|
||||
ffi (1.17.2-x86-linux-gnu)
|
||||
ffi (1.17.2-x86-linux-musl)
|
||||
ffi (1.17.2-x86_64-darwin)
|
||||
ffi (1.17.2-x86_64-linux-gnu)
|
||||
ffi (1.17.2-x86_64-linux-musl)
|
||||
ffi (1.17.3)
|
||||
ffi (1.17.3-aarch64-linux-gnu)
|
||||
ffi (1.17.3-aarch64-linux-musl)
|
||||
ffi (1.17.3-arm-linux-gnu)
|
||||
ffi (1.17.3-arm-linux-musl)
|
||||
ffi (1.17.3-arm64-darwin)
|
||||
ffi (1.17.3-x86-linux-gnu)
|
||||
ffi (1.17.3-x86-linux-musl)
|
||||
ffi (1.17.3-x86_64-darwin)
|
||||
ffi (1.17.3-x86_64-linux-gnu)
|
||||
ffi (1.17.3-x86_64-linux-musl)
|
||||
fiber-annotation (0.2.0)
|
||||
fiber-local (1.1.0)
|
||||
fiber-storage
|
||||
fiber-storage (1.0.1)
|
||||
formatador (1.2.3)
|
||||
reline
|
||||
guard (2.19.1)
|
||||
guard (2.20.1)
|
||||
formatador (>= 0.2.4)
|
||||
listen (>= 2.7, < 4.0)
|
||||
logger (~> 1.6)
|
||||
lumberjack (>= 1.0.12, < 2.0)
|
||||
nenv (~> 0.1)
|
||||
notiffany (~> 0.0)
|
||||
ostruct (~> 0.6)
|
||||
pry (>= 0.13.0)
|
||||
shellany (~> 0.0)
|
||||
thor (>= 0.18.1)
|
||||
|
|
@ -70,15 +69,16 @@ GEM
|
|||
guard-compat (~> 1.1)
|
||||
rspec (>= 2.99.0, < 4.0)
|
||||
ice_nine (0.11.2)
|
||||
io-console (0.8.1)
|
||||
json (2.16.0)
|
||||
kramdown (2.5.1)
|
||||
rexml (>= 3.3.9)
|
||||
io-console (0.8.2)
|
||||
json (2.18.1)
|
||||
kramdown (2.5.2)
|
||||
rexml (>= 3.4.4)
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
language_server-protocol (3.17.0.5)
|
||||
lint_roller (1.1.0)
|
||||
listen (3.9.0)
|
||||
listen (3.10.0)
|
||||
logger
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
logger (1.7.0)
|
||||
|
|
@ -87,49 +87,53 @@ GEM
|
|||
method_source (1.1.0)
|
||||
mini_portile2 (2.8.9)
|
||||
nenv (0.3.0)
|
||||
nokogiri (1.18.10)
|
||||
nokogiri (1.19.0)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.10-aarch64-linux-gnu)
|
||||
nokogiri (1.19.0-aarch64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.10-aarch64-linux-musl)
|
||||
nokogiri (1.19.0-aarch64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.10-arm-linux-gnu)
|
||||
nokogiri (1.19.0-arm-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.10-arm-linux-musl)
|
||||
nokogiri (1.19.0-arm-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.10-arm64-darwin)
|
||||
nokogiri (1.19.0-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.10-x86_64-darwin)
|
||||
nokogiri (1.19.0-x86_64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.10-x86_64-linux-gnu)
|
||||
nokogiri (1.19.0-x86_64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.10-x86_64-linux-musl)
|
||||
nokogiri (1.19.0-x86_64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
notiffany (0.1.3)
|
||||
nenv (~> 0.1)
|
||||
shellany (~> 0.0)
|
||||
ostruct (0.6.3)
|
||||
parallel (1.27.0)
|
||||
parser (3.3.10.0)
|
||||
parser (3.3.10.1)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
phlex (2.3.1)
|
||||
phlex (2.4.1)
|
||||
refract (~> 1.0)
|
||||
zeitwerk (~> 2.7)
|
||||
prism (1.6.0)
|
||||
pry (0.15.2)
|
||||
prism (1.9.0)
|
||||
pry (0.16.0)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
reline (>= 0.6.0)
|
||||
racc (1.8.1)
|
||||
rainbow (3.1.1)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.11.1)
|
||||
ffi (~> 1.0)
|
||||
refract (1.1.0)
|
||||
prism
|
||||
zeitwerk
|
||||
regexp_parser (2.11.3)
|
||||
reline (0.6.3)
|
||||
io-console (~> 0.5)
|
||||
rexml (3.4.4)
|
||||
rouge (4.6.1)
|
||||
rouge (4.7.0)
|
||||
rspec (3.13.2)
|
||||
rspec-core (~> 3.13.0)
|
||||
rspec-expectations (~> 3.13.0)
|
||||
|
|
@ -142,8 +146,8 @@ GEM
|
|||
rspec-mocks (3.13.7)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-support (3.13.6)
|
||||
rubocop (1.80.2)
|
||||
rspec-support (3.13.7)
|
||||
rubocop (1.82.1)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.1.0)
|
||||
|
|
@ -151,38 +155,38 @@ GEM
|
|||
parser (>= 3.3.0.2)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 2.9.3, < 3.0)
|
||||
rubocop-ast (>= 1.46.0, < 2.0)
|
||||
rubocop-ast (>= 1.48.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 4.0)
|
||||
rubocop-ast (1.48.0)
|
||||
rubocop-ast (1.49.0)
|
||||
parser (>= 3.3.7.2)
|
||||
prism (~> 1.4)
|
||||
rubocop-performance (1.25.0)
|
||||
prism (~> 1.7)
|
||||
rubocop-performance (1.26.1)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (>= 1.75.0, < 2.0)
|
||||
rubocop-ast (>= 1.38.0, < 2.0)
|
||||
rubocop-ast (>= 1.47.1, < 2.0)
|
||||
ruby-progressbar (1.13.0)
|
||||
samovar (2.4.1)
|
||||
console (~> 1.0)
|
||||
mapping (~> 1.0)
|
||||
shellany (0.0.1)
|
||||
standard (1.51.1)
|
||||
standard (1.53.0)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.0)
|
||||
rubocop (~> 1.80.2)
|
||||
rubocop (~> 1.82.0)
|
||||
standard-custom (~> 1.0.0)
|
||||
standard-performance (~> 1.8)
|
||||
standard-custom (1.0.2)
|
||||
lint_roller (~> 1.0)
|
||||
rubocop (~> 1.50)
|
||||
standard-performance (1.8.0)
|
||||
standard-performance (1.9.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop-performance (~> 1.25.0)
|
||||
thor (1.4.0)
|
||||
rubocop-performance (~> 1.26.0)
|
||||
thor (1.5.0)
|
||||
unicode-display_width (3.2.0)
|
||||
unicode-emoji (~> 4.1)
|
||||
unicode-emoji (4.1.0)
|
||||
zeitwerk (2.7.3)
|
||||
unicode-emoji (4.2.0)
|
||||
zeitwerk (2.7.4)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux-gnu
|
||||
|
|
@ -212,7 +216,7 @@ DEPENDENCIES
|
|||
standard (~> 1.43)
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.4.1p0
|
||||
ruby 4.0.1p0
|
||||
|
||||
BUNDLED WITH
|
||||
2.6.2
|
||||
4.0.3
|
||||
|
|
|
|||
123
bake.rb
123
bake.rb
|
|
@ -1,42 +1,42 @@
|
|||
# Build tasks for samhuri.net static site generator
|
||||
|
||||
require 'etc'
|
||||
require 'fileutils'
|
||||
require "etc"
|
||||
require "fileutils"
|
||||
|
||||
DRAFTS_DIR = 'public/drafts'.freeze
|
||||
PUBLISH_HOST = 'mudge'.freeze
|
||||
PRODUCTION_PUBLISH_DIR = '/var/www/samhuri.net/public'.freeze
|
||||
BETA_PUBLISH_DIR = '/var/www/beta.samhuri.net/public'.freeze
|
||||
DRAFTS_DIR = "public/drafts".freeze
|
||||
PUBLISH_HOST = "mudge".freeze
|
||||
PRODUCTION_PUBLISH_DIR = "/var/www/samhuri.net/public".freeze
|
||||
BETA_PUBLISH_DIR = "/var/www/beta.samhuri.net/public".freeze
|
||||
WATCHABLE_DIRECTORIES = %w[public posts lib].freeze
|
||||
LINT_TARGETS = %w[bake.rb bin Gemfile lib spec].freeze
|
||||
LINT_TARGETS = %w[bake.rb Gemfile lib spec].freeze
|
||||
BUILD_TARGETS = %w[debug mudge beta release].freeze
|
||||
|
||||
# Generate the site in debug mode (localhost:8000)
|
||||
def debug
|
||||
build('http://localhost:8000')
|
||||
build("http://localhost:8000")
|
||||
end
|
||||
|
||||
# Generate the site for the mudge development server
|
||||
def mudge
|
||||
build('http://mudge:8000')
|
||||
build("http://mudge:8000")
|
||||
end
|
||||
|
||||
# Generate the site for beta/staging
|
||||
def beta
|
||||
build('https://beta.samhuri.net')
|
||||
build("https://beta.samhuri.net")
|
||||
end
|
||||
|
||||
# Generate the site for production
|
||||
def release
|
||||
build('https://samhuri.net')
|
||||
build("https://samhuri.net")
|
||||
end
|
||||
|
||||
# Start local development server
|
||||
def serve
|
||||
require 'webrick'
|
||||
server = WEBrick::HTTPServer.new(Port: 8000, DocumentRoot: 'www')
|
||||
trap('INT') { server.shutdown }
|
||||
puts 'Server running at http://localhost:8000'
|
||||
require "webrick"
|
||||
server = WEBrick::HTTPServer.new(Port: 8000, DocumentRoot: "www")
|
||||
trap("INT") { server.shutdown }
|
||||
puts "Server running at http://localhost:8000"
|
||||
server.start
|
||||
end
|
||||
|
||||
|
|
@ -45,11 +45,11 @@ end
|
|||
def new_draft(*title_parts)
|
||||
title, filename =
|
||||
if title_parts.empty?
|
||||
['Untitled', next_available_draft]
|
||||
["Untitled", next_available_draft]
|
||||
else
|
||||
given_title = title_parts.join(' ')
|
||||
given_title = title_parts.join(" ")
|
||||
slug = slugify(given_title)
|
||||
abort 'Error: title cannot be converted to a filename.' if slug.empty?
|
||||
abort "Error: title cannot be converted to a filename." if slug.empty?
|
||||
|
||||
filename = "#{slug}.md"
|
||||
path = draft_path(filename)
|
||||
|
|
@ -64,7 +64,7 @@ def new_draft(*title_parts)
|
|||
File.write(path, content)
|
||||
|
||||
puts "Created new draft at #{path}"
|
||||
puts '>>> Contents below <<<'
|
||||
puts ">>> Contents below <<<"
|
||||
puts
|
||||
puts content
|
||||
end
|
||||
|
|
@ -73,12 +73,12 @@ end
|
|||
# @parameter input_path [String] Draft path or filename in public/drafts.
|
||||
def publish_draft(input_path = nil)
|
||||
if input_path.nil? || input_path.strip.empty?
|
||||
puts 'Usage: bake publish_draft <draft-path-or-filename>'
|
||||
puts "Usage: bake publish_draft <draft-path-or-filename>"
|
||||
puts
|
||||
puts 'Available drafts:'
|
||||
puts "Available drafts:"
|
||||
drafts = Dir.glob("#{DRAFTS_DIR}/*.md").map { |path| File.basename(path) }
|
||||
if drafts.empty?
|
||||
puts ' (no drafts found)'
|
||||
puts " (no drafts found)"
|
||||
else
|
||||
drafts.each { |draft| puts " #{draft}" }
|
||||
end
|
||||
|
|
@ -91,9 +91,9 @@ def publish_draft(input_path = nil)
|
|||
now = Time.now
|
||||
content = File.read(draft_path_value)
|
||||
content.sub!(/^Date:.*$/, "Date: #{ordinal_date(now)}")
|
||||
content.sub!(/^Timestamp:.*$/, "Timestamp: #{now.strftime('%Y-%m-%dT%H:%M:%S%:z')}")
|
||||
content.sub!(/^Timestamp:.*$/, "Timestamp: #{now.strftime("%Y-%m-%dT%H:%M:%S%:z")}")
|
||||
|
||||
target_dir = "posts/#{now.strftime('%Y/%m')}"
|
||||
target_dir = "posts/#{now.strftime("%Y/%m")}"
|
||||
FileUtils.mkdir_p(target_dir)
|
||||
target_path = "#{target_dir}/#{draft_file}"
|
||||
|
||||
|
|
@ -105,13 +105,13 @@ end
|
|||
|
||||
# Watch content directories and rebuild on every change.
|
||||
# @parameter target [String] One of debug, mudge, beta, or release.
|
||||
def watch(target: 'debug')
|
||||
unless command_available?('inotifywait')
|
||||
abort 'inotifywait is required (install inotify-tools).'
|
||||
def watch(target: "debug")
|
||||
unless command_available?("inotifywait")
|
||||
abort "inotifywait is required (install inotify-tools)."
|
||||
end
|
||||
|
||||
loop do
|
||||
abort 'Error: watch failed.' unless system('inotifywait', '-e', 'modify,create,delete,move', *watch_paths)
|
||||
abort "Error: watch failed." unless system("inotifywait", "-e", "modify,create,delete,move", *watch_paths)
|
||||
puts "changed at #{Time.now}"
|
||||
sleep 2
|
||||
run_build_target(target)
|
||||
|
|
@ -121,29 +121,29 @@ end
|
|||
# Publish to beta/staging server
|
||||
def publish_beta
|
||||
beta
|
||||
run_rsync(local_paths: ['www/'], publish_dir: BETA_PUBLISH_DIR, dry_run: false, delete: true)
|
||||
run_rsync(local_paths: ["www/"], publish_dir: BETA_PUBLISH_DIR, dry_run: false, delete: true)
|
||||
end
|
||||
|
||||
# Publish to production server
|
||||
def publish
|
||||
release
|
||||
run_rsync(local_paths: ['www/'], publish_dir: PRODUCTION_PUBLISH_DIR, dry_run: false, delete: true)
|
||||
run_rsync(local_paths: ["www/"], publish_dir: PRODUCTION_PUBLISH_DIR, dry_run: false, delete: true)
|
||||
end
|
||||
|
||||
# Clean generated files
|
||||
def clean
|
||||
FileUtils.rm_rf('www')
|
||||
puts 'Cleaned www/ directory'
|
||||
FileUtils.rm_rf("www")
|
||||
puts "Cleaned www/ directory"
|
||||
end
|
||||
|
||||
# Run RSpec tests
|
||||
def test
|
||||
exec 'bundle exec rspec'
|
||||
exec "bundle exec rspec"
|
||||
end
|
||||
|
||||
# Run Guard for continuous testing
|
||||
def guard
|
||||
exec 'bundle exec guard'
|
||||
exec "bundle exec guard"
|
||||
end
|
||||
|
||||
# List all available drafts
|
||||
|
|
@ -160,7 +160,7 @@ end
|
|||
|
||||
# Auto-fix StandardRB issues
|
||||
def lint_fix
|
||||
exec(*standardrb_command('--fix'))
|
||||
exec(*standardrb_command("--fix"))
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -168,26 +168,26 @@ private
|
|||
# Build the site with specified URL
|
||||
# @parameter url [String] The site URL to use
|
||||
def build(url)
|
||||
require_relative 'lib/pressa'
|
||||
require_relative "lib/pressa"
|
||||
|
||||
puts "Building site for #{url}..."
|
||||
site = Pressa.create_site(source_path: '.', url_override: url)
|
||||
site = Pressa.create_site(source_path: ".", url_override: url)
|
||||
generator = Pressa::SiteGenerator.new(site:)
|
||||
generator.generate(source_path: '.', target_path: 'www')
|
||||
puts 'Site built successfully in www/'
|
||||
generator.generate(source_path: ".", target_path: "www")
|
||||
puts "Site built successfully in www/"
|
||||
end
|
||||
|
||||
def run_build_target(target)
|
||||
target_name = target.to_s
|
||||
unless BUILD_TARGETS.include?(target_name)
|
||||
abort "Error: invalid target '#{target_name}'. Use one of: #{BUILD_TARGETS.join(', ')}"
|
||||
abort "Error: invalid target '#{target_name}'. Use one of: #{BUILD_TARGETS.join(", ")}"
|
||||
end
|
||||
|
||||
public_send(target_name)
|
||||
end
|
||||
|
||||
def watch_paths
|
||||
WATCHABLE_DIRECTORIES.flat_map { |path| ['-r', path] }
|
||||
WATCHABLE_DIRECTORIES.flat_map { |path| ["-r", path] }
|
||||
end
|
||||
|
||||
def standardrb_command(*extra_args)
|
||||
|
|
@ -195,17 +195,17 @@ def standardrb_command(*extra_args)
|
|||
end
|
||||
|
||||
def run_rsync(local_paths:, publish_dir:, dry_run:, delete:)
|
||||
command = ['rsync', '-aKv', '-e', 'ssh -4']
|
||||
command << '--dry-run' if dry_run
|
||||
command << '--delete' if delete
|
||||
command = ["rsync", "-aKv", "-e", "ssh -4"]
|
||||
command << "--dry-run" if dry_run
|
||||
command << "--delete" if delete
|
||||
command.concat(local_paths)
|
||||
command << "#{PUBLISH_HOST}:#{publish_dir}"
|
||||
abort 'Error: rsync failed.' unless system(*command)
|
||||
abort "Error: rsync failed." unless system(*command)
|
||||
end
|
||||
|
||||
def resolve_draft_input(input_path)
|
||||
if input_path.include?('/')
|
||||
if input_path.start_with?('posts/')
|
||||
if input_path.include?("/")
|
||||
if input_path.start_with?("posts/")
|
||||
abort "Error: '#{input_path}' is already published in posts/ directory"
|
||||
end
|
||||
|
||||
|
|
@ -221,16 +221,15 @@ end
|
|||
|
||||
def slugify(title)
|
||||
title.downcase
|
||||
.gsub(/[^a-z0-9\s-]/, '')
|
||||
.gsub(/\s+/, '-')
|
||||
.gsub(/-+/, '-')
|
||||
.gsub(/^-|-$/, '')
|
||||
.gsub(/[^a-z0-9\s-]/, "")
|
||||
.gsub(/\s+/, "-").squeeze("-")
|
||||
.gsub(/^-|-$/, "")
|
||||
end
|
||||
|
||||
def next_available_draft(base_filename = 'untitled.md')
|
||||
def next_available_draft(base_filename = "untitled.md")
|
||||
return base_filename unless File.exist?(draft_path(base_filename))
|
||||
|
||||
name_without_ext = File.basename(base_filename, '.md')
|
||||
name_without_ext = File.basename(base_filename, ".md")
|
||||
counter = 1
|
||||
loop do
|
||||
numbered_filename = "#{name_without_ext}-#{counter}.md"
|
||||
|
|
@ -247,7 +246,7 @@ def render_draft_template(title)
|
|||
Author: #{current_author}
|
||||
Title: #{title}
|
||||
Date: unpublished
|
||||
Timestamp: #{now.strftime('%Y-%m-%dT%H:%M:%S%:z')}
|
||||
Timestamp: #{now.strftime("%Y-%m-%dT%H:%M:%S%:z")}
|
||||
Tags:
|
||||
---
|
||||
|
||||
|
|
@ -258,27 +257,27 @@ def render_draft_template(title)
|
|||
end
|
||||
|
||||
def current_author
|
||||
Etc.getlogin || ENV['USER'] || `whoami`.strip
|
||||
rescue StandardError
|
||||
ENV['USER'] || `whoami`.strip
|
||||
Etc.getlogin || ENV["USER"] || `whoami`.strip
|
||||
rescue
|
||||
ENV["USER"] || `whoami`.strip
|
||||
end
|
||||
|
||||
def ordinal_date(time)
|
||||
day = time.day
|
||||
suffix = case day
|
||||
when 1, 21, 31
|
||||
'st'
|
||||
"st"
|
||||
when 2, 22
|
||||
'nd'
|
||||
"nd"
|
||||
when 3, 23
|
||||
'rd'
|
||||
"rd"
|
||||
else
|
||||
'th'
|
||||
"th"
|
||||
end
|
||||
|
||||
time.strftime("#{day}#{suffix} %B, %Y")
|
||||
end
|
||||
|
||||
def command_available?(command)
|
||||
system('which', command, out: File::NULL, err: File::NULL)
|
||||
system("which", command, out: File::NULL, err: File::NULL)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
require_relative '../site'
|
||||
require_relative '../posts/plugin'
|
||||
require_relative '../projects/plugin'
|
||||
require_relative '../utils/markdown_renderer'
|
||||
require_relative 'simple_toml'
|
||||
require_relative "../site"
|
||||
require_relative "../posts/plugin"
|
||||
require_relative "../projects/plugin"
|
||||
require_relative "../utils/markdown_renderer"
|
||||
require_relative "simple_toml"
|
||||
|
||||
module Pressa
|
||||
module Config
|
||||
|
|
@ -17,31 +17,31 @@ module Pressa
|
|||
end
|
||||
|
||||
def build_site(url_override: nil)
|
||||
site_config = load_toml('site.toml')
|
||||
projects_config = load_toml('projects.toml')
|
||||
site_config = load_toml("site.toml")
|
||||
projects_config = load_toml("projects.toml")
|
||||
|
||||
validate_required!(site_config, REQUIRED_SITE_KEYS, context: 'site.toml')
|
||||
validate_required!(site_config, REQUIRED_SITE_KEYS, context: "site.toml")
|
||||
|
||||
site_url = url_override || site_config['url']
|
||||
projects_plugin = hash_or_empty(site_config['projects_plugin'], 'site.toml projects_plugin')
|
||||
site_url = url_override || site_config["url"]
|
||||
projects_plugin = hash_or_empty(site_config["projects_plugin"], "site.toml projects_plugin")
|
||||
|
||||
projects = build_projects(projects_config)
|
||||
|
||||
Site.new(
|
||||
author: site_config['author'],
|
||||
email: site_config['email'],
|
||||
title: site_config['title'],
|
||||
description: site_config['description'],
|
||||
author: site_config["author"],
|
||||
email: site_config["email"],
|
||||
title: site_config["title"],
|
||||
description: site_config["description"],
|
||||
url: site_url,
|
||||
image_url: normalize_image_url(site_config['image_url'], site_url),
|
||||
scripts: build_scripts(site_config['scripts'], context: 'site.toml scripts'),
|
||||
styles: build_styles(site_config['styles'], context: 'site.toml styles'),
|
||||
image_url: normalize_image_url(site_config["image_url"], site_url),
|
||||
scripts: build_scripts(site_config["scripts"], context: "site.toml scripts"),
|
||||
styles: build_styles(site_config["styles"], context: "site.toml styles"),
|
||||
plugins: [
|
||||
Posts::Plugin.new,
|
||||
Projects::Plugin.new(
|
||||
projects:,
|
||||
scripts: build_scripts(projects_plugin['scripts'], context: 'site.toml projects_plugin.scripts'),
|
||||
styles: build_styles(projects_plugin['styles'], context: 'site.toml projects_plugin.styles')
|
||||
scripts: build_scripts(projects_plugin["scripts"], context: "site.toml projects_plugin.scripts"),
|
||||
styles: build_styles(projects_plugin["styles"], context: "site.toml projects_plugin.styles")
|
||||
)
|
||||
],
|
||||
renderers: [
|
||||
|
|
@ -60,7 +60,7 @@ module Pressa
|
|||
end
|
||||
|
||||
def build_projects(projects_config)
|
||||
projects = projects_config['projects']
|
||||
projects = projects_config["projects"]
|
||||
raise ValidationError, "Missing required top-level array 'projects' in projects.toml" unless projects
|
||||
raise ValidationError, "Expected 'projects' to be an array in projects.toml" unless projects.is_a?(Array)
|
||||
|
||||
|
|
@ -72,10 +72,10 @@ module Pressa
|
|||
validate_required!(project, REQUIRED_PROJECT_KEYS, context: "projects.toml project ##{index + 1}")
|
||||
|
||||
Projects::Project.new(
|
||||
name: project['name'],
|
||||
title: project['title'],
|
||||
description: project['description'],
|
||||
url: project['url']
|
||||
name: project["name"],
|
||||
title: project["title"],
|
||||
description: project["description"],
|
||||
url: project["url"]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -87,7 +87,7 @@ module Pressa
|
|||
|
||||
return if missing.empty?
|
||||
|
||||
raise ValidationError, "Missing required #{context} keys: #{missing.join(', ')}"
|
||||
raise ValidationError, "Missing required #{context} keys: #{missing.join(", ")}"
|
||||
end
|
||||
|
||||
def hash_or_empty(value, context)
|
||||
|
|
@ -105,10 +105,10 @@ module Pressa
|
|||
when String
|
||||
Script.new(src: item, defer: true)
|
||||
when Hash
|
||||
src = item['src']
|
||||
src = item["src"]
|
||||
raise ValidationError, "Expected #{context}[#{index}].src to be a String" unless src.is_a?(String) && !src.empty?
|
||||
|
||||
defer = item.key?('defer') ? item['defer'] : true
|
||||
defer = item.key?("defer") ? item["defer"] : true
|
||||
unless [true, false].include?(defer)
|
||||
raise ValidationError, "Expected #{context}[#{index}].defer to be a Boolean"
|
||||
end
|
||||
|
|
@ -128,7 +128,7 @@ module Pressa
|
|||
when String
|
||||
Stylesheet.new(href: item)
|
||||
when Hash
|
||||
href = item['href']
|
||||
href = item["href"]
|
||||
raise ValidationError, "Expected #{context}[#{index}].href to be a String" unless href.is_a?(String) && !href.empty?
|
||||
|
||||
Stylesheet.new(href:)
|
||||
|
|
@ -147,9 +147,9 @@ module Pressa
|
|||
|
||||
def normalize_image_url(value, site_url)
|
||||
return nil if value.nil?
|
||||
return value if value.start_with?('http://', 'https://')
|
||||
return value if value.start_with?("http://", "https://")
|
||||
|
||||
normalized = value.start_with?('/') ? value : "/#{value}"
|
||||
normalized = value.start_with?("/") ? value : "/#{value}"
|
||||
"#{site_url}#{normalized}"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
require 'json'
|
||||
require "json"
|
||||
|
||||
module Pressa
|
||||
module Config
|
||||
|
|
@ -89,7 +89,7 @@ module Pressa
|
|||
keys.each do |key|
|
||||
cursor[key] ||= {}
|
||||
unless cursor[key].is_a?(Hash)
|
||||
raise ParseError, "Expected table path '#{keys.join('.')}' at line #{line_number}"
|
||||
raise ParseError, "Expected table path '#{keys.join(".")}' at line #{line_number}"
|
||||
end
|
||||
|
||||
cursor = cursor[key]
|
||||
|
|
@ -99,7 +99,7 @@ module Pressa
|
|||
end
|
||||
|
||||
def parse_path(raw_path, line_number)
|
||||
keys = raw_path.split('.').map(&:strip)
|
||||
keys = raw_path.split(".").map(&:strip)
|
||||
if keys.empty? || keys.any? { |part| part.empty? || part !~ /\A[A-Za-z0-9_]+\z/ }
|
||||
raise ParseError, "Invalid table path '#{raw_path}' at line #{line_number}"
|
||||
end
|
||||
|
|
@ -107,7 +107,7 @@ module Pressa
|
|||
end
|
||||
|
||||
def parse_assignment(source, line_number)
|
||||
separator = index_of_unquoted(source, '=')
|
||||
separator = index_of_unquoted(source, "=")
|
||||
raise ParseError, "Invalid assignment at line #{line_number}" unless separator
|
||||
|
||||
key = source[0...separator].strip
|
||||
|
|
@ -136,7 +136,7 @@ module Pressa
|
|||
if in_string
|
||||
if escaped
|
||||
escaped = false
|
||||
elsif char == '\\'
|
||||
elsif char == "\\"
|
||||
escaped = true
|
||||
elsif char == '"'
|
||||
in_string = false
|
||||
|
|
@ -148,9 +148,9 @@ module Pressa
|
|||
case char
|
||||
when '"'
|
||||
in_string = true
|
||||
when '[', '{'
|
||||
when "[", "{"
|
||||
depth += 1
|
||||
when ']', '}'
|
||||
when "]", "}"
|
||||
depth -= 1
|
||||
end
|
||||
end
|
||||
|
|
@ -159,7 +159,7 @@ module Pressa
|
|||
end
|
||||
|
||||
def strip_comments(line)
|
||||
output = +''
|
||||
output = +""
|
||||
in_string = false
|
||||
escaped = false
|
||||
|
||||
|
|
@ -169,7 +169,7 @@ module Pressa
|
|||
|
||||
if escaped
|
||||
escaped = false
|
||||
elsif char == '\\'
|
||||
elsif char == "\\"
|
||||
escaped = true
|
||||
elsif char == '"'
|
||||
in_string = false
|
||||
|
|
@ -182,7 +182,7 @@ module Pressa
|
|||
when '"'
|
||||
in_string = true
|
||||
output << char
|
||||
when '#'
|
||||
when "#"
|
||||
break
|
||||
else
|
||||
output << char
|
||||
|
|
@ -200,7 +200,7 @@ module Pressa
|
|||
if in_string
|
||||
if escaped
|
||||
escaped = false
|
||||
elsif char == '\\'
|
||||
elsif char == "\\"
|
||||
escaped = true
|
||||
elsif char == '"'
|
||||
in_string = false
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
require 'json'
|
||||
require_relative '../utils/file_writer'
|
||||
require_relative '../views/feed_post_view'
|
||||
require "json"
|
||||
require_relative "../utils/file_writer"
|
||||
require_relative "../views/feed_post_view"
|
||||
|
||||
module Pressa
|
||||
module Posts
|
||||
|
|
@ -18,7 +18,7 @@ module Pressa
|
|||
feed = build_feed(recent)
|
||||
|
||||
json = JSON.pretty_generate(feed)
|
||||
file_path = File.join(target_path, 'feed.json')
|
||||
file_path = File.join(target_path, "feed.json")
|
||||
Utils::FileWriter.write(path: file_path, content: json)
|
||||
end
|
||||
|
||||
|
|
@ -41,18 +41,18 @@ module Pressa
|
|||
author:,
|
||||
version: FEED_VERSION,
|
||||
authors: [author],
|
||||
feed_url: @site.url_for('/feed.json'),
|
||||
language: 'en-CA',
|
||||
feed_url: @site.url_for("/feed.json"),
|
||||
language: "en-CA",
|
||||
title: @site.title
|
||||
}
|
||||
end
|
||||
|
||||
def icon_url
|
||||
@site.url_for('/images/apple-touch-icon-300.png')
|
||||
@site.url_for("/images/apple-touch-icon-300.png")
|
||||
end
|
||||
|
||||
def favicon_url
|
||||
@site.url_for('/images/apple-touch-icon-80.png')
|
||||
@site.url_for("/images/apple-touch-icon-80.png")
|
||||
end
|
||||
|
||||
def feed_item(post)
|
||||
|
|
@ -65,7 +65,7 @@ module Pressa
|
|||
item[:tags] = post.tags unless post.tags.empty?
|
||||
item[:content_html] = content_html
|
||||
item[:title] = post.link_post? ? "→ #{post.title}" : post.title
|
||||
item[:author] = { name: post.author }
|
||||
item[:author] = {name: post.author}
|
||||
item[:date_published] = post.date.iso8601
|
||||
item[:id] = permalink
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require 'yaml'
|
||||
require 'date'
|
||||
require "yaml"
|
||||
require "date"
|
||||
|
||||
module Pressa
|
||||
module Posts
|
||||
|
|
@ -28,36 +28,36 @@ module Pressa
|
|||
|
||||
def validate_required_fields!
|
||||
missing = REQUIRED_FIELDS.reject { |field| @raw.key?(field) }
|
||||
raise "Missing required fields: #{missing.join(', ')}" unless missing.empty?
|
||||
raise "Missing required fields: #{missing.join(", ")}" unless missing.empty?
|
||||
end
|
||||
|
||||
def parse_fields
|
||||
@title = @raw['Title']
|
||||
@author = @raw['Author']
|
||||
timestamp = @raw['Timestamp']
|
||||
@title = @raw["Title"]
|
||||
@author = @raw["Author"]
|
||||
timestamp = @raw["Timestamp"]
|
||||
@date = timestamp.is_a?(String) ? DateTime.parse(timestamp) : timestamp.to_datetime
|
||||
@formatted_date = @raw['Date']
|
||||
@link = @raw['Link']
|
||||
@tags = parse_tags(@raw['Tags'])
|
||||
@scripts = parse_scripts(@raw['Scripts'])
|
||||
@styles = parse_styles(@raw['Styles'])
|
||||
@formatted_date = @raw["Date"]
|
||||
@link = @raw["Link"]
|
||||
@tags = parse_tags(@raw["Tags"])
|
||||
@scripts = parse_scripts(@raw["Scripts"])
|
||||
@styles = parse_styles(@raw["Styles"])
|
||||
end
|
||||
|
||||
def parse_tags(value)
|
||||
return [] if value.nil?
|
||||
value.is_a?(Array) ? value : value.split(',').map(&:strip)
|
||||
value.is_a?(Array) ? value : value.split(",").map(&:strip)
|
||||
end
|
||||
|
||||
def parse_comma_separated(value)
|
||||
return [] if value.nil? || value.empty?
|
||||
value.split(',').map(&:strip)
|
||||
value.split(",").map(&:strip)
|
||||
end
|
||||
|
||||
def parse_scripts(value)
|
||||
return [] if value.nil?
|
||||
|
||||
parse_comma_separated(value).map do |src|
|
||||
Script.new(src: normalize_asset_path(src, 'js'), defer: true)
|
||||
Script.new(src: normalize_asset_path(src, "js"), defer: true)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -65,13 +65,13 @@ module Pressa
|
|||
return [] if value.nil?
|
||||
|
||||
parse_comma_separated(value).map do |href|
|
||||
Stylesheet.new(href: normalize_asset_path(href, 'css'))
|
||||
Stylesheet.new(href: normalize_asset_path(href, "css"))
|
||||
end
|
||||
end
|
||||
|
||||
def normalize_asset_path(path, default_dir)
|
||||
return path if path.start_with?('http://', 'https://', '/')
|
||||
return path if path.include?('/')
|
||||
return path if path.start_with?("http://", "https://", "/")
|
||||
return path if path.include?("/")
|
||||
|
||||
"#{default_dir}/#{path}"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require 'dry-struct'
|
||||
require_relative '../site'
|
||||
require "dry-struct"
|
||||
require_relative "../site"
|
||||
|
||||
module Pressa
|
||||
module Posts
|
||||
|
|
@ -30,11 +30,11 @@ module Pressa
|
|||
end
|
||||
|
||||
def formatted_month
|
||||
date.strftime('%B')
|
||||
date.strftime("%B")
|
||||
end
|
||||
|
||||
def padded_month
|
||||
format('%02d', month)
|
||||
format("%02d", month)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -45,9 +45,9 @@ module Pressa
|
|||
|
||||
def self.from_date(date)
|
||||
new(
|
||||
name: date.strftime('%B'),
|
||||
name: date.strftime("%B"),
|
||||
number: date.month,
|
||||
padded: format('%02d', date.month)
|
||||
padded: format("%02d", date.month)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
require_relative '../plugin'
|
||||
require_relative 'repo'
|
||||
require_relative 'writer'
|
||||
require_relative 'json_feed'
|
||||
require_relative 'rss_feed'
|
||||
require_relative "../plugin"
|
||||
require_relative "repo"
|
||||
require_relative "writer"
|
||||
require_relative "json_feed"
|
||||
require_relative "rss_feed"
|
||||
|
||||
module Pressa
|
||||
module Posts
|
||||
|
|
@ -10,7 +10,7 @@ module Pressa
|
|||
attr_reader :posts_by_year
|
||||
|
||||
def setup(site:, source_path:)
|
||||
posts_dir = File.join(source_path, 'posts')
|
||||
posts_dir = File.join(source_path, "posts")
|
||||
return unless Dir.exist?(posts_dir)
|
||||
|
||||
repo = PostRepo.new
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
require 'kramdown'
|
||||
require_relative 'models'
|
||||
require_relative 'metadata'
|
||||
require "kramdown"
|
||||
require_relative "models"
|
||||
require_relative "metadata"
|
||||
|
||||
module Pressa
|
||||
module Posts
|
||||
class PostRepo
|
||||
EXCERPT_LENGTH = 300
|
||||
|
||||
def initialize(output_path: 'posts')
|
||||
def initialize(output_path: "posts")
|
||||
@output_path = output_path
|
||||
@posts_by_year = {}
|
||||
end
|
||||
|
|
@ -24,18 +24,18 @@ module Pressa
|
|||
private
|
||||
|
||||
def enumerate_markdown_files(dir, &block)
|
||||
Dir.glob(File.join(dir, '**', '*.md')).each(&block)
|
||||
Dir.glob(File.join(dir, "**", "*.md")).each(&block)
|
||||
end
|
||||
|
||||
def read_post(file_path)
|
||||
content = File.read(file_path)
|
||||
metadata = PostMetadata.parse(content)
|
||||
|
||||
body_markdown = content.sub(/\A---\s*\n.*?\n---\s*\n/m, '')
|
||||
body_markdown = content.sub(/\A---\s*\n.*?\n---\s*\n/m, "")
|
||||
|
||||
html_body = render_markdown(body_markdown)
|
||||
|
||||
slug = File.basename(file_path, '.md')
|
||||
slug = File.basename(file_path, ".md")
|
||||
path = generate_path(slug, metadata.date)
|
||||
excerpt = generate_excerpt(body_markdown)
|
||||
|
||||
|
|
@ -58,9 +58,9 @@ module Pressa
|
|||
def render_markdown(markdown)
|
||||
Kramdown::Document.new(
|
||||
markdown,
|
||||
input: 'GFM',
|
||||
input: "GFM",
|
||||
hard_wrap: false,
|
||||
syntax_highlighter: 'rouge',
|
||||
syntax_highlighter: "rouge",
|
||||
syntax_highlighter_opts: {
|
||||
line_numbers: false,
|
||||
wrap: true
|
||||
|
|
@ -70,27 +70,27 @@ module Pressa
|
|||
|
||||
def generate_path(slug, date)
|
||||
year = date.year
|
||||
month = format('%02d', date.month)
|
||||
month = format("%02d", date.month)
|
||||
"/#{@output_path}/#{year}/#{month}/#{slug}"
|
||||
end
|
||||
|
||||
def generate_excerpt(markdown)
|
||||
text = markdown.dup
|
||||
|
||||
text.gsub!(/!\[[^\]]*\]\([^)]+\)/, '')
|
||||
text.gsub!(/!\[[^\]]*\]\[[^\]]+\]/, '')
|
||||
text.gsub!(/!\[[^\]]*\]\([^)]+\)/, "")
|
||||
text.gsub!(/!\[[^\]]*\]\[[^\]]+\]/, "")
|
||||
|
||||
text.gsub!(/\[([^\]]+)\]\([^)]+\)/, '\1')
|
||||
text.gsub!(/\[([^\]]+)\]\[[^\]]+\]/, '\1')
|
||||
|
||||
text.gsub!(/(?m)^\[[^\]]+\]:\s*\S.*$/, '')
|
||||
text.gsub!(/(?m)^\[[^\]]+\]:\s*\S.*$/, "")
|
||||
|
||||
text.gsub!(/<[^>]+>/, '')
|
||||
text.gsub!(/<[^>]+>/, "")
|
||||
|
||||
text.gsub!(/\s+/, ' ')
|
||||
text.gsub!(/\s+/, " ")
|
||||
text.strip!
|
||||
|
||||
return '...' if text.empty?
|
||||
return "..." if text.empty?
|
||||
|
||||
"#{text[0...EXCERPT_LENGTH]}..."
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
require 'builder'
|
||||
require_relative '../utils/file_writer'
|
||||
require_relative '../views/feed_post_view'
|
||||
require "builder"
|
||||
require_relative "../utils/file_writer"
|
||||
require_relative "../views/feed_post_view"
|
||||
|
||||
module Pressa
|
||||
module Posts
|
||||
|
|
@ -16,15 +16,15 @@ module Pressa
|
|||
xml = Builder::XmlMarkup.new(indent: 2)
|
||||
xml.instruct! :xml, version: "1.0", encoding: "UTF-8"
|
||||
|
||||
xml.rss version: "2.0",
|
||||
"xmlns:atom" => "http://www.w3.org/2005/Atom",
|
||||
"xmlns:content" => "http://purl.org/rss/1.0/modules/content/" do
|
||||
xml.rss :version => "2.0",
|
||||
"xmlns:atom" => "http://www.w3.org/2005/Atom",
|
||||
"xmlns:content" => "http://purl.org/rss/1.0/modules/content/" do
|
||||
xml.channel do
|
||||
xml.title @site.title
|
||||
xml.link @site.url
|
||||
xml.description @site.description
|
||||
xml.pubDate recent.first.date.rfc822 if recent.any?
|
||||
xml.tag! "atom:link", href: @site.url_for('/feed.xml'), rel: "self", type: "application/rss+xml"
|
||||
xml.tag! "atom:link", href: @site.url_for("/feed.xml"), rel: "self", type: "application/rss+xml"
|
||||
|
||||
recent.each do |post|
|
||||
xml.item do
|
||||
|
|
@ -35,13 +35,13 @@ module Pressa
|
|||
xml.guid permalink, isPermaLink: "true"
|
||||
xml.pubDate post.date.rfc822
|
||||
xml.author post.author
|
||||
xml.tag!('content:encoded') { xml.cdata!(render_feed_post(post)) }
|
||||
xml.tag!("content:encoded") { xml.cdata!(render_feed_post(post)) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
file_path = File.join(target_path, 'feed.xml')
|
||||
file_path = File.join(target_path, "feed.xml")
|
||||
Utils::FileWriter.write(path: file_path, content: xml.target!)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
require_relative '../utils/file_writer'
|
||||
require_relative '../views/layout'
|
||||
require_relative '../views/post_view'
|
||||
require_relative '../views/recent_posts_view'
|
||||
require_relative '../views/archive_view'
|
||||
require_relative '../views/year_posts_view'
|
||||
require_relative '../views/month_posts_view'
|
||||
require_relative "../utils/file_writer"
|
||||
require_relative "../views/layout"
|
||||
require_relative "../views/post_view"
|
||||
require_relative "../views/recent_posts_view"
|
||||
require_relative "../views/archive_view"
|
||||
require_relative "../views/year_posts_view"
|
||||
require_relative "../views/month_posts_view"
|
||||
|
||||
module Pressa
|
||||
module Posts
|
||||
|
|
@ -28,11 +28,11 @@ module Pressa
|
|||
page_subtitle: nil,
|
||||
canonical_url: @site.url,
|
||||
content: content_view,
|
||||
page_description: 'Recent posts',
|
||||
page_type: 'article'
|
||||
page_description: "Recent posts",
|
||||
page_type: "article"
|
||||
)
|
||||
|
||||
file_path = File.join(target_path, 'index.html')
|
||||
file_path = File.join(target_path, "index.html")
|
||||
Utils::FileWriter.write(path: file_path, content: html)
|
||||
end
|
||||
|
||||
|
|
@ -40,13 +40,13 @@ module Pressa
|
|||
content_view = Views::ArchiveView.new(posts_by_year: @posts_by_year, site: @site)
|
||||
|
||||
html = render_layout(
|
||||
page_subtitle: 'Archive',
|
||||
canonical_url: @site.url_for('/posts/'),
|
||||
page_subtitle: "Archive",
|
||||
canonical_url: @site.url_for("/posts/"),
|
||||
content: content_view,
|
||||
page_description: 'Archive of all posts'
|
||||
page_description: "Archive of all posts"
|
||||
)
|
||||
|
||||
file_path = File.join(target_path, 'posts', 'index.html')
|
||||
file_path = File.join(target_path, "posts", "index.html")
|
||||
Utils::FileWriter.write(path: file_path, content: html)
|
||||
end
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ module Pressa
|
|||
private
|
||||
|
||||
def write_post(post:, target_path:)
|
||||
content_view = Views::PostView.new(post:, site: @site, article_class: 'container')
|
||||
content_view = Views::PostView.new(post:, site: @site, article_class: "container")
|
||||
|
||||
html = render_layout(
|
||||
page_subtitle: post.title,
|
||||
|
|
@ -77,10 +77,10 @@ module Pressa
|
|||
page_scripts: post.scripts,
|
||||
page_styles: post.styles,
|
||||
page_description: post.excerpt,
|
||||
page_type: 'article'
|
||||
page_type: "article"
|
||||
)
|
||||
|
||||
file_path = File.join(target_path, post.path.sub(/^\//, ''), 'index.html')
|
||||
file_path = File.join(target_path, post.path.sub(/^\//, ""), "index.html")
|
||||
Utils::FileWriter.write(path: file_path, content: html)
|
||||
end
|
||||
|
||||
|
|
@ -92,10 +92,10 @@ module Pressa
|
|||
canonical_url: @site.url_for("/posts/#{year}/"),
|
||||
content: content_view,
|
||||
page_description: "Archive of all posts from #{year}",
|
||||
page_type: 'article'
|
||||
page_type: "article"
|
||||
)
|
||||
|
||||
file_path = File.join(target_path, 'posts', year.to_s, 'index.html')
|
||||
file_path = File.join(target_path, "posts", year.to_s, "index.html")
|
||||
Utils::FileWriter.write(path: file_path, content: html)
|
||||
end
|
||||
|
||||
|
|
@ -109,10 +109,10 @@ module Pressa
|
|||
canonical_url: @site.url_for("/posts/#{year}/#{month.padded}/"),
|
||||
content: content_view,
|
||||
page_description: "Archive of all posts from #{title}",
|
||||
page_type: 'article'
|
||||
page_type: "article"
|
||||
)
|
||||
|
||||
file_path = File.join(target_path, 'posts', year.to_s, month.padded, 'index.html')
|
||||
file_path = File.join(target_path, "posts", year.to_s, month.padded, "index.html")
|
||||
Utils::FileWriter.write(path: file_path, content: html)
|
||||
end
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ module Pressa
|
|||
page_scripts: [],
|
||||
page_styles: [],
|
||||
page_description: nil,
|
||||
page_type: 'website'
|
||||
page_type: "website"
|
||||
)
|
||||
layout = Views::Layout.new(
|
||||
site: @site,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
require_relative 'site'
|
||||
require_relative 'site_generator'
|
||||
require_relative 'plugin'
|
||||
require_relative 'posts/plugin'
|
||||
require_relative 'projects/plugin'
|
||||
require_relative 'utils/markdown_renderer'
|
||||
require_relative 'config/loader'
|
||||
require_relative "site"
|
||||
require_relative "site_generator"
|
||||
require_relative "plugin"
|
||||
require_relative "posts/plugin"
|
||||
require_relative "projects/plugin"
|
||||
require_relative "utils/markdown_renderer"
|
||||
require_relative "config/loader"
|
||||
|
||||
module Pressa
|
||||
def self.create_site(source_path: '.', url_override: nil)
|
||||
def self.create_site(source_path: ".", url_override: nil)
|
||||
loader = Config::Loader.new(source_path:)
|
||||
loader.build_site(url_override:)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require 'dry-struct'
|
||||
require_relative '../site'
|
||||
require "dry-struct"
|
||||
require_relative "../site"
|
||||
|
||||
module Pressa
|
||||
module Projects
|
||||
|
|
@ -11,7 +11,7 @@ module Pressa
|
|||
|
||||
def github_path
|
||||
uri = URI.parse(url)
|
||||
uri.path.sub(/^\//, '')
|
||||
uri.path.sub(/^\//, "")
|
||||
end
|
||||
|
||||
def path
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
require_relative '../plugin'
|
||||
require_relative '../utils/file_writer'
|
||||
require_relative '../views/layout'
|
||||
require_relative '../views/projects_view'
|
||||
require_relative '../views/project_view'
|
||||
require_relative 'models'
|
||||
require_relative "../plugin"
|
||||
require_relative "../utils/file_writer"
|
||||
require_relative "../views/layout"
|
||||
require_relative "../views/projects_view"
|
||||
require_relative "../views/project_view"
|
||||
require_relative "models"
|
||||
|
||||
module Pressa
|
||||
module Projects
|
||||
|
|
@ -34,12 +34,12 @@ module Pressa
|
|||
|
||||
html = render_layout(
|
||||
site:,
|
||||
page_subtitle: 'Projects',
|
||||
canonical_url: site.url_for('/projects/'),
|
||||
page_subtitle: "Projects",
|
||||
canonical_url: site.url_for("/projects/"),
|
||||
content: content_view
|
||||
)
|
||||
|
||||
file_path = File.join(target_path, 'projects', 'index.html')
|
||||
file_path = File.join(target_path, "projects", "index.html")
|
||||
Utils::FileWriter.write(path: file_path, content: html)
|
||||
end
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ module Pressa
|
|||
page_description: project.description
|
||||
)
|
||||
|
||||
file_path = File.join(target_path, 'projects', project.name, 'index.html')
|
||||
file_path = File.join(target_path, "projects", project.name, "index.html")
|
||||
Utils::FileWriter.write(path: file_path, content: html)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
require 'dry-struct'
|
||||
require "dry-struct"
|
||||
|
||||
module Pressa
|
||||
module Types
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require 'fileutils'
|
||||
require_relative 'utils/file_writer'
|
||||
require "fileutils"
|
||||
require_relative "utils/file_writer"
|
||||
|
||||
module Pressa
|
||||
class SiteGenerator
|
||||
|
|
@ -30,7 +30,7 @@ module Pressa
|
|||
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'
|
||||
raise ArgumentError, "target_path must not be the same as or contain source_path"
|
||||
end
|
||||
|
||||
def absolute_path(path)
|
||||
|
|
@ -42,12 +42,12 @@ module Pressa
|
|||
end
|
||||
|
||||
def copy_static_files(source_path, target_path)
|
||||
public_dir = File.join(source_path, 'public')
|
||||
public_dir = File.join(source_path, "public")
|
||||
return unless Dir.exist?(public_dir)
|
||||
|
||||
Dir.glob(File.join(public_dir, '**', '*'), File::FNM_DOTMATCH).each do |source_file|
|
||||
Dir.glob(File.join(public_dir, "**", "*"), File::FNM_DOTMATCH).each do |source_file|
|
||||
next if File.directory?(source_file)
|
||||
next if File.basename(source_file) == '.' || File.basename(source_file) == '..'
|
||||
next if File.basename(source_file) == "." || File.basename(source_file) == ".."
|
||||
|
||||
filename = File.basename(source_file)
|
||||
ext = File.extname(source_file)[1..]
|
||||
|
|
@ -56,7 +56,7 @@ module Pressa
|
|||
next
|
||||
end
|
||||
|
||||
relative_path = source_file.sub("#{public_dir}/", '')
|
||||
relative_path = source_file.sub("#{public_dir}/", "")
|
||||
target_file = File.join(target_path, relative_path)
|
||||
|
||||
FileUtils.mkdir_p(File.dirname(target_file))
|
||||
|
|
@ -69,11 +69,11 @@ module Pressa
|
|||
end
|
||||
|
||||
def process_public_directory(source_path, target_path)
|
||||
public_dir = File.join(source_path, 'public')
|
||||
public_dir = File.join(source_path, "public")
|
||||
return unless Dir.exist?(public_dir)
|
||||
|
||||
site.renderers.each do |renderer|
|
||||
Dir.glob(File.join(public_dir, '**', '*'), File::FNM_DOTMATCH).each do |source_file|
|
||||
Dir.glob(File.join(public_dir, "**", "*"), File::FNM_DOTMATCH).each do |source_file|
|
||||
next if File.directory?(source_file)
|
||||
|
||||
filename = File.basename(source_file)
|
||||
|
|
@ -82,10 +82,10 @@ module Pressa
|
|||
if renderer.can_render_file?(filename:, extension: ext)
|
||||
dir_name = File.dirname(source_file)
|
||||
relative_path = if dir_name == public_dir
|
||||
''
|
||||
else
|
||||
dir_name.sub("#{public_dir}/", '')
|
||||
end
|
||||
""
|
||||
else
|
||||
dir_name.sub("#{public_dir}/", "")
|
||||
end
|
||||
target_dir = File.join(target_path, relative_path)
|
||||
|
||||
renderer.render(site:, file_path: source_file, target_dir:)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
require 'fileutils'
|
||||
require "fileutils"
|
||||
|
||||
module Pressa
|
||||
module Utils
|
||||
class FileWriter
|
||||
def self.write(path:, content:, permissions: 0o644)
|
||||
FileUtils.mkdir_p(File.dirname(path))
|
||||
File.write(path, content, mode: 'w')
|
||||
File.write(path, content, mode: "w")
|
||||
File.chmod(permissions, path)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
require 'kramdown'
|
||||
require 'yaml'
|
||||
require_relative 'file_writer'
|
||||
require_relative '../site'
|
||||
require_relative '../views/layout'
|
||||
require_relative '../views/icons'
|
||||
require "kramdown"
|
||||
require "yaml"
|
||||
require_relative "file_writer"
|
||||
require_relative "../site"
|
||||
require_relative "../views/layout"
|
||||
require_relative "../views/icons"
|
||||
|
||||
module Pressa
|
||||
module Utils
|
||||
|
|
@ -11,7 +11,7 @@ module Pressa
|
|||
EXCERPT_LENGTH = 300
|
||||
|
||||
def can_render_file?(filename:, extension:)
|
||||
extension == 'md'
|
||||
extension == "md"
|
||||
end
|
||||
|
||||
def render(site:, file_path:, target_dir:)
|
||||
|
|
@ -20,21 +20,21 @@ module Pressa
|
|||
|
||||
html_body = render_markdown(body_markdown)
|
||||
|
||||
page_title = presence(metadata['Title']) || File.basename(file_path, '.md').capitalize
|
||||
page_type = presence(metadata['Page type']) || 'website'
|
||||
page_description = presence(metadata['Description']) || generate_excerpt(body_markdown)
|
||||
show_extension = ['true', 'yes', true].include?(metadata['Show extension'])
|
||||
page_title = presence(metadata["Title"]) || File.basename(file_path, ".md").capitalize
|
||||
page_type = presence(metadata["Page type"]) || "website"
|
||||
page_description = presence(metadata["Description"]) || generate_excerpt(body_markdown)
|
||||
show_extension = ["true", "yes", true].include?(metadata["Show extension"])
|
||||
|
||||
slug = File.basename(file_path, '.md')
|
||||
slug = File.basename(file_path, ".md")
|
||||
|
||||
relative_dir = File.dirname(file_path).sub(/^.*?\/public\/?/, '')
|
||||
relative_dir = '' if relative_dir == '.'
|
||||
relative_dir = File.dirname(file_path).sub(/^.*?\/public\/?/, "")
|
||||
relative_dir = "" if relative_dir == "."
|
||||
|
||||
canonical_path = if show_extension
|
||||
"/#{relative_dir}/#{slug}.html".squeeze('/')
|
||||
else
|
||||
"/#{relative_dir}/#{slug}/".squeeze('/')
|
||||
end
|
||||
"/#{relative_dir}/#{slug}.html".squeeze("/")
|
||||
else
|
||||
"/#{relative_dir}/#{slug}/".squeeze("/")
|
||||
end
|
||||
|
||||
html = render_layout(
|
||||
site:,
|
||||
|
|
@ -46,10 +46,10 @@ module Pressa
|
|||
)
|
||||
|
||||
output_filename = if show_extension
|
||||
"#{slug}.html"
|
||||
else
|
||||
File.join(slug, 'index.html')
|
||||
end
|
||||
"#{slug}.html"
|
||||
else
|
||||
File.join(slug, "index.html")
|
||||
end
|
||||
|
||||
output_path = File.join(target_dir, output_filename)
|
||||
FileWriter.write(path: output_path, content: html)
|
||||
|
|
@ -71,9 +71,9 @@ module Pressa
|
|||
def render_markdown(markdown)
|
||||
Kramdown::Document.new(
|
||||
markdown,
|
||||
input: 'GFM',
|
||||
input: "GFM",
|
||||
hard_wrap: false,
|
||||
syntax_highlighter: 'rouge',
|
||||
syntax_highlighter: "rouge",
|
||||
syntax_highlighter_opts: {
|
||||
line_numbers: false,
|
||||
wrap: true
|
||||
|
|
@ -101,13 +101,13 @@ module Pressa
|
|||
end
|
||||
|
||||
def view_template
|
||||
article(class: 'container') do
|
||||
article(class: "container") do
|
||||
h1 { @page_title }
|
||||
raw(safe(@body))
|
||||
end
|
||||
|
||||
div(class: 'row clearfix') do
|
||||
p(class: 'fin') do
|
||||
div(class: "row clearfix") do
|
||||
p(class: "fin") do
|
||||
raw(safe(Views::Icons.code))
|
||||
end
|
||||
end
|
||||
|
|
@ -118,18 +118,18 @@ module Pressa
|
|||
text = markdown.dup
|
||||
|
||||
# Drop inline and reference-style images before links are simplified.
|
||||
text.gsub!(/!\[[^\]]*\]\([^)]+\)/, '')
|
||||
text.gsub!(/!\[[^\]]*\]\[[^\]]+\]/, '')
|
||||
text.gsub!(/!\[[^\]]*\]\([^)]+\)/, "")
|
||||
text.gsub!(/!\[[^\]]*\]\[[^\]]+\]/, "")
|
||||
|
||||
# Replace inline and reference links with just their text.
|
||||
text.gsub!(/\[([^\]]+)\]\([^)]+\)/, '\1')
|
||||
text.gsub!(/\[([^\]]+)\]\[[^\]]+\]/, '\1')
|
||||
|
||||
# Remove link reference definitions such as: [foo]: http://example.com
|
||||
text.gsub!(/(?m)^\[[^\]]+\]:\s*\S.*$/, '')
|
||||
text.gsub!(/(?m)^\[[^\]]+\]:\s*\S.*$/, "")
|
||||
|
||||
text.gsub!(/<[^>]+>/, '')
|
||||
text.gsub!(/\s+/, ' ')
|
||||
text.gsub!(/<[^>]+>/, "")
|
||||
text.gsub!(/\s+/, " ")
|
||||
text.strip!
|
||||
|
||||
return nil if text.empty?
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require 'phlex'
|
||||
require_relative 'year_posts_view'
|
||||
require "phlex"
|
||||
require_relative "year_posts_view"
|
||||
|
||||
module Pressa
|
||||
module Views
|
||||
|
|
@ -10,8 +10,8 @@ module Pressa
|
|||
end
|
||||
|
||||
def view_template
|
||||
div(class: 'container') do
|
||||
h1 { 'Archive' }
|
||||
div(class: "container") do
|
||||
h1 { "Archive" }
|
||||
end
|
||||
|
||||
@posts_by_year.sorted_years.each do |year|
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
require 'phlex'
|
||||
require "phlex"
|
||||
|
||||
module Pressa
|
||||
module Views
|
||||
|
|
@ -10,10 +10,10 @@ module Pressa
|
|||
|
||||
def view_template
|
||||
div do
|
||||
p(class: 'time') { @post.formatted_date }
|
||||
p(class: "time") { @post.formatted_date }
|
||||
raw(safe(@post.body))
|
||||
p do
|
||||
a(class: 'permalink', href: @site.url_for(@post.path)) { '∞' }
|
||||
a(class: "permalink", href: @site.url_for(@post.path)) { "∞" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,19 +4,19 @@ module Pressa
|
|||
module_function
|
||||
|
||||
def mastodon
|
||||
svg(class_name: 'icon icon-mastodon', view_box: '0 0 448 512', path: IconPath::MASTODON)
|
||||
svg(class_name: "icon icon-mastodon", view_box: "0 0 448 512", path: IconPath::MASTODON)
|
||||
end
|
||||
|
||||
def github
|
||||
svg(class_name: 'icon icon-github', view_box: '0 0 496 512', path: IconPath::GITHUB)
|
||||
svg(class_name: "icon icon-github", view_box: "0 0 496 512", path: IconPath::GITHUB)
|
||||
end
|
||||
|
||||
def rss
|
||||
svg(class_name: 'icon icon-rss', view_box: '0 0 448 512', path: IconPath::RSS)
|
||||
svg(class_name: "icon icon-rss", view_box: "0 0 448 512", path: IconPath::RSS)
|
||||
end
|
||||
|
||||
def code
|
||||
svg(class_name: 'icon icon-code', view_box: '0 0 640 512', path: IconPath::CODE)
|
||||
svg(class_name: "icon icon-code", view_box: "0 0 640 512", path: IconPath::CODE)
|
||||
end
|
||||
|
||||
private_class_method def svg(class_name:, view_box:, path:)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require 'phlex'
|
||||
require_relative 'icons'
|
||||
require "phlex"
|
||||
require_relative "icons"
|
||||
|
||||
module Pressa
|
||||
module Views
|
||||
|
|
@ -7,20 +7,19 @@ module Pressa
|
|||
START_YEAR = 2006
|
||||
|
||||
attr_reader :site,
|
||||
:page_subtitle,
|
||||
:page_description,
|
||||
:page_type,
|
||||
:canonical_url,
|
||||
:page_scripts,
|
||||
:page_styles,
|
||||
:content
|
||||
:page_subtitle,
|
||||
:page_description,
|
||||
:page_type,
|
||||
:canonical_url,
|
||||
:page_scripts,
|
||||
:page_styles,
|
||||
:content
|
||||
|
||||
def initialize(
|
||||
site:,
|
||||
page_subtitle: nil,
|
||||
canonical_url:,
|
||||
canonical_url:, page_subtitle: nil,
|
||||
page_description: nil,
|
||||
page_type: 'website',
|
||||
page_type: "website",
|
||||
page_scripts: [],
|
||||
page_styles: [],
|
||||
content: nil
|
||||
|
|
@ -42,54 +41,54 @@ module Pressa
|
|||
def view_template
|
||||
doctype
|
||||
|
||||
html(lang: 'en') do
|
||||
comment { 'meow' }
|
||||
html(lang: "en") do
|
||||
comment { "meow" }
|
||||
|
||||
head do
|
||||
meta(charset: 'UTF-8')
|
||||
meta(charset: "UTF-8")
|
||||
title { full_title }
|
||||
meta(name: 'twitter:title', content: full_title)
|
||||
meta(property: 'og:title', content: full_title)
|
||||
meta(name: 'description', content: description)
|
||||
meta(name: 'twitter:description', content: description)
|
||||
meta(property: 'og:description', content: description)
|
||||
meta(property: 'og:site_name', content: site.title)
|
||||
meta(name: "twitter:title", content: full_title)
|
||||
meta(property: "og:title", content: full_title)
|
||||
meta(name: "description", content: description)
|
||||
meta(name: "twitter:description", content: description)
|
||||
meta(property: "og:description", content: description)
|
||||
meta(property: "og:site_name", content: site.title)
|
||||
|
||||
link(rel: 'canonical', href: canonical_url)
|
||||
meta(name: 'twitter:url', content: canonical_url)
|
||||
meta(property: 'og:url', content: canonical_url)
|
||||
meta(property: 'og:image', content: og_image_url) if og_image_url
|
||||
meta(property: 'og:type', content: page_type)
|
||||
meta(property: 'article:author', content: site.author)
|
||||
meta(name: 'twitter:card', content: 'summary')
|
||||
link(rel: "canonical", href: canonical_url)
|
||||
meta(name: "twitter:url", content: canonical_url)
|
||||
meta(property: "og:url", content: canonical_url)
|
||||
meta(property: "og:image", content: og_image_url) if og_image_url
|
||||
meta(property: "og:type", content: page_type)
|
||||
meta(property: "article:author", content: site.author)
|
||||
meta(name: "twitter:card", content: "summary")
|
||||
|
||||
link(
|
||||
rel: 'alternate',
|
||||
href: site.url_for('/feed.xml'),
|
||||
type: 'application/rss+xml',
|
||||
rel: "alternate",
|
||||
href: site.url_for("/feed.xml"),
|
||||
type: "application/rss+xml",
|
||||
title: site.title
|
||||
)
|
||||
link(
|
||||
rel: 'alternate',
|
||||
href: site.url_for('/feed.json'),
|
||||
type: 'application/json',
|
||||
rel: "alternate",
|
||||
href: site.url_for("/feed.json"),
|
||||
type: "application/json",
|
||||
title: site.title
|
||||
)
|
||||
|
||||
meta(name: 'fediverse:creator', content: '@sjs@techhub.social')
|
||||
link(rel: 'author', type: 'text/plain', href: site.url_for('/humans.txt'))
|
||||
link(rel: 'icon', type: 'image/png', href: site.url_for('/images/favicon-32x32.png'))
|
||||
link(rel: 'shortcut icon', href: site.url_for('/images/favicon.icon'))
|
||||
link(rel: 'apple-touch-icon', href: site.url_for('/images/apple-touch-icon.png'))
|
||||
link(rel: 'mask-icon', color: '#aa0000', href: site.url_for('/images/safari-pinned-tab.svg'))
|
||||
link(rel: 'manifest', href: site.url_for('/images/manifest.json'))
|
||||
meta(name: 'msapplication-config', content: site.url_for('/images/browserconfig.xml'))
|
||||
meta(name: 'theme-color', content: '#121212')
|
||||
meta(name: 'viewport', content: 'width=device-width, initial-scale=1.0, viewport-fit=cover')
|
||||
link(rel: 'dns-prefetch', href: 'https://gist.github.com')
|
||||
meta(name: "fediverse:creator", content: "@sjs@techhub.social")
|
||||
link(rel: "author", type: "text/plain", href: site.url_for("/humans.txt"))
|
||||
link(rel: "icon", type: "image/png", href: site.url_for("/images/favicon-32x32.png"))
|
||||
link(rel: "shortcut icon", href: site.url_for("/images/favicon.icon"))
|
||||
link(rel: "apple-touch-icon", href: site.url_for("/images/apple-touch-icon.png"))
|
||||
link(rel: "mask-icon", color: "#aa0000", href: site.url_for("/images/safari-pinned-tab.svg"))
|
||||
link(rel: "manifest", href: site.url_for("/images/manifest.json"))
|
||||
meta(name: "msapplication-config", content: site.url_for("/images/browserconfig.xml"))
|
||||
meta(name: "theme-color", content: "#121212")
|
||||
meta(name: "viewport", content: "width=device-width, initial-scale=1.0, viewport-fit=cover")
|
||||
link(rel: "dns-prefetch", href: "https://gist.github.com")
|
||||
|
||||
all_styles.each do |style|
|
||||
link(rel: 'stylesheet', type: 'text/css', href: absolute_asset(style.href))
|
||||
link(rel: "stylesheet", type: "text/css", href: absolute_asset(style.href))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -127,73 +126,73 @@ module Pressa
|
|||
end
|
||||
|
||||
def render_header
|
||||
header(class: 'primary') do
|
||||
div(class: 'title') do
|
||||
header(class: "primary") do
|
||||
div(class: "title") do
|
||||
h1 do
|
||||
a(href: site.url) { site.title }
|
||||
end
|
||||
br
|
||||
h4 do
|
||||
plain 'By '
|
||||
a(href: site.url_for('/about')) { site.author }
|
||||
plain "By "
|
||||
a(href: site.url_for("/about")) { site.author }
|
||||
end
|
||||
end
|
||||
|
||||
nav(class: 'remote') do
|
||||
nav(class: "remote") do
|
||||
ul do
|
||||
li(class: 'mastodon') do
|
||||
a(rel: 'me', 'aria-label': 'Mastodon', href: 'https://techhub.social/@sjs') do
|
||||
li(class: "mastodon") do
|
||||
a(rel: "me", "aria-label": "Mastodon", href: "https://techhub.social/@sjs") do
|
||||
raw(safe(Icons.mastodon))
|
||||
end
|
||||
end
|
||||
li(class: 'github') do
|
||||
a('aria-label': 'GitHub', href: 'https://github.com/samsonjs') do
|
||||
li(class: "github") do
|
||||
a("aria-label": "GitHub", href: "https://github.com/samsonjs") do
|
||||
raw(safe(Icons.github))
|
||||
end
|
||||
end
|
||||
li(class: 'rss') do
|
||||
a('aria-label': 'RSS', href: site.url_for('/feed.xml')) do
|
||||
li(class: "rss") do
|
||||
a("aria-label": "RSS", href: site.url_for("/feed.xml")) do
|
||||
raw(safe(Icons.rss))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
nav(class: 'local') do
|
||||
nav(class: "local") do
|
||||
ul do
|
||||
li { a(href: site.url_for('/about')) { 'About' } }
|
||||
li { a(href: site.url_for('/posts')) { 'Archive' } }
|
||||
li { a(href: site.url_for('/projects')) { 'Projects' } }
|
||||
li { a(href: site.url_for("/about")) { "About" } }
|
||||
li { a(href: site.url_for("/posts")) { "Archive" } }
|
||||
li { a(href: site.url_for("/projects")) { "Projects" } }
|
||||
end
|
||||
end
|
||||
|
||||
div(class: 'clearfix')
|
||||
div(class: "clearfix")
|
||||
end
|
||||
end
|
||||
|
||||
def render_footer
|
||||
footer do
|
||||
plain "© #{START_YEAR} - #{Time.now.year} "
|
||||
a(href: site.url_for('/about')) { site.author }
|
||||
a(href: site.url_for("/about")) { site.author }
|
||||
end
|
||||
end
|
||||
|
||||
def render_scripts
|
||||
all_scripts.each do |scr|
|
||||
attrs = { src: script_src(scr.src) }
|
||||
attrs = {src: script_src(scr.src)}
|
||||
attrs[:defer] = true if scr.defer
|
||||
script(**attrs)
|
||||
end
|
||||
end
|
||||
|
||||
def script_src(src)
|
||||
return src if src.start_with?('http://', 'https://')
|
||||
return src if src.start_with?("http://", "https://")
|
||||
|
||||
absolute_asset(src)
|
||||
end
|
||||
|
||||
def absolute_asset(path)
|
||||
normalized = path.start_with?('/') ? path : "/#{path}"
|
||||
normalized = path.start_with?("/") ? path : "/#{path}"
|
||||
site.url_for(normalized)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require 'phlex'
|
||||
require_relative 'post_view'
|
||||
require "phlex"
|
||||
require_relative "post_view"
|
||||
|
||||
module Pressa
|
||||
module Views
|
||||
|
|
@ -11,12 +11,12 @@ module Pressa
|
|||
end
|
||||
|
||||
def view_template
|
||||
div(class: 'container') do
|
||||
div(class: "container") do
|
||||
h1 { "#{@month_posts.month.name} #{@year}" }
|
||||
end
|
||||
|
||||
@month_posts.sorted_posts.each do |post|
|
||||
div(class: 'container') do
|
||||
div(class: "container") do
|
||||
render PostView.new(post:, site: @site)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require 'phlex'
|
||||
require_relative 'icons'
|
||||
require "phlex"
|
||||
require_relative "icons"
|
||||
|
||||
module Pressa
|
||||
module Views
|
||||
|
|
@ -21,14 +21,14 @@ module Pressa
|
|||
end
|
||||
end
|
||||
time { @post.formatted_date }
|
||||
a(href: @post.path, class: 'permalink') { '∞' }
|
||||
a(href: @post.path, class: "permalink") { "∞" }
|
||||
end
|
||||
|
||||
raw(safe(@post.body))
|
||||
end
|
||||
|
||||
div(class: 'row clearfix') do
|
||||
p(class: 'fin') do
|
||||
div(class: "row clearfix") do
|
||||
p(class: "fin") do
|
||||
raw(safe(Icons.code))
|
||||
end
|
||||
end
|
||||
|
|
@ -39,7 +39,7 @@ module Pressa
|
|||
def article_attributes
|
||||
return {} unless @article_class
|
||||
|
||||
{ class: @article_class }
|
||||
{class: @article_class}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require 'phlex'
|
||||
require_relative 'icons'
|
||||
require "phlex"
|
||||
require_relative "icons"
|
||||
|
||||
module Pressa
|
||||
module Views
|
||||
|
|
@ -10,44 +10,43 @@ module Pressa
|
|||
end
|
||||
|
||||
def view_template
|
||||
article(class: 'container project') do
|
||||
h1(id: 'project', data: { title: @project.title }) { @project.title }
|
||||
article(class: "container project") do
|
||||
h1(id: "project", data: {title: @project.title}) { @project.title }
|
||||
h4 { @project.description }
|
||||
|
||||
div(class: 'project-stats') do
|
||||
div(class: "project-stats") do
|
||||
p do
|
||||
a(href: @project.url) { 'GitHub' }
|
||||
plain ' • '
|
||||
a(id: 'nstar', href: stargazers_url)
|
||||
plain ' • '
|
||||
a(id: 'nfork', href: network_url)
|
||||
a(href: @project.url) { "GitHub" }
|
||||
plain " • "
|
||||
a(id: "nstar", href: stargazers_url)
|
||||
plain " • "
|
||||
a(id: "nfork", href: network_url)
|
||||
end
|
||||
|
||||
p do
|
||||
plain 'Last updated on '
|
||||
span(id: 'updated')
|
||||
plain "Last updated on "
|
||||
span(id: "updated")
|
||||
end
|
||||
end
|
||||
|
||||
div(class: 'project-info row clearfix') do
|
||||
div(class: 'column half') do
|
||||
h3 { 'Contributors' }
|
||||
div(id: 'contributors')
|
||||
div(class: "project-info row clearfix") do
|
||||
div(class: "column half") do
|
||||
h3 { "Contributors" }
|
||||
div(id: "contributors")
|
||||
end
|
||||
|
||||
div(class: 'column half') do
|
||||
h3 { 'Languages' }
|
||||
div(id: 'langs')
|
||||
div(class: "column half") do
|
||||
h3 { "Languages" }
|
||||
div(id: "langs")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
div(class: 'row clearfix') do
|
||||
p(class: 'fin') do
|
||||
div(class: "row clearfix") do
|
||||
p(class: "fin") do
|
||||
raw(safe(Icons.code))
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require 'phlex'
|
||||
require_relative 'icons'
|
||||
require "phlex"
|
||||
require_relative "icons"
|
||||
|
||||
module Pressa
|
||||
module Views
|
||||
|
|
@ -10,21 +10,21 @@ module Pressa
|
|||
end
|
||||
|
||||
def view_template
|
||||
article(class: 'container') do
|
||||
h1 { 'Projects' }
|
||||
article(class: "container") do
|
||||
h1 { "Projects" }
|
||||
|
||||
@projects.each do |project|
|
||||
div(class: 'project-listing') do
|
||||
div(class: "project-listing") do
|
||||
h4 do
|
||||
a(href: @site.url_for(project.path)) { project.title }
|
||||
end
|
||||
p(class: 'description') { project.description }
|
||||
p(class: "description") { project.description }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
div(class: 'row clearfix') do
|
||||
p(class: 'fin') do
|
||||
div(class: "row clearfix") do
|
||||
p(class: "fin") do
|
||||
raw(safe(Icons.code))
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require 'phlex'
|
||||
require_relative 'post_view'
|
||||
require "phlex"
|
||||
require_relative "post_view"
|
||||
|
||||
module Pressa
|
||||
module Views
|
||||
|
|
@ -10,7 +10,7 @@ module Pressa
|
|||
end
|
||||
|
||||
def view_template
|
||||
div(class: 'container') do
|
||||
div(class: "container") do
|
||||
@posts.each do |post|
|
||||
render PostView.new(post:, site: @site)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
require 'phlex'
|
||||
require "phlex"
|
||||
|
||||
module Pressa
|
||||
module Views
|
||||
|
|
@ -10,8 +10,8 @@ module Pressa
|
|||
end
|
||||
|
||||
def view_template
|
||||
div(class: 'container') do
|
||||
h2(class: 'year') do
|
||||
div(class: "container") do
|
||||
h2(class: "year") do
|
||||
a(href: year_path) { @year.to_s }
|
||||
end
|
||||
|
||||
|
|
@ -30,13 +30,13 @@ module Pressa
|
|||
def render_month(month_posts)
|
||||
month = month_posts.month
|
||||
|
||||
h3(class: 'month') do
|
||||
h3(class: "month") do
|
||||
a(href: @site.url_for("/posts/#{@year}/#{month.padded}/")) do
|
||||
month.name
|
||||
end
|
||||
end
|
||||
|
||||
ul(class: 'archive') do
|
||||
ul(class: "archive") do
|
||||
month_posts.sorted_posts.each do |post|
|
||||
li do
|
||||
a(href: post_link(post)) { post.title }
|
||||
|
|
@ -51,7 +51,7 @@ module Pressa
|
|||
end
|
||||
|
||||
def short_date(date)
|
||||
date.strftime('%-d %b')
|
||||
date.strftime("%-d %b")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,39 +1,39 @@
|
|||
require 'spec_helper'
|
||||
require 'fileutils'
|
||||
require 'tmpdir'
|
||||
require "spec_helper"
|
||||
require "fileutils"
|
||||
require "tmpdir"
|
||||
|
||||
RSpec.describe Pressa::Config::Loader do
|
||||
describe '#build_site' do
|
||||
it 'builds a site from site.toml and projects.toml' do
|
||||
describe "#build_site" do
|
||||
it "builds a site from site.toml and projects.toml" do
|
||||
with_temp_config do |dir|
|
||||
loader = described_class.new(source_path: dir)
|
||||
site = loader.build_site
|
||||
|
||||
expect(site.author).to eq('Sami Samhuri')
|
||||
expect(site.url).to eq('https://samhuri.net')
|
||||
expect(site.image_url).to eq('https://samhuri.net/images/me.jpg')
|
||||
expect(site.styles.map(&:href)).to eq(['css/style.css'])
|
||||
expect(site.author).to eq("Sami Samhuri")
|
||||
expect(site.url).to eq("https://samhuri.net")
|
||||
expect(site.image_url).to eq("https://samhuri.net/images/me.jpg")
|
||||
expect(site.styles.map(&:href)).to eq(["css/style.css"])
|
||||
|
||||
projects_plugin = site.plugins.find { |plugin| plugin.is_a?(Pressa::Projects::Plugin) }
|
||||
expect(projects_plugin).not_to be_nil
|
||||
expect(projects_plugin.scripts.map(&:src)).to eq(['js/projects.js'])
|
||||
expect(projects_plugin.scripts.map(&:src)).to eq(["js/projects.js"])
|
||||
end
|
||||
end
|
||||
|
||||
it 'applies url_override and rewrites relative image_url with override host' do
|
||||
it "applies url_override and rewrites relative image_url with override host" do
|
||||
with_temp_config do |dir|
|
||||
loader = described_class.new(source_path: dir)
|
||||
site = loader.build_site(url_override: 'https://beta.samhuri.net')
|
||||
site = loader.build_site(url_override: "https://beta.samhuri.net")
|
||||
|
||||
expect(site.url).to eq('https://beta.samhuri.net')
|
||||
expect(site.image_url).to eq('https://beta.samhuri.net/images/me.jpg')
|
||||
expect(site.url).to eq("https://beta.samhuri.net")
|
||||
expect(site.image_url).to eq("https://beta.samhuri.net/images/me.jpg")
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises a validation error for missing required site keys' do
|
||||
it "raises a validation error for missing required site keys" do
|
||||
Dir.mktmpdir do |dir|
|
||||
File.write(File.join(dir, 'site.toml'), "title = \"x\"\n")
|
||||
File.write(File.join(dir, 'projects.toml'), '')
|
||||
File.write(File.join(dir, "site.toml"), "title = \"x\"\n")
|
||||
File.write(File.join(dir, "projects.toml"), "")
|
||||
|
||||
loader = described_class.new(source_path: dir)
|
||||
expect { loader.build_site }.to raise_error(Pressa::Config::ValidationError, /Missing required site\.toml keys/)
|
||||
|
|
@ -43,7 +43,7 @@ RSpec.describe Pressa::Config::Loader do
|
|||
|
||||
def with_temp_config
|
||||
Dir.mktmpdir do |dir|
|
||||
File.write(File.join(dir, 'site.toml'), <<~TOML)
|
||||
File.write(File.join(dir, "site.toml"), <<~TOML)
|
||||
author = "Sami Samhuri"
|
||||
email = "sami@samhuri.net"
|
||||
title = "samhuri.net"
|
||||
|
|
@ -58,7 +58,7 @@ RSpec.describe Pressa::Config::Loader do
|
|||
styles = []
|
||||
TOML
|
||||
|
||||
File.write(File.join(dir, 'projects.toml'), <<~TOML)
|
||||
File.write(File.join(dir, "projects.toml"), <<~TOML)
|
||||
[[projects]]
|
||||
name = "demo"
|
||||
title = "demo"
|
||||
|
|
|
|||
|
|
@ -1,72 +1,72 @@
|
|||
require 'spec_helper'
|
||||
require 'json'
|
||||
require 'tmpdir'
|
||||
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'
|
||||
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(:posts_by_year) { double("posts_by_year", recent_posts: [post]) }
|
||||
let(:writer) { described_class.new(site:, posts_by_year:) }
|
||||
|
||||
context 'for link posts' do
|
||||
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: '<p>hello</p>',
|
||||
excerpt: 'hello...',
|
||||
path: '/posts/2015/05/github-flow-like-a-pro'
|
||||
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: "<p>hello</p>",
|
||||
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
|
||||
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
|
||||
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/')
|
||||
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
|
||||
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: '<p>hello</p>',
|
||||
excerpt: 'hello...',
|
||||
path: '/posts/2017/10/swift-optional-or'
|
||||
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: "<p>hello</p>",
|
||||
excerpt: "hello...",
|
||||
path: "/posts/2017/10/swift-optional-or"
|
||||
)
|
||||
end
|
||||
|
||||
it 'omits external_url' do
|
||||
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
|
||||
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')
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
require 'spec_helper'
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Pressa::Posts::PostMetadata do
|
||||
describe '.parse' do
|
||||
it 'parses valid YAML front-matter' do
|
||||
describe ".parse" do
|
||||
it "parses valid YAML front-matter" do
|
||||
content = <<~MARKDOWN
|
||||
---
|
||||
Title: Test Post
|
||||
|
|
@ -20,19 +20,19 @@ RSpec.describe Pressa::Posts::PostMetadata do
|
|||
|
||||
metadata = described_class.parse(content)
|
||||
|
||||
expect(metadata.title).to eq('Test Post')
|
||||
expect(metadata.author).to eq('Trent Reznor')
|
||||
expect(metadata.formatted_date).to eq('5th November, 2025')
|
||||
expect(metadata.title).to eq("Test Post")
|
||||
expect(metadata.author).to eq("Trent Reznor")
|
||||
expect(metadata.formatted_date).to eq("5th November, 2025")
|
||||
expect(metadata.date.year).to eq(2025)
|
||||
expect(metadata.date.month).to eq(11)
|
||||
expect(metadata.date.day).to eq(5)
|
||||
expect(metadata.link).to eq('https://example.net/external')
|
||||
expect(metadata.tags).to eq(['Ruby', 'Testing'])
|
||||
expect(metadata.scripts.map(&:src)).to eq(['js/highlight.js'])
|
||||
expect(metadata.styles.map(&:href)).to eq(['css/code.css'])
|
||||
expect(metadata.link).to eq("https://example.net/external")
|
||||
expect(metadata.tags).to eq(["Ruby", "Testing"])
|
||||
expect(metadata.scripts.map(&:src)).to eq(["js/highlight.js"])
|
||||
expect(metadata.styles.map(&:href)).to eq(["css/code.css"])
|
||||
end
|
||||
|
||||
it 'raises error when required fields are missing' do
|
||||
it "raises error when required fields are missing" do
|
||||
content = <<~MARKDOWN
|
||||
---
|
||||
Title: Incomplete Post
|
||||
|
|
@ -46,7 +46,7 @@ RSpec.describe Pressa::Posts::PostMetadata do
|
|||
}.to raise_error(/Missing required fields/)
|
||||
end
|
||||
|
||||
it 'handles posts without optional fields' do
|
||||
it "handles posts without optional fields" do
|
||||
content = <<~MARKDOWN
|
||||
---
|
||||
Title: Simple Post
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
require 'spec_helper'
|
||||
require 'fileutils'
|
||||
require 'tmpdir'
|
||||
require "spec_helper"
|
||||
require "fileutils"
|
||||
require "tmpdir"
|
||||
|
||||
RSpec.describe Pressa::Posts::PostRepo do
|
||||
let(:repo) { described_class.new }
|
||||
|
||||
describe '#read_posts' do
|
||||
it 'reads and organizes posts by year and month' do
|
||||
describe "#read_posts" do
|
||||
it "reads and organizes posts by year and month" do
|
||||
Dir.mktmpdir do |tmpdir|
|
||||
posts_dir = File.join(tmpdir, 'posts', '2025', '11')
|
||||
posts_dir = File.join(tmpdir, "posts", "2025", "11")
|
||||
FileUtils.mkdir_p(posts_dir)
|
||||
|
||||
post_content = <<~MARKDOWN
|
||||
|
|
@ -22,25 +22,25 @@ RSpec.describe Pressa::Posts::PostRepo do
|
|||
Had an epic day at Whistler. The powder was deep and the lines were short.
|
||||
MARKDOWN
|
||||
|
||||
File.write(File.join(posts_dir, 'shredding.md'), post_content)
|
||||
File.write(File.join(posts_dir, "shredding.md"), post_content)
|
||||
|
||||
posts_by_year = repo.read_posts(File.join(tmpdir, 'posts'))
|
||||
posts_by_year = repo.read_posts(File.join(tmpdir, "posts"))
|
||||
|
||||
expect(posts_by_year.all_posts.length).to eq(1)
|
||||
|
||||
post = posts_by_year.all_posts.first
|
||||
expect(post.title).to eq('Shredding in November')
|
||||
expect(post.author).to eq('Shaun White')
|
||||
expect(post.slug).to eq('shredding')
|
||||
expect(post.title).to eq("Shredding in November")
|
||||
expect(post.author).to eq("Shaun White")
|
||||
expect(post.slug).to eq("shredding")
|
||||
expect(post.year).to eq(2025)
|
||||
expect(post.month).to eq(11)
|
||||
expect(post.path).to eq('/posts/2025/11/shredding')
|
||||
expect(post.path).to eq("/posts/2025/11/shredding")
|
||||
end
|
||||
end
|
||||
|
||||
it 'generates excerpts from post content' do
|
||||
it "generates excerpts from post content" do
|
||||
Dir.mktmpdir do |tmpdir|
|
||||
posts_dir = File.join(tmpdir, 'posts', '2025', '11')
|
||||
posts_dir = File.join(tmpdir, "posts", "2025", "11")
|
||||
FileUtils.mkdir_p(posts_dir)
|
||||
|
||||
post_content = <<~MARKDOWN
|
||||
|
|
@ -58,15 +58,15 @@ RSpec.describe Pressa::Posts::PostRepo do
|
|||
More content with a [link](https://example.net).
|
||||
MARKDOWN
|
||||
|
||||
File.write(File.join(posts_dir, 'test.md'), post_content)
|
||||
File.write(File.join(posts_dir, "test.md"), post_content)
|
||||
|
||||
posts_by_year = repo.read_posts(File.join(tmpdir, 'posts'))
|
||||
posts_by_year = repo.read_posts(File.join(tmpdir, "posts"))
|
||||
post = posts_by_year.all_posts.first
|
||||
|
||||
expect(post.excerpt).to include('test post')
|
||||
expect(post.excerpt).not_to include('![')
|
||||
expect(post.excerpt).to include('link')
|
||||
expect(post.excerpt).not_to include('[link]')
|
||||
expect(post.excerpt).to include("test post")
|
||||
expect(post.excerpt).not_to include("![")
|
||||
expect(post.excerpt).to include("link")
|
||||
expect(post.excerpt).not_to include("[link]")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
require 'spec_helper'
|
||||
require 'fileutils'
|
||||
require 'tmpdir'
|
||||
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',
|
||||
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
|
||||
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')
|
||||
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:)
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ RSpec.describe Pressa::SiteGenerator do
|
|||
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')
|
||||
expect(File.read(source_file)).to eq("safe")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
require_relative '../lib/pressa'
|
||||
require_relative "../lib/pressa"
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.expect_with :rspec do |expectations|
|
||||
|
|
|
|||
|
|
@ -1,45 +1,47 @@
|
|||
require 'spec_helper'
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Pressa::Views::Layout do
|
||||
class TestContentView < Phlex::HTML
|
||||
def view_template
|
||||
article do
|
||||
h1 { 'Hello' }
|
||||
let(:test_content_view) do
|
||||
Class.new(Phlex::HTML) do
|
||||
def view_template
|
||||
article do
|
||||
h1 { "Hello" }
|
||||
end
|
||||
end
|
||||
end
|
||||
end.new
|
||||
end
|
||||
|
||||
let(:site) do
|
||||
Pressa::Site.new(
|
||||
author: 'Sami Samhuri',
|
||||
email: 'sami@samhuri.net',
|
||||
title: 'samhuri.net',
|
||||
description: 'blog',
|
||||
url: 'https://samhuri.net'
|
||||
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
|
||||
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
|
||||
canonical_url: "https://samhuri.net/posts/",
|
||||
content: test_content_view
|
||||
).call
|
||||
|
||||
expect(html).to include('<article>')
|
||||
expect(html).to include('<h1>Hello</h1>')
|
||||
expect(html).not_to include('<article>')
|
||||
expect(html).to include("<article>")
|
||||
expect(html).to include("<h1>Hello</h1>")
|
||||
expect(html).not_to include("<article>")
|
||||
end
|
||||
|
||||
it 'keeps escaping enabled for untrusted string fields' do
|
||||
subtitle = '<img src=x onerror=alert(1)>'
|
||||
it "keeps escaping enabled for untrusted string fields" do
|
||||
subtitle = "<img src=x onerror=alert(1)>"
|
||||
html = described_class.new(
|
||||
site:,
|
||||
canonical_url: 'https://samhuri.net/posts/',
|
||||
canonical_url: "https://samhuri.net/posts/",
|
||||
page_subtitle: subtitle,
|
||||
content: TestContentView.new
|
||||
content: test_content_view
|
||||
).call
|
||||
|
||||
expect(html).to include('<title>samhuri.net: <img src=x onerror=alert(1)></title>')
|
||||
expect(html).to include("<title>samhuri.net: <img src=x onerror=alert(1)></title>")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue