Add OutputBuilder DSL for catalog rendering

sep / line / cols / para / badge methods replace manual string
concatenation. badges auto-flush before the next content line.
render { } wraps a block in the builder via instance_eval.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-30 09:55:13 +02:00
parent 390142d892
commit 78184ad62f

75
bbs.rb
View File

@@ -28,6 +28,49 @@ def c(color, text)
"#{C.const_get(color.to_s.upcase)}#{text}#{C::RESET}" "#{C.const_get(color.to_s.upcase)}#{text}#{C::RESET}"
end end
class OutputBuilder
def initialize = (@buf = +''; @badges = [])
def to_s = (flush_badges; @buf)
def sep(color = :gray)
flush_badges
@buf << "\r\n #{c(color, '─' * 66)}\r\n"
end
def line(text, color = nil)
flush_badges
@buf << " #{color ? c(color, text) : text}\r\n"
end
def cols(**pairs)
flush_badges
@buf << " #{pairs.map { |color, text| c(color, text.to_s) }.join(' ')}\r\n"
end
def para(text)
flush_badges
@buf << wrap_text(text) unless text.to_s.strip.empty?
end
def badge(color, text)
@badges << c(color, text)
end
private
def c(color, text) = "#{C.const_get(color.to_s.upcase)}#{text}#{C::RESET}"
def flush_badges
return if @badges.empty?
@buf << " #{@badges.join(' ')}\r\n"
@badges = []
end
end
def render(&block)
OutputBuilder.new.tap { |b| b.instance_eval(&block) }.to_s
end
def fmt_date(iso) def fmt_date(iso)
Time.parse(iso.to_s).strftime('%Y-%m-%d') Time.parse(iso.to_s).strftime('%Y-%m-%d')
rescue rescue
@@ -130,27 +173,23 @@ BBS.configure do |c|
end end
output do |ctx| output do |ctx|
games = Array(ctx[:games]) games = Array(ctx[:games])
next " #{c(:red, 'Could not load catalog.')}\r\n\r\n" unless games.any? next " #{c(:red, 'Could not load catalog.')}\r\n" unless games.any?
out = +'' render do
games.each_with_index do |game, i| games.each_with_index do |game, i|
out << "\r\n #{c(:gray, '─' * 66)}\r\n" sep
out << " #{c(:cyan, "#{i + 1}. #{game.title}")} #{c(:gray, "#{game.platform} #{game.author}")}\r\n" cols cyan: "#{i + 1}. #{game.title}", gray: "#{game.platform} #{game.author}"
out << wrap_text(game.desc) unless game.desc.empty? para game.desc
badge :green, '[▶ Play]' unless game.play_path.empty?
badges = [] badge :yellow, '[⬇ Download]' unless game.download_path.empty?
badges << c(:green, '[▶ Play]') unless game.play_path.empty? badge :blue, '[Source]' unless game.source_path.empty?
badges << c(:yellow, '[⬇ Download]') unless game.download_path.empty? badge :magenta, '[Docs]' unless game.docs_path.empty?
badges << c(:blue, '[Source]') unless game.source_path.empty? line CATALOG.play_url(game.play_path), :gray unless game.play_path.empty?
badges << c(:magenta, '[Docs]') unless game.docs_path.empty? line "#{game.release_count} versions available", :gray if game.release_count > 1
out << " #{badges.join(' ')}\r\n" unless badges.empty? end
out << " #{c(:gray, CATALOG.play_url(game.play_path))}\r\n" unless game.play_path.empty? sep
out << " #{c(:gray, "#{game.release_count} versions available")}\r\n" if game.release_count > 1 line CatalogRepository::GAMES_URL, :gray
end end
out << "\r\n #{c(:gray, '─' * 66)}\r\n"
out << " #{c(:gray, CatalogRepository::GAMES_URL)}\r\n\r\n"
out
end end
wait_enter wait_enter
end end