Remove number from Episode, use array index instead
Prepares for feeds without itunes:episode numbers (Patreon).
Episodes are now an array ordered by feed position, with the
index used for numbering in slugs and task lookups.

Assisted-by: Claude Opus 4.6 via pi
change lslzuznqllptzwuwvtkuuvnqmlmyxoro
commit 2838f6860150ea02b85db271f76677a1d02dedb5
author Alpha Chen <alpha@kejadlen.dev>
date
parent tsnxuskk
diff --git a/Rakefile b/Rakefile
index a4c4675..789f9a6 100644
--- a/Rakefile
+++ b/Rakefile
@@ -33,32 +33,36 @@ Rake::Task[HRN_FEED.to_s].invoke
 
 EPISODES = CookingIssues::Feed.parse(HRN_FEED)
 
-def audio_path(ep)
-  (AUDIO_DIR / "#{ep.slug}.mp3").to_s
+def episode_slug(index, ep)
+  format("%03d-%s", index + 1, ep.slug)
+end
+
+def audio_path(index, ep)
+  (AUDIO_DIR / "#{episode_slug(index, ep)}.mp3").to_s
 end
 
 TRANSCRIBER_CACHE_DIR = CACHE_DIR / TRANSCRIBER.name
 
 directory TRANSCRIBER_CACHE_DIR.to_s
 
-def transcript_path(ep)
-  (TRANSCRIBER_CACHE_DIR / "#{ep.slug}.json").to_s
+def transcript_path(index, ep)
+  (TRANSCRIBER_CACHE_DIR / "#{episode_slug(index, ep)}.json").to_s
 end
 
-def text_path(ep)
-  (TRANSCRIPTS_DIR / TRANSCRIBER.name / "#{ep.slug}.txt").to_s
+def text_path(index, ep)
+  (TRANSCRIPTS_DIR / TRANSCRIBER.name / "#{episode_slug(index, ep)}.txt").to_s
 end
 
 TEXT_DIR = TRANSCRIPTS_DIR / TRANSCRIBER.name
 
 directory TEXT_DIR.to_s
 
-EPISODES.values.each do |ep|
-  audio = audio_path(ep)
-  transcript = transcript_path(ep)
+EPISODES.each_with_index do |ep, i|
+  audio = audio_path(i, ep)
+  transcript = transcript_path(i, ep)
 
   file audio => AUDIO_DIR.to_s do
-    puts "Downloading #{ep.slug}..."
+    puts "Downloading #{episode_slug(i, ep)}..."
     CookingIssues::Download.fetch(ep.audio_url, audio)
   end
 
@@ -66,7 +70,7 @@ EPISODES.values.each do |ep|
     TRANSCRIBER.call(audio, transcript)
   end
 
-  txt = text_path(ep)
+  txt = text_path(i, ep)
 
   file txt => [transcript, TEXT_DIR.to_s] do
     TRANSCRIBER.render(transcript, txt)
@@ -77,17 +81,16 @@ task default: :sync
 
 desc "Download, transcribe, and render all episodes"
 task :sync do
-  # The feed is reverse-chronological; process oldest episodes first.
-  EPISODES.values.sort_by(&:number).each do |ep|
-    Rake::Task[text_path(ep)].invoke
+  EPISODES.each_with_index do |ep, i|
+    Rake::Task[text_path(i, ep)].invoke
   end
 end
 
 desc "List all episodes from the feed"
 task :episodes do
-  EPISODES.values.sort_by(&:number).each do |ep|
-    status = Pathname(text_path(ep)).exist? ? "✓" : " "
-    puts "[#{status}] #{ep.slug}  #{ep.title}"
+  EPISODES.each_with_index do |ep, i|
+    status = Pathname(text_path(i, ep)).exist? ? "✓" : " "
+    puts "[#{status}] #{episode_slug(i, ep)}  #{ep.title}"
   end
 end
 
@@ -95,17 +98,17 @@ desc "Transcribe an episode by number (e.g., rake transcribe[42])"
 task :transcribe, [:number] do |_t, args|
   abort "Usage: rake transcribe[NUMBER]" unless args[:number]
 
-  ep = EPISODES[args[:number].to_i]
-  abort "Episode #{args[:number]} not found in feed." unless ep
+  i = args[:number].to_i - 1
+  ep = EPISODES.fetch(i) { abort "Episode #{args[:number]} not found in feed." }
 
-  Rake::Task[text_path(ep)].invoke
+  Rake::Task[text_path(i, ep)].invoke
 end
 
 desc "Move intermediate files from transcripts/ to cache/"
 task :migrate_cache do
-  EPISODES.values.each do |ep|
-    old_path = TRANSCRIPTS_DIR / TRANSCRIBER.name / "#{ep.slug}.json"
-    new_path = Pathname(transcript_path(ep))
+  EPISODES.each_with_index do |ep, i|
+    old_path = TRANSCRIPTS_DIR / TRANSCRIBER.name / "#{episode_slug(i, ep)}.json"
+    new_path = Pathname(transcript_path(i, ep))
     next unless old_path.exist?
 
     new_path.dirname.mkpath
@@ -120,14 +123,14 @@ desc "Re-transcribe an episode (e.g., rake retranscribe[42])"
 task :retranscribe, [:number] do |_t, args|
   abort "Usage: rake retranscribe[NUMBER]" unless args[:number]
 
-  ep = EPISODES[args[:number].to_i]
-  abort "Episode #{args[:number]} not found in feed." unless ep
+  i = args[:number].to_i - 1
+  ep = EPISODES.fetch(i) { abort "Episode #{args[:number]} not found in feed." }
 
-  json = Pathname(transcript_path(ep))
-  txt = Pathname(text_path(ep))
+  json = Pathname(transcript_path(i, ep))
+  txt = Pathname(text_path(i, ep))
   json.delete if json.exist?
   txt.delete if txt.exist?
-  Rake::Task[text_path(ep)].reenable
-  Rake::Task[transcript_path(ep)].reenable
-  Rake::Task[text_path(ep)].invoke
+  Rake::Task[text_path(i, ep)].reenable
+  Rake::Task[transcript_path(i, ep)].reenable
+  Rake::Task[text_path(i, ep)].invoke
 end
diff --git a/lib/feed.rb b/lib/feed.rb
index 9b9574d..208880b 100644
--- a/lib/feed.rb
+++ b/lib/feed.rb
@@ -1,10 +1,9 @@
 require "nokogiri"
 
 module CookingIssues
-  Episode = Data.define(:number, :title, :published_at, :audio_url) do
+  Episode = Data.define(:title, :published_at, :audio_url) do
     def self.parse(item)
       new(
-        number: item.at_xpath("itunes:episode").text.to_i,
         title: item.at_xpath("title").text,
         published_at: item.at_xpath("pubDate").text,
         audio_url: item.at_xpath("enclosure")["url"]
@@ -12,8 +11,7 @@ module CookingIssues
     end
 
     def slug
-      safe_title = title.downcase.gsub(/[^a-z0-9]+/, "-").chomp("-")
-      format("%03d-%s", number, safe_title)
+      title.downcase.gsub(/[^a-z0-9]+/, "-").chomp("-")
     end
   end
 
@@ -22,8 +20,7 @@ module CookingIssues
       doc = Nokogiri::XML(File.read(path))
       doc.xpath("//item")
         .map { Episode.parse(it) }
-        .sort_by(&:number)
-        .to_h { [it.number, it] }
+        .reverse
     end
   end
 end
diff --git a/lib/tasks/site.rake b/lib/tasks/site.rake
index 1a8492f..2d845ad 100644
--- a/lib/tasks/site.rake
+++ b/lib/tasks/site.rake
@@ -5,18 +5,20 @@ SITE_DIR = Pathname("site")
 
 desc "Generate an HTML page of transcripts"
 task :site do
-  episodes = EPISODES.values.sort_by(&:number).select { |ep| Pathname(text_path(ep)).exist? }
+  transcripts = []
+  EPISODES.each_with_index do |ep, i|
+    txt = Pathname(text_path(i, ep))
+    next unless txt.exist?
 
-  transcripts = episodes.map do |ep|
-    {
-      number: ep.number,
+    transcripts << {
+      number: i + 1,
       title: ep.title,
-      slug: ep.slug,
-      text: File.read(text_path(ep))
+      slug: episode_slug(i, ep),
+      text: txt.read
     }
   end
 
-  template = File.read(File.expand_path("lib/site.html.erb", __dir__))
+  template = File.read(File.expand_path("../../site.html.erb", __FILE__))
   html = ERB.new(template).result(binding)
 
   SITE_DIR.mkpath