~adamski/arena-web

8af8f7b665375f5b5c5a701aaf3631263a2e6515 — Adam Stück a month ago d466434
switch to wruby site generator
12 files changed, 198 insertions(+), 190 deletions(-)

M .build.yml
A .gitignore
M LICENSE
M Makefile
M README.md
A _config.yml
D barf
M footer.html
M header.html
M index.md
A pages/posts.md
A wruby.rb
M .build.yml => .build.yml +12 -9
@@ 1,20 1,23 @@
image: alpine/edge
image: alpine/latest
oauth: pages.sr.ht/PAGES:RW
packages:
- hut
- rsync
  - ruby
  - ruby-dev
  - go
  - hut
  - rsync
sources:
  - https://git.sr.ht/~adamski/arena-web
  - https://git.btxx.org/smu
environment:
  site: arena.adast.dk
tasks:
- smu: |
    cd smu
    sudo make install
- package: |
- install-gems: |
    sudo gem install bundler 'kramdown:2.4.0' 'rss:0.3.0'
- build: |
    cd arena-web
    make build
    tar -C build -cvz . > ../site.tar.gz
- package: | 
    cd arena-web/build
    tar -cvz . > ../../site.tar.gz
- upload: |
    hut pages publish -d $site site.tar.gz

A .gitignore => .gitignore +1 -0
@@ 0,0 1,1 @@
build/

M LICENSE => LICENSE +2 -2
@@ 1,7 1,7 @@
MIT License

Copyright (c) 2023 Bradley Taunt
Copyright (c) 2023 Adam Stück
Copyright (c) 2024 Bradley Taunt
Copyright (c) 2024 Adam Stück

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

M Makefile => Makefile +3 -8
@@ 1,13 1,8 @@
build:
	sh ./barf
	rsync -r public/ build/public
	rm -rf build && mkdir build
	ruby wruby.rb 

clean:
	rm -rf build/*

watch:
	while true; do \
	ls -d .git/* * posts/* pages/* header.html | entr -cd make ;\
	done

.PHONY: build clean watch
.PHONY: build clean

M README.md => README.md +1 -1
@@ 1,5 1,5 @@
# arena-web

Sources for [arena.adast.dk](https://arena.adast.dk), built with [barf](https://barf.btxx.org).
Sources for [arena.adast.dk](https://arena.adast.dk), built with [wruby](https://wruby.btxx.org).

[[project home]](https://sr.ht/~adamski/arena/)

A _config.yml => _config.yml +21 -0
@@ 0,0 1,21 @@
site_url: 'https://arena.adast.dk'
site_name: 'arena'
author_name: 'Adam Stück'

directories:
  posts: 'posts'
  pages: 'pages'
  public: 'public'
  output: 'build'
  posts_output: 'build/posts'
  pages_output: 'build/'
  
files:
  header: 'header.html'
  footer: 'footer.html'
  root_index: 'index.md'
  posts_index: 'pages/posts.md'
  rss: 'build/index.rss'

misc:
  post_count: 5

D barf => barf +0 -158
@@ 1,158 0,0 @@
#!/bin/sh

domain="https://arena.adast.dk"

# Check the operating system
os_name=$(uname -s)

if [ "$os_name" = "OpenBSD" ]; then
    alias sed=gsed
    alias date=gdate
    alias rsync=openrsync
elif [ "$os_name" = "Darwin" ]; then
    alias sed=gsed
    alias date=gdate
fi

set -eu
MARKDOWN=smu
IFS='	'

# Create tab separated file with filename, title, creation date, last update
index_tsv() {
	for f in "$1"/*.md
	do
		title=$(sed -n '/^# /{s/# //p; q}' "$f")
		printf '%s\t%s\t%s\t%s\n' "$f" "${title:="No Title"}"
	done
}

index_html() {
	# Print header
	title=$(sed -n '/^# /{s/# //p; q}' index.md)
	sed "s/{{TITLE}}/$title/" header.html

	# Intro text
	$MARKDOWN index.md

	echo '<ul class="posts">'

	# Posts
	while read -r f title created; do
		link=$(echo "$f" | sed -E 's|.*/(.*).md|/\1|')
		created=$(echo $(head -3 "$f" | tail -1))
		echo "<li><span>$created</span><a href=\"$link\">$title</a></li>"
	done < "$1" | sort -r

	echo "</ul>"

	# Print footer after post list
	cat footer.html
}

atom_xml() {
	uri=$(sed -rn '/atom.xml/ s/.*href="([^"]*)".*/\1/ p' header.html)
	first_commit_date=$(git log --pretty='format:%ai' . | cut -d ' ' -f1 | tail -1)

	cat <<EOF
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	<title>$(sed -n '/^# /{s/# //p; q}' index.md)</title>
	<link href="$domain/atom.xml" rel="self" />
	<updated>$(date +%FT%TZ)</updated>
	<author>
		<name>$(git config user.name)</name>
	</author>
	<id>$domain,$first_commit_date:default-atom-feed/</id>
EOF

	while read -r f title created; do

		content=$($MARKDOWN "$f" | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/"/\&quot;/g; s/'"'"'/\&#39;/g')
		post_link=$(echo "$f" | sed -E 's|posts/(.*).md|\1|')
		basic_date=$(echo $(head -3 "$f" | tail -1))
		published_date=$(date -d $basic_date -u +%Y-%m-%dT10:%M:%SZ)

		cat <<EOF
	<entry>
		<title>$title</title>
		<content type="html">$content</content>
		<link href="$domain/$post_link"/>
		<id>$domain/$post_link</id>
		<updated>$published_date</updated>
		<published>$published_date</published>
	</entry>
EOF
	done < "$1"

	echo '</feed>'
}

rss_xml() {
	uri=$(sed -rn '/rss.xml/ s/.*href="([^"]*)".*/\1/ p' header.html)
	first_commit_date=$(git log --pretty='format:%ai' . | cut -d ' ' -f1 | tail -1)

	cat <<EOF
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
	<channel>
		<title>$(sed -n '/^# /{s/# //p; q}' index.md)</title>
		<link>$domain/rss.xml</link>
		<description>Updates for Project Arena</description>
		<lastBuildDate>$(date -u +"%a, %d %b %Y %H:%M:%S %z")</lastBuildDate>
		<pubDate>$(date -u +"%a, %d %b %Y %H:%M:%S %z")</pubDate>
		<generator>Custom RSS Generator</generator>
		<ttl>1800</ttl>
EOF

	while read -r f title created; do
		content=$($MARKDOWN "$f" | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/"/\&quot;/g; s/'"'"'/\&#39;/g')
		post_link=$(echo "$f" | sed -E 's|posts/(.*).md|\1|')
		basic_date=$(echo $(head -3 "$f" | tail -1))
		published_date=$(date -d "$basic_date" -u +"%a, %d %b %Y %H:%M:%S %z")

		cat <<EOF
		<item>
			<title>$title</title>
			<description>$content</description>
			<link>$domain/$post_link</link>
			<guid isPermaLink="false">$domain/$post_link</guid>
			<pubDate>$published_date</pubDate>
		</item>
EOF
	done < "$1"

	echo '</channel>'
	echo '</rss>'
}

write_page() {
	filename=$1
	directory=$(echo $(basename "$filename" .md))
	$(mkdir -p build/$directory)
	target=$(echo "$filename" | sed -r 's|\w+/(.*).md|build/\1/index.html|')
	created=$(echo $(head -3 "$filename" | tail -1))
	title=$2

	$MARKDOWN "$filename" | \
		cat header.html - |\
		sed "s|{{TITLE}}|$title|" \
		> "$target" && cat footer.html >> "$target"
}

rm -rf build && mkdir build

# Blog posts
index_tsv posts | sort -rt "	" -k 3 > build/posts.tsv
index_html build/posts.tsv > build/index.html
atom_xml build/posts.tsv > build/atom.xml
rss_xml build/posts.tsv > build/rss.xml
while read -r f title created; do
	write_page "$f" "$title" "$created"
done < build/posts.tsv

# Pages
index_tsv pages > build/pages.tsv
while read -r f title created; do
	write_page "$f" "$title" "$created"
done < build/pages.tsv

M footer.html => footer.html +3 -2
@@ 4,11 4,12 @@
    <ul id="menu">
        <li><a href="/">Home</a></li>
        <li><a href="https://tv.adast.dk/c/arena/videos">Videos</a></li>
        <li><a href="/index.rss">RSS</a></li>
        <li><a href="#top">↑ Top of the page</a></li>
    </ul>
    <small>
        Feeds: <a href="/atom.xml">Atom</a> | <a href="/rss.xml">RSS</a> <br>
        Built with <a href="https://barf.btxx.org">barf</a>. <br>
        Built with <a href="https://wruby.btxx.org">wruby</a>. <br>
        Hosted on <a href="https://sourcehut.org">sourcehut</a>. <br>
        The <a href="https://git.sr.ht/~adamski/arena-web">code for this site</a> is <a href="https://git.sr.ht/~adamski/arena-web/tree/main/item/LICENSE">MIT</a>.
    </small>
</footer>

M header.html => header.html +2 -6
@@ 6,12 6,8 @@
    <meta name="color-scheme" content="dark light">
    <link rel="icon" href="data:,">
    <title>{{TITLE}}</title>
    <link href="/atom.xml" type="application/atom+xml" rel="alternate" title="Atom feed for blog posts" />
    <link href="/rss.xml" type="application/rss+xml" rel="alternate" title="RSS feed for blog posts" />
    <style>*{box-sizing:border-box;}body{font-family:sans-serif;line-height:1.33;margin:0 auto;max-width:650px;padding:1rem;}blockquote{border-left:4px
    solid;padding-left:5px;}img{max-width:100%;}pre{border:1px solid;overflow:auto;padding:5px;}table{text-align:left;width:100%;}
    .posts,#menu{list-style:none;padding:0;}.posts li{margin-bottom:8px;}.posts li span{display:block;font-size:90%;}#menu li{display:inline-block;margin-right:8px;}.footnotes{font-size:90%;}
   a:link{text-decoration:none;}a:visited{text-decoration:none;}a:hover{text-decoration:underline;}a:active{text-decoration:underline;}</style>
    <link href="/index.rss" type="application/rss+xml" rel="alternate" title="RSS feed for blog posts" />
    <style>*{box-sizing:border-box;}body{font-family:sans-serif;line-height:1.33;margin:0 auto;max-width:650px;padding:1rem;}blockquote{border-left:4px solid;padding-left:5px;}img{max-width:100%;}pre{border:1px solid;overflow:auto;padding:5px;}table{text-align:left;width:100%;}.posts,#menu{list-style:none;padding:0;}.posts li{margin-bottom:8px;}.posts li span{display:block;font-size:90%;}#menu li{display:inline-block;margin-right:8px;}.footnotes{font-size:90%;}</style>
</head>

<nav id="top">

M index.md => index.md +2 -4
@@ 13,8 13,6 @@ You can watch more [gameplay clips](https://tv.adast.dk/c/arena/videos) on my Pe

<!-- * [Follow my progress](https://trello.com/b/ycQyrouQ) -->

## Updates 📢
## News 📢

**2023-12-17**<br>
**arena** is currently on hiatus. I am hoping to do a complete open-source
rewrite using the [Godot game engine](https://godotengine.org).
You can keep up-to-date by following the [RSS feed](/index.rss).

A pages/posts.md => pages/posts.md +1 -0
@@ 0,0 1,1 @@
# Posts

A wruby.rb => wruby.rb +150 -0
@@ 0,0 1,150 @@
require 'bundler/inline'
gemfile do
  gem 'kramdown', '2.4.0'
  gem 'rss', '0.3.0'
end

require 'kramdown'
require 'fileutils'
require 'date'
require 'rss'
require 'find'
require 'yaml'

# Load configuration
config = YAML.load_file('_config.yml')

site_url = config['site_url']
site_name = config['site_name']
author_name = config['author_name']

posts_dir = config['directories']['posts']
pages_dir = config['directories']['pages']
public_dir = config['directories']['public']
output_dir = config['directories']['output']
posts_output_dir = config['directories']['posts_output']
pages_output_dir = config['directories']['pages_output']

header_file = config['files']['header']
footer_file = config['files']['footer']
root_index_file = config['files']['root_index']
posts_index_file = config['files']['posts_index']
rss_file = config['files']['rss']

post_count = config['misc']['post_count']

# Make sure output directories exist
[posts_output_dir, pages_output_dir].each { |dir| FileUtils.mkdir_p(dir) }

# Read the footer content
footer_content = File.read(footer_file)

# Replace the title meta tag in the header.html
def replace_title_placeholder(header_content, title)
  header_content.gsub('<title>{{TITLE}}</title>', "<title>#{title}</title>")
end

# Grab the title from each markdown file
def extract_title_from_md(lines)
  first_line = lines.first
  first_line&.start_with?('# ') ? first_line[2..-1].strip : 'Blog Index'
end

# Convert markdown files
def process_markdown_files(input_directory, output_directory, header_content, footer_content)
  items = []

  Find.find(input_directory) do |path|
    next unless path =~ /\.md\z/

    md_content = File.read(path)
    lines = md_content.lines

    title = extract_title_from_md(lines)
    date = Date.parse(lines[2]&.strip || '') rescue Date.today
    html_content = Kramdown::Document.new(md_content).to_html

    relative_path = path.sub(input_directory + '/', '').sub('.md', '')
    item_dir = File.join(output_directory, relative_path)
    output_file = "#{item_dir}/index.html"
    FileUtils.mkdir_p(item_dir)

    header = replace_title_placeholder(header_content, title)
    File.write(output_file, header + html_content + footer_content)

    items << { title: title, date: date, link: relative_path + '/', content: html_content }
  end

  items
end

# Create the root index file
def generate_index(posts, header_content, footer_content, root_index_file, post_count, output_dir, posts_dir)
  root_index_content = File.read(root_index_file)
  root_title = extract_title_from_md(root_index_content.lines)
  root_html = Kramdown::Document.new(root_index_content).to_html

  header = replace_title_placeholder(header_content, root_title)

  index_content = header + root_html + "<ul class=\"posts\">\n"
  posts.first(post_count).each { |post| index_content << "<li><span>#{post[:date]}</span><a href='/#{posts_dir}/#{post[:link]}'>#{post[:title]}</a></li>\n" }
  index_content << "</ul>\n<p><a href='/#{posts_dir}'>View all posts &rarr;</a></p>\n" + footer_content

  File.write("#{output_dir}/index.html", index_content)
end

# Create the full posts list page
def generate_full_posts_list(posts, header_content, footer_content, posts_index_file, output_dir, posts_dir)
  posts_index_content = File.read(posts_index_file)
  posts_title = extract_title_from_md(posts_index_content.lines)
  posts_html = Kramdown::Document.new(posts_index_content).to_html

  header = replace_title_placeholder(header_content, posts_title)

  list_content = header + posts_html + "<ul class=\"posts\">\n"
  posts.each { |post| list_content << "<li><span>#{post[:date]}</span><a href='/#{posts_dir}/#{post[:link]}'>#{post[:title]}</a></li>\n" }
  list_content << "</ul>\n" + footer_content

  File.write("#{output_dir}/posts/index.html", list_content)
end

# Generate the RSS 2.0 feed
def generate_rss(posts, rss_file, author_name, site_name, site_url, posts_dir)
  rss = RSS::Maker.make("2.0") do |maker|
    maker.channel.author = author_name
    maker.channel.updated = Time.now.to_s
    maker.channel.title = "#{site_name} RSS Feed"
    maker.channel.description = "The official RSS Feed for #{site_url}"
    maker.channel.link = site_url

    posts.each do |post|
      date = Date.parse(post[:date].to_s).to_time + 12*60*60 # Force time to midday
      item_link = "#{site_url}/#{posts_dir}/#{post[:link]}"
      item_title = post[:title]
      item_content = post[:content]

      maker.items.new_item do |item|
        item.link = item_link
        item.title = item_title
        item.updated = date.to_s
        item.pubDate = date.rfc822
        item.description = item_content
      end
    end
  end

  File.write(rss_file, rss)
end

# Process header, posts, pages, etc.
header_content = File.read(header_file)

posts = process_markdown_files(posts_dir, posts_output_dir, header_content, footer_content).sort_by { |post| -post[:date].to_time.to_i }
pages = process_markdown_files(pages_dir, pages_output_dir, header_content, footer_content)

generate_index(posts, header_content, footer_content, root_index_file, post_count, output_dir, posts_dir)
generate_full_posts_list(posts, header_content, footer_content, posts_index_file, output_dir, posts_dir)
FileUtils.cp_r(public_dir, output_dir)
generate_rss(posts, rss_file, author_name, site_name, site_url, posts_dir)

puts "Blog built successfully in '#{output_dir}' folder. Have a great day!"