mirror of
https://github.com/samsonjs/samhuri.net.git
synced 2026-04-27 14:57:40 +00:00
Move bin workflows into Bake tasks
This commit is contained in:
parent
5ef44a5ccb
commit
d4e9b53822
8 changed files with 276 additions and 324 deletions
28
Readme.md
28
Readme.md
|
|
@ -7,8 +7,7 @@ Source code for [samhuri.net](https://samhuri.net), powered by a Ruby static sit
|
||||||
This repository is now a single integrated Ruby project. The legacy Swift generators (`gensite/` and `samhuri.net/`) have been removed.
|
This repository is now a single integrated Ruby project. The legacy Swift generators (`gensite/` and `samhuri.net/`) have been removed.
|
||||||
|
|
||||||
- Generator core: `lib/`
|
- Generator core: `lib/`
|
||||||
- Build tasks: `bake.rb`
|
- Build tasks and utility workflows: `bake.rb`
|
||||||
- CLI and utilities: `bin/`
|
|
||||||
- Tests: `spec/`
|
- Tests: `spec/`
|
||||||
- Config: `site.toml` and `projects.toml`
|
- Config: `site.toml` and `projects.toml`
|
||||||
- Content: `posts/` and `public/`
|
- Content: `posts/` and `public/`
|
||||||
|
|
@ -22,12 +21,6 @@ This repository is now a single integrated Ruby project. The legacy Swift genera
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
```bash
|
|
||||||
bin/bootstrap
|
|
||||||
```
|
|
||||||
|
|
||||||
Or manually:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rbenv install -s "$(cat .ruby-version)"
|
rbenv install -s "$(cat .ruby-version)"
|
||||||
rbenv exec bundle install
|
rbenv exec bundle install
|
||||||
|
|
@ -55,6 +48,9 @@ Other targets:
|
||||||
rbenv exec bundle exec bake mudge
|
rbenv exec bundle exec bake mudge
|
||||||
rbenv exec bundle exec bake beta
|
rbenv exec bundle exec bake beta
|
||||||
rbenv exec bundle exec bake release
|
rbenv exec bundle exec bake release
|
||||||
|
rbenv exec bundle exec bake generate . www https://samhuri.net
|
||||||
|
rbenv exec bundle exec bake watch target=debug
|
||||||
|
rbenv exec bundle exec bake deploy --test true --delete true
|
||||||
rbenv exec bundle exec bake publish_beta
|
rbenv exec bundle exec bake publish_beta
|
||||||
rbenv exec bundle exec bake publish
|
rbenv exec bundle exec bake publish
|
||||||
```
|
```
|
||||||
|
|
@ -62,8 +58,8 @@ rbenv exec bundle exec bake publish
|
||||||
## Draft Workflow
|
## Draft Workflow
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bin/new-draft "Post title"
|
rbenv exec bundle exec bake new_draft "Post title"
|
||||||
bin/publish-draft public/drafts/post-title.md
|
rbenv exec bundle exec bake publish_draft public/drafts/post-title.md
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tests And Lint
|
## Tests And Lint
|
||||||
|
|
@ -80,15 +76,15 @@ rbenv exec bundle exec bake test
|
||||||
rbenv exec bundle exec bake lint
|
rbenv exec bundle exec bake lint
|
||||||
```
|
```
|
||||||
|
|
||||||
## Site Generation CLI
|
## Site Generation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bin/pressa SOURCE TARGET [URL]
|
rbenv exec bundle exec bake generate SOURCE TARGET [URL]
|
||||||
# example
|
# example:
|
||||||
bin/pressa . www https://samhuri.net
|
rbenv exec bundle exec bake generate . www https://samhuri.net
|
||||||
```
|
```
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- `bin/watch` is Linux-only and requires `inotifywait`.
|
- `bake watch` is Linux-only and requires `inotifywait`.
|
||||||
- Deployment uses `rsync` to the configured `mudge` host paths in `bake.rb` and `bin/publish`.
|
- Deployment uses `rsync` to the configured `mudge` host paths in `bake.rb`.
|
||||||
|
|
|
||||||
278
bake.rb
278
bake.rb
|
|
@ -1,5 +1,16 @@
|
||||||
# Build tasks for samhuri.net static site generator
|
# Build tasks for samhuri.net static site generator
|
||||||
|
|
||||||
|
require 'etc'
|
||||||
|
require 'fileutils'
|
||||||
|
require 'shellwords'
|
||||||
|
|
||||||
|
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
|
||||||
|
BUILD_TARGETS = %w[debug mudge beta release].freeze
|
||||||
|
|
||||||
# Generate the site in debug mode (localhost:8000)
|
# Generate the site in debug mode (localhost:8000)
|
||||||
def debug
|
def debug
|
||||||
build('http://localhost:8000')
|
build('http://localhost:8000')
|
||||||
|
|
@ -25,29 +36,158 @@ def serve
|
||||||
require 'webrick'
|
require 'webrick'
|
||||||
server = WEBrick::HTTPServer.new(Port: 8000, DocumentRoot: 'www')
|
server = WEBrick::HTTPServer.new(Port: 8000, DocumentRoot: 'www')
|
||||||
trap('INT') { server.shutdown }
|
trap('INT') { server.shutdown }
|
||||||
puts "Server running at http://localhost:8000"
|
puts 'Server running at http://localhost:8000'
|
||||||
server.start
|
server.start
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Generate a site from an arbitrary source directory into a target directory.
|
||||||
|
# @parameter source_path [String] Directory containing site sources.
|
||||||
|
# @parameter target_path [String] Directory to write generated site.
|
||||||
|
# @parameter url [String] Optional site URL override.
|
||||||
|
def generate(source_path = '.', target_path = 'www', url = nil)
|
||||||
|
require_relative 'lib/pressa'
|
||||||
|
|
||||||
|
site = Pressa.create_site(source_path:, url_override: url)
|
||||||
|
generator = Pressa::SiteGenerator.new(site:)
|
||||||
|
generator.generate(source_path:, target_path:)
|
||||||
|
puts "Site built successfully in #{target_path}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Install local prerequisites and gem dependencies.
|
||||||
|
def setup
|
||||||
|
ruby_version = File.read('.ruby-version').strip
|
||||||
|
|
||||||
|
if RUBY_PLATFORM.include?('linux')
|
||||||
|
puts '*** installing Linux prerequisites'
|
||||||
|
unless system('sudo', 'apt', 'install', '-y',
|
||||||
|
'build-essential',
|
||||||
|
'git',
|
||||||
|
'inotify-tools',
|
||||||
|
'libffi-dev',
|
||||||
|
'libyaml-dev',
|
||||||
|
'pkg-config',
|
||||||
|
'zlib1g-dev')
|
||||||
|
abort 'Error: failed to install Linux prerequisites.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if command_available?('rbenv')
|
||||||
|
puts "*** using rbenv (ruby #{ruby_version})"
|
||||||
|
abort 'Error: rbenv install failed.' unless system('rbenv', 'install', '-s', ruby_version)
|
||||||
|
abort 'Error: bundle install failed.' unless system('rbenv', 'exec', 'bundle', 'install')
|
||||||
|
else
|
||||||
|
puts '*** rbenv not found, using system Ruby'
|
||||||
|
abort 'Error: bundle install failed.' unless system('bundle', 'install')
|
||||||
|
end
|
||||||
|
|
||||||
|
puts '*** done'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create a new draft in public/drafts/.
|
||||||
|
# @parameter title_parts [Array] Optional title words; defaults to Untitled.
|
||||||
|
def new_draft(*title_parts)
|
||||||
|
title, filename =
|
||||||
|
if title_parts.empty?
|
||||||
|
['Untitled', next_available_draft]
|
||||||
|
else
|
||||||
|
given_title = title_parts.join(' ')
|
||||||
|
slug = slugify(given_title)
|
||||||
|
abort 'Error: title cannot be converted to a filename.' if slug.empty?
|
||||||
|
|
||||||
|
filename = "#{slug}.md"
|
||||||
|
path = draft_path(filename)
|
||||||
|
abort "Error: draft already exists at #{path}" if File.exist?(path)
|
||||||
|
|
||||||
|
[given_title, filename]
|
||||||
|
end
|
||||||
|
|
||||||
|
FileUtils.mkdir_p(DRAFTS_DIR)
|
||||||
|
path = draft_path(filename)
|
||||||
|
content = render_draft_template(title)
|
||||||
|
File.write(path, content)
|
||||||
|
|
||||||
|
puts "Created new draft at #{path}"
|
||||||
|
puts '>>> Contents below <<<'
|
||||||
|
puts
|
||||||
|
puts content
|
||||||
|
end
|
||||||
|
|
||||||
|
# Publish a draft by moving it to posts/YYYY/MM and updating dates.
|
||||||
|
# @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
|
||||||
|
puts 'Available drafts:'
|
||||||
|
drafts = Dir.glob("#{DRAFTS_DIR}/*.md").map { |path| File.basename(path) }
|
||||||
|
if drafts.empty?
|
||||||
|
puts ' (no drafts found)'
|
||||||
|
else
|
||||||
|
drafts.each { |draft| puts " #{draft}" }
|
||||||
|
end
|
||||||
|
abort
|
||||||
|
end
|
||||||
|
|
||||||
|
draft_path_value, draft_file = resolve_draft_input(input_path)
|
||||||
|
abort "Error: File not found: #{draft_path_value}" unless File.exist?(draft_path_value)
|
||||||
|
|
||||||
|
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')}")
|
||||||
|
|
||||||
|
target_dir = "posts/#{now.strftime('%Y/%m')}"
|
||||||
|
FileUtils.mkdir_p(target_dir)
|
||||||
|
target_path = "#{target_dir}/#{draft_file}"
|
||||||
|
|
||||||
|
File.write(target_path, content)
|
||||||
|
FileUtils.rm_f(draft_path_value)
|
||||||
|
|
||||||
|
puts "Published draft: #{draft_path_value} -> #{target_path}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Watch content directories and rebuild on every change.
|
||||||
|
# @parameter target [String] One of debug, mudge, beta, or release.
|
||||||
|
def watch(target: 'mudge')
|
||||||
|
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)
|
||||||
|
puts "changed at #{Time.now}"
|
||||||
|
sleep 2
|
||||||
|
run_build_target(target)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Deploy files via rsync without building first.
|
||||||
|
# @parameter beta [Boolean] Deploy to beta host path.
|
||||||
|
# @parameter test [Boolean] Enable rsync --dry-run.
|
||||||
|
# @parameter delete [Boolean] Enable rsync --delete.
|
||||||
|
# @parameter paths [Array] Optional local paths; defaults to www/.
|
||||||
|
def deploy(*paths, beta: false, test: false, delete: false)
|
||||||
|
publish_dir = truthy?(beta) ? BETA_PUBLISH_DIR : PRODUCTION_PUBLISH_DIR
|
||||||
|
local_paths = paths.empty? ? ['www/'] : paths
|
||||||
|
run_rsync(local_paths:, publish_dir:, dry_run: test, delete:)
|
||||||
|
end
|
||||||
|
|
||||||
# Publish to beta/staging server
|
# Publish to beta/staging server
|
||||||
def publish_beta
|
def publish_beta
|
||||||
beta
|
beta
|
||||||
puts "Deploying to beta server..."
|
run_rsync(local_paths: ['www/'], publish_dir: BETA_PUBLISH_DIR, dry_run: false, delete: true)
|
||||||
system('rsync -avz --delete www/ mudge:/var/www/beta.samhuri.net/public')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Publish to production server
|
# Publish to production server
|
||||||
def publish
|
def publish
|
||||||
release
|
release
|
||||||
puts "Deploying to production server..."
|
run_rsync(local_paths: ['www/'], publish_dir: PRODUCTION_PUBLISH_DIR, dry_run: false, delete: true)
|
||||||
system('rsync -avz --delete www/ mudge:/var/www/samhuri.net/public')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Clean generated files
|
# Clean generated files
|
||||||
def clean
|
def clean
|
||||||
require 'fileutils'
|
|
||||||
FileUtils.rm_rf('www')
|
FileUtils.rm_rf('www')
|
||||||
puts "Cleaned www/ directory"
|
puts 'Cleaned www/ directory'
|
||||||
end
|
end
|
||||||
|
|
||||||
# Run RSpec tests
|
# Run RSpec tests
|
||||||
|
|
@ -62,7 +202,7 @@ end
|
||||||
|
|
||||||
# List all available drafts
|
# List all available drafts
|
||||||
def drafts
|
def drafts
|
||||||
Dir.glob('public/drafts/*.md').sort.each do |draft|
|
Dir.glob("#{DRAFTS_DIR}/*.md").sort.each do |draft|
|
||||||
puts File.basename(draft)
|
puts File.basename(draft)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -82,11 +222,121 @@ private
|
||||||
# Build the site with specified URL
|
# Build the site with specified URL
|
||||||
# @parameter url [String] The site URL to use
|
# @parameter url [String] The site URL to use
|
||||||
def build(url)
|
def build(url)
|
||||||
require_relative 'lib/pressa'
|
|
||||||
|
|
||||||
puts "Building site for #{url}..."
|
puts "Building site for #{url}..."
|
||||||
site = Pressa.create_site(source_path: '.', url_override: url)
|
generate('.', 'www', url)
|
||||||
generator = Pressa::SiteGenerator.new(site:)
|
end
|
||||||
generator.generate(source_path: '.', target_path: 'www')
|
|
||||||
puts "Site built successfully in www/"
|
def run_rsync(local_paths:, publish_dir:, dry_run:, delete:)
|
||||||
|
command = ['rsync', '-aKv', '-e', 'ssh -4']
|
||||||
|
command << '--dry-run' if truthy?(dry_run)
|
||||||
|
command << '--delete' if truthy?(delete)
|
||||||
|
command.concat(local_paths)
|
||||||
|
command << "#{PUBLISH_HOST}:#{publish_dir}"
|
||||||
|
|
||||||
|
puts "Running: #{Shellwords.join(command)}"
|
||||||
|
abort 'Error: rsync failed.' unless system(*command)
|
||||||
|
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(', ')}"
|
||||||
|
end
|
||||||
|
|
||||||
|
public_send(target_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def watch_paths
|
||||||
|
WATCHABLE_DIRECTORIES.flat_map { |path| ['-r', path] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve_draft_input(input_path)
|
||||||
|
if input_path.include?('/')
|
||||||
|
if input_path.start_with?('posts/')
|
||||||
|
abort "Error: '#{input_path}' is already published in posts/ directory"
|
||||||
|
end
|
||||||
|
|
||||||
|
[input_path, File.basename(input_path)]
|
||||||
|
else
|
||||||
|
[draft_path(input_path), input_path]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def draft_path(filename)
|
||||||
|
File.join(DRAFTS_DIR, filename)
|
||||||
|
end
|
||||||
|
|
||||||
|
def slugify(title)
|
||||||
|
title.downcase
|
||||||
|
.gsub(/[^a-z0-9\s-]/, '')
|
||||||
|
.gsub(/\s+/, '-')
|
||||||
|
.gsub(/-+/, '-')
|
||||||
|
.gsub(/^-|-$/, '')
|
||||||
|
end
|
||||||
|
|
||||||
|
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')
|
||||||
|
counter = 1
|
||||||
|
loop do
|
||||||
|
numbered_filename = "#{name_without_ext}-#{counter}.md"
|
||||||
|
return numbered_filename unless File.exist?(draft_path(numbered_filename))
|
||||||
|
|
||||||
|
counter += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_draft_template(title)
|
||||||
|
now = Time.now
|
||||||
|
<<~FRONTMATTER
|
||||||
|
---
|
||||||
|
Author: #{current_author}
|
||||||
|
Title: #{title}
|
||||||
|
Date: unpublished
|
||||||
|
Timestamp: #{now.strftime('%Y-%m-%dT%H:%M:%S%:z')}
|
||||||
|
Tags:
|
||||||
|
---
|
||||||
|
|
||||||
|
# #{title}
|
||||||
|
|
||||||
|
TKTK
|
||||||
|
FRONTMATTER
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_author
|
||||||
|
Etc.getlogin || ENV['USER'] || `whoami`.strip
|
||||||
|
rescue StandardError
|
||||||
|
ENV['USER'] || `whoami`.strip
|
||||||
|
end
|
||||||
|
|
||||||
|
def ordinal_date(time)
|
||||||
|
day = time.day
|
||||||
|
suffix = case day
|
||||||
|
when 1, 21, 31
|
||||||
|
'st'
|
||||||
|
when 2, 22
|
||||||
|
'nd'
|
||||||
|
when 3, 23
|
||||||
|
'rd'
|
||||||
|
else
|
||||||
|
'th'
|
||||||
|
end
|
||||||
|
|
||||||
|
time.strftime("#{day}#{suffix} %B, %Y")
|
||||||
|
end
|
||||||
|
|
||||||
|
def command_available?(command)
|
||||||
|
system('which', command, out: File::NULL, err: File::NULL)
|
||||||
|
end
|
||||||
|
|
||||||
|
def truthy?(value)
|
||||||
|
case value
|
||||||
|
when true
|
||||||
|
true
|
||||||
|
when false, nil
|
||||||
|
false
|
||||||
|
else
|
||||||
|
%w[1 true yes on].include?(value.to_s.downcase)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
||||||
RUBY_VERSION="$(cat "$ROOT_DIR/.ruby-version")"
|
|
||||||
|
|
||||||
if [[ "$(uname)" = "Linux" ]]; then
|
|
||||||
echo "*** installing Linux prerequisites"
|
|
||||||
sudo apt install -y \
|
|
||||||
build-essential \
|
|
||||||
git \
|
|
||||||
inotify-tools \
|
|
||||||
libffi-dev \
|
|
||||||
libyaml-dev \
|
|
||||||
pkg-config \
|
|
||||||
zlib1g-dev
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$ROOT_DIR"
|
|
||||||
|
|
||||||
if command -v rbenv >/dev/null 2>/dev/null; then
|
|
||||||
echo "*** using rbenv (ruby $RUBY_VERSION)"
|
|
||||||
rbenv install -s "$RUBY_VERSION"
|
|
||||||
rbenv exec bundle install
|
|
||||||
else
|
|
||||||
echo "*** rbenv not found, using system Ruby"
|
|
||||||
bundle install
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "*** done"
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
#!/usr/bin/env ruby -w
|
|
||||||
|
|
||||||
require 'fileutils'
|
|
||||||
|
|
||||||
DRAFTS_DIR = File.expand_path("../public/drafts", __dir__).freeze
|
|
||||||
|
|
||||||
def usage
|
|
||||||
puts "Usage: #{$0} [title]"
|
|
||||||
puts
|
|
||||||
puts "Examples:"
|
|
||||||
puts " #{$0} Top 5 Ways to Write Clickbait # using a title without quotes"
|
|
||||||
puts " #{$0} 'Something with punctuation?!' # fancy chars need quotes"
|
|
||||||
puts " #{$0} working-with-databases # using a slug"
|
|
||||||
puts " #{$0} # Creates untitled.md (or untitled-2.md, etc.)"
|
|
||||||
puts
|
|
||||||
puts "Creates a new draft in public/drafts/ directory with proper frontmatter."
|
|
||||||
end
|
|
||||||
|
|
||||||
def draft_path(filename)
|
|
||||||
File.join(DRAFTS_DIR, filename)
|
|
||||||
end
|
|
||||||
|
|
||||||
def main
|
|
||||||
if ARGV.include?('-h') || ARGV.include?('--help')
|
|
||||||
usage
|
|
||||||
exit 0
|
|
||||||
end
|
|
||||||
|
|
||||||
title, filename =
|
|
||||||
if ARGV.empty?
|
|
||||||
['Untitled', next_available_draft]
|
|
||||||
else
|
|
||||||
given_title = ARGV.join(' ')
|
|
||||||
filename = "#{slugify(given_title)}.md"
|
|
||||||
path = draft_path(filename)
|
|
||||||
if File.exist?(path)
|
|
||||||
puts "Error: draft already exists at #{path}"
|
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
|
|
||||||
[given_title, filename]
|
|
||||||
end
|
|
||||||
|
|
||||||
FileUtils.mkdir_p(DRAFTS_DIR)
|
|
||||||
path = draft_path(filename)
|
|
||||||
content = render_template(title)
|
|
||||||
File.write(path, content)
|
|
||||||
|
|
||||||
puts "Created new draft at #{path}"
|
|
||||||
puts '>>> Contents below <<<'
|
|
||||||
puts
|
|
||||||
puts content
|
|
||||||
end
|
|
||||||
|
|
||||||
def slugify(title)
|
|
||||||
title.downcase
|
|
||||||
.gsub(/[^a-z0-9\s-]/, '')
|
|
||||||
.gsub(/\s+/, '-')
|
|
||||||
.gsub(/-+/, '-')
|
|
||||||
.gsub(/^-|-$/, '')
|
|
||||||
end
|
|
||||||
|
|
||||||
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')
|
|
||||||
counter = 1
|
|
||||||
loop do
|
|
||||||
numbered_filename = "#{name_without_ext}-#{counter}.md"
|
|
||||||
return numbered_filename unless File.exist?(draft_path(numbered_filename))
|
|
||||||
counter += 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_template(title)
|
|
||||||
now = Time.now
|
|
||||||
iso_timestamp = now.strftime('%Y-%m-%dT%H:%M:%S%:z')
|
|
||||||
|
|
||||||
<<~FRONTMATTER
|
|
||||||
---
|
|
||||||
Author: #{`whoami`.strip}
|
|
||||||
Title: #{title}
|
|
||||||
Date: unpublished
|
|
||||||
Timestamp: #{iso_timestamp}
|
|
||||||
Tags:
|
|
||||||
---
|
|
||||||
|
|
||||||
# #{title}
|
|
||||||
|
|
||||||
TKTK
|
|
||||||
FRONTMATTER
|
|
||||||
end
|
|
||||||
|
|
||||||
main if $0 == __FILE__
|
|
||||||
28
bin/pressa
28
bin/pressa
|
|
@ -1,28 +0,0 @@
|
||||||
#!/usr/bin/env ruby
|
|
||||||
|
|
||||||
require_relative '../lib/pressa'
|
|
||||||
|
|
||||||
if ARGV.length < 2
|
|
||||||
puts "Usage: pressa SOURCE TARGET [URL]"
|
|
||||||
puts ""
|
|
||||||
puts "Arguments:"
|
|
||||||
puts " SOURCE Directory containing posts/ and public/"
|
|
||||||
puts " TARGET Directory to write generated site"
|
|
||||||
puts " URL Optional site URL override"
|
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
|
|
||||||
source_path = ARGV[0]
|
|
||||||
target_path = ARGV[1]
|
|
||||||
site_url = ARGV[2]
|
|
||||||
|
|
||||||
begin
|
|
||||||
site = Pressa.create_site(source_path:, url_override: site_url)
|
|
||||||
generator = Pressa::SiteGenerator.new(site:)
|
|
||||||
generator.generate(source_path:, target_path:)
|
|
||||||
puts "Site generated successfully!"
|
|
||||||
rescue => e
|
|
||||||
puts "Error: #{e.message}"
|
|
||||||
puts e.backtrace
|
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
54
bin/publish
54
bin/publish
|
|
@ -1,54 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# exit on errors
|
|
||||||
set -e
|
|
||||||
|
|
||||||
PUBLISH_HOST="mudge"
|
|
||||||
PUBLISH_DIR="/var/www/samhuri.net/public"
|
|
||||||
ECHO=0
|
|
||||||
RSYNC_OPTS=""
|
|
||||||
|
|
||||||
BREAK_WHILE=0
|
|
||||||
while [[ $# > 0 ]]; do
|
|
||||||
ARG="$1"
|
|
||||||
case "$ARG" in
|
|
||||||
|
|
||||||
-b|--beta)
|
|
||||||
PUBLISH_DIR="/var/www/beta.samhuri.net/public"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
|
|
||||||
-t|--test)
|
|
||||||
ECHO=1
|
|
||||||
RSYNC_OPTS="$RSYNC_OPTS --dry-run"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
|
|
||||||
-d|--delete)
|
|
||||||
RSYNC_OPTS="$RSYNC_OPTS --delete"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
|
|
||||||
# we're at the paths, no more options
|
|
||||||
*)
|
|
||||||
BREAK_WHILE=1
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
|
|
||||||
esac
|
|
||||||
|
|
||||||
[[ $BREAK_WHILE -eq 1 ]] && break
|
|
||||||
done
|
|
||||||
|
|
||||||
declare -a CMD
|
|
||||||
if [[ $# -eq 0 ]]; then
|
|
||||||
CMD=(rsync -aKv -e "ssh -4" $RSYNC_OPTS www/ $PUBLISH_HOST:$PUBLISH_DIR)
|
|
||||||
else
|
|
||||||
CMD=(rsync -aKv -e "ssh -4" $RSYNC_OPTS $@ $PUBLISH_HOST:$PUBLISH_DIR)
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $ECHO -eq 1 ]]; then
|
|
||||||
echo "${CMD[@]}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
"${CMD[@]}"
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
#!/usr/bin/env ruby -w
|
|
||||||
|
|
||||||
require 'fileutils'
|
|
||||||
|
|
||||||
def usage
|
|
||||||
puts "Usage: #{$0} <draft-path-or-filename>"
|
|
||||||
puts
|
|
||||||
puts "Examples:"
|
|
||||||
puts " #{$0} public/drafts/reverse-engineering-photo-urls.md"
|
|
||||||
puts
|
|
||||||
puts "Available drafts:"
|
|
||||||
drafts = Dir.glob('public/drafts/*.md').map { |f| File.basename(f) }
|
|
||||||
if drafts.empty?
|
|
||||||
puts " (no drafts found)"
|
|
||||||
else
|
|
||||||
drafts.each { |d| puts " #{d}" }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if ARGV.empty?
|
|
||||||
usage
|
|
||||||
abort
|
|
||||||
end
|
|
||||||
|
|
||||||
input_path = ARGV.first
|
|
||||||
|
|
||||||
# Handle both full paths and just filenames
|
|
||||||
if input_path.include?('/')
|
|
||||||
draft_path = input_path
|
|
||||||
draft_file = File.basename(input_path)
|
|
||||||
if input_path.start_with?('posts/')
|
|
||||||
abort "Error: '#{input_path}' is already published in posts/ directory"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
draft_file = input_path
|
|
||||||
draft_path = "public/drafts/#{draft_file}"
|
|
||||||
end
|
|
||||||
|
|
||||||
abort "Error: File not found: #{draft_path}" unless File.exist?(draft_path)
|
|
||||||
|
|
||||||
# Update display date timestamp to current time
|
|
||||||
def ordinal_date(time)
|
|
||||||
day = time.day
|
|
||||||
suffix = case day
|
|
||||||
when 1, 21, 31 then 'st'
|
|
||||||
when 2, 22 then 'nd'
|
|
||||||
when 3, 23 then 'rd'
|
|
||||||
else 'th'
|
|
||||||
end
|
|
||||||
time.strftime("#{day}#{suffix} %B, %Y")
|
|
||||||
end
|
|
||||||
now = Time.now
|
|
||||||
iso_timestamp = now.strftime('%Y-%m-%dT%H:%M:%S%:z')
|
|
||||||
human_date = ordinal_date(now)
|
|
||||||
content = File.read(draft_path)
|
|
||||||
content.sub!(/^Date:.*$/, "Date: #{human_date}")
|
|
||||||
content.sub!(/^Timestamp:.*$/, "Timestamp: #{iso_timestamp}")
|
|
||||||
|
|
||||||
# Use current year/month for directory, pad with strftime
|
|
||||||
year_month = now.strftime('%Y-%m')
|
|
||||||
year, month = year_month.split('-')
|
|
||||||
|
|
||||||
target_dir = "posts/#{year}/#{month}"
|
|
||||||
FileUtils.mkdir_p(target_dir)
|
|
||||||
target_path = "#{target_dir}/#{draft_file}"
|
|
||||||
|
|
||||||
File.write(target_path, content)
|
|
||||||
FileUtils.rm_f(draft_path)
|
|
||||||
|
|
||||||
puts "Published draft: #{draft_path} → #{target_path}"
|
|
||||||
17
bin/watch
17
bin/watch
|
|
@ -1,17 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
BLOG_TARGET=${BLOG_TARGET:-mudge}
|
|
||||||
|
|
||||||
if ! command -v inotifywait >/dev/null 2>/dev/null; then
|
|
||||||
echo "inotifywait is required (install inotify-tools)."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
inotifywait -e modify,create,delete,move -r public -r posts -r lib
|
|
||||||
echo "changed at $(date)"
|
|
||||||
sleep 2
|
|
||||||
rbenv exec bundle exec bake "$BLOG_TARGET"
|
|
||||||
done
|
|
||||||
Loading…
Reference in a new issue