mirror of
https://github.com/samsonjs/samhuri.net.git
synced 2026-04-11 11:35:53 +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.
|
||||
|
||||
- Generator core: `lib/`
|
||||
- Build tasks: `bake.rb`
|
||||
- CLI and utilities: `bin/`
|
||||
- Build tasks and utility workflows: `bake.rb`
|
||||
- Tests: `spec/`
|
||||
- Config: `site.toml` and `projects.toml`
|
||||
- Content: `posts/` and `public/`
|
||||
|
|
@ -22,12 +21,6 @@ This repository is now a single integrated Ruby project. The legacy Swift genera
|
|||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
bin/bootstrap
|
||||
```
|
||||
|
||||
Or manually:
|
||||
|
||||
```bash
|
||||
rbenv install -s "$(cat .ruby-version)"
|
||||
rbenv exec bundle install
|
||||
|
|
@ -55,6 +48,9 @@ Other targets:
|
|||
rbenv exec bundle exec bake mudge
|
||||
rbenv exec bundle exec bake beta
|
||||
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
|
||||
```
|
||||
|
|
@ -62,8 +58,8 @@ rbenv exec bundle exec bake publish
|
|||
## Draft Workflow
|
||||
|
||||
```bash
|
||||
bin/new-draft "Post title"
|
||||
bin/publish-draft public/drafts/post-title.md
|
||||
rbenv exec bundle exec bake new_draft "Post title"
|
||||
rbenv exec bundle exec bake publish_draft public/drafts/post-title.md
|
||||
```
|
||||
|
||||
## Tests And Lint
|
||||
|
|
@ -80,15 +76,15 @@ rbenv exec bundle exec bake test
|
|||
rbenv exec bundle exec bake lint
|
||||
```
|
||||
|
||||
## Site Generation CLI
|
||||
## Site Generation
|
||||
|
||||
```bash
|
||||
bin/pressa SOURCE TARGET [URL]
|
||||
# example
|
||||
bin/pressa . www https://samhuri.net
|
||||
rbenv exec bundle exec bake generate SOURCE TARGET [URL]
|
||||
# example:
|
||||
rbenv exec bundle exec bake generate . www https://samhuri.net
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- `bin/watch` is Linux-only and requires `inotifywait`.
|
||||
- Deployment uses `rsync` to the configured `mudge` host paths in `bake.rb` and `bin/publish`.
|
||||
- `bake watch` is Linux-only and requires `inotifywait`.
|
||||
- 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
|
||||
|
||||
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)
|
||||
def debug
|
||||
build('http://localhost:8000')
|
||||
|
|
@ -25,29 +36,158 @@ def serve
|
|||
require 'webrick'
|
||||
server = WEBrick::HTTPServer.new(Port: 8000, DocumentRoot: 'www')
|
||||
trap('INT') { server.shutdown }
|
||||
puts "Server running at http://localhost:8000"
|
||||
puts 'Server running at http://localhost:8000'
|
||||
server.start
|
||||
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
|
||||
def publish_beta
|
||||
beta
|
||||
puts "Deploying to beta server..."
|
||||
system('rsync -avz --delete www/ mudge:/var/www/beta.samhuri.net/public')
|
||||
run_rsync(local_paths: ['www/'], publish_dir: BETA_PUBLISH_DIR, dry_run: false, delete: true)
|
||||
end
|
||||
|
||||
# Publish to production server
|
||||
def publish
|
||||
release
|
||||
puts "Deploying to production server..."
|
||||
system('rsync -avz --delete www/ mudge:/var/www/samhuri.net/public')
|
||||
run_rsync(local_paths: ['www/'], publish_dir: PRODUCTION_PUBLISH_DIR, dry_run: false, delete: true)
|
||||
end
|
||||
|
||||
# Clean generated files
|
||||
def clean
|
||||
require 'fileutils'
|
||||
FileUtils.rm_rf('www')
|
||||
puts "Cleaned www/ directory"
|
||||
puts 'Cleaned www/ directory'
|
||||
end
|
||||
|
||||
# Run RSpec tests
|
||||
|
|
@ -62,7 +202,7 @@ end
|
|||
|
||||
# List all available 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)
|
||||
end
|
||||
end
|
||||
|
|
@ -82,11 +222,121 @@ private
|
|||
# Build the site with specified URL
|
||||
# @parameter url [String] The site URL to use
|
||||
def build(url)
|
||||
require_relative 'lib/pressa'
|
||||
|
||||
puts "Building site for #{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/"
|
||||
generate('.', 'www', url)
|
||||
end
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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