require "kemal"
require "json"
require "markd"
struct Recipe
property tags : Array(String)
property authors : Array({String, String}) # (name, link)
property file_path : String
property html_content : String
def initialize(tags : Array(String), authors : Array({String, String}), file_path : String, html_content : String)
@tags = tags
@authors = authors
@file_path = file_path
@html_content = html_content
end
def self.load_from_folder(folder_path : String)
recipes = [] of Recipe
Dir.glob("#{folder_path}/*/*.md") do |file_path|
content = File.read(file_path)
# Extract front matter for tags and authors
tags = [] of String
authors = [] of {String, String} # Array of {name, link}
body_content = content
if content.starts_with?("---")
front_matter_end = content.index(/---\s*\n/, 4) # find the second "---" line
if front_matter_end
front_matter = content[4...front_matter_end].strip
body_content = content[(front_matter_end + 4)..-1].strip
# Parse tags
if match = front_matter.match(/tags:\s*\[(.*?)\]/)
tags = match[1].split(",").map(&.strip)
end
# Parse authors
if match = front_matter.match(/authors:\s*\[(.*?)\]/)
# Authors list in "name|link" format, e.g., authors: ["Alice|https://github.com/alice", "Bob|https://bob.com"]
authors = match[1].split(",").map do |author|
parts = author.strip.split("|")
{parts[0].strip, parts[1].strip}
end
end
end
end
# Extract title as the first line with a single '#'
title = body_content.lines.find { |line| line.match(/^#\s+/) }?.strip || "Untitled"
title_html = "
#{title.sub(/^#\s+/, "")}
"
# Render the markdown content without the tags
html_content = title_html + Markd::Document.new(body_content).to_html
# Append tags to the end in a readable format
if !tags.empty?
html_content += ""
end
# Append authors to the end in alphabetical order
if !authors.empty?
sorted_authors = authors.sort_by { |author| author[0].downcase }
html_content += ""
html_content += "
Authors:
"
html_content += "
"
html_content += sorted_authors.map { |author| "#{author[0]}" }.join(", ")
html_content += "
"
end
recipes << Recipe.new(tags, authors, file_path, html_content)
end
recipes
end
end
recipes = Recipe.load_from_folder("recipes")
get "/" do
categorized_recipes = recipes.group_by { |r| File.basename(File.dirname(r.file_path)).to_s }
render "src/views/index.ecr", "src/views/layout.ecr"
end
get "/recipes/:category/:recipe" do |env|
category = env.params.url["category"]
recipe_name = env.params.url["recipe"]
recipe = recipes.find do |r|
File.basename(File.dirname(r.file_path)) == category && File.basename(r.file_path, ".md") == recipe_name
end
if recipe
html_content = recipe.html_content
render "src/views/recipe.ecr", "src/views/layout.ecr"
else
env.response.status_code = 404
"Recipe not found"
end
end
get "/search" do |env|
render "src/views/search.ecr", "src/views/layout.ecr"
end
get "/search/results" do |env|
query = env.params.query["tag"] || ""
matched_recipes = recipes.select { |recipe| recipe.tags.includes?(query.downcase) }
render "src/views/search_results.ecr", "src/views/layout.ecr"
end
Kemal.run