RSSAmplifier

Hearthside by Caleb Hearth · Nov 21, 2024

Oxtail, and more Cooklang Exploration

0
Sign in to vote or save

Andrew Oppenheimer · Hearthside

After yesterday’s post, it was time to finally try to handle a blog post and Cooklang recipe in one file. The solution to how to format things was simple: inline the recipe in a Markdown code fence and pre-process it. The file looks like:

 ---
 [yaml frontmatter]
 ---
 [markdown intro]
 ```cooklang
 [cooklang recipe]
 ```
 [markdown outro]

The code to do it is ugly and relies on a Regular Expression1, CookCLI to generate some JSON that I use to render a partial during the view render. I didn’t bother building a nice data object, so I’m using a mess of {}.dig and case / in / else pattern-matching statements to generate the markup. It does properly handle marking up the recipe with the h-recipe microformat, so that’s a win. See below for Rails partial2.

As you’ll see, the CLI doesn’t handle combining ingredients of the same name to amounts, and it isn’t able to parse ingredient aliases or comments. I’ll probably open an issue on the project’s GitHub repo, but ultimately it looks like the cooklang-rs Rust library is a more robust parser. I thought the CLI used it, but it seems that I’m wrong. Might be time to learn how to build a Ruby gem with Rust native extensions…

Oxtail

Author

Ingredients

  • I'm guessing 2.5 lb oxtails
  • salt
  • pepper
  • canola oil
  • oxtails
  • 5 cups beef stock
  • salt
  • pepper
  • canola oil
  • 2.5 c oyster mushrooms
  • 2.5 c oyster mushrooms
  • 2 Tbsp shallots
  • 1 tsp thyme
  • 2 Tbsp butter
  • 1 lb loaf of bread
  • olive oil
  • 12 c onion slices
  • large, ovenproof sauté pan
  • Brush

Instructions

Preheat the oven to 400°F. Set a cooling rack over a baking sheet.

Generously season both sides of the oxtails with salt and pepper . Pour canola oil into a large, ovenproof sauté pan that will hold the oxtails in one layer, and heat over medium-high heat until hot but not smoking.

Reduce the heat to medium, add about half the oxtails , and cook, adjusting the heat if necessary, until the first side is richly browned, minutes . Flip and brown the second side, about 5 minutes more. Transfer to the cooling rack. Repeat with the remaining oxtails.

Pour off the fat and return the oxtails to the pan. Add enough beef stock to come 3/4 up the oxtails. Bring to a simmer, cover, transfer to the oven, and braise until the oxtails are tender, hours .

Turn the oxtails over and let rest on top of the stove for at least 30 minutes , or up to 1 hour .

Remove the oxtails from the cooking liquid, and strain the cooking liquid through a fine-mesh conical strainer; set aside. Remove the meat from the bones, discarding the fat and tough connective tissue, and put the meat in a bowl. Season to taste with salt and pepper .

The meat can be covered with the strained cooking liquid and refrigerated overnight. The cooking liquid will solidify; when you reheat the oxtails, it may be necessary to add about 1/4 c water.

Heat canola oil in a large sauté pan over medium heat until hot but not smoking. Add half the oyster mushrooms (sliced into 1 1/2in pieces) and cook, without moving them, until golden brown, about 2 minutes . Stir and cook for another 2 minutes , or until browned on all sides. Season with salt and pepper and transfer to a bowl. Cook the remaining oyster mushrooms in the same way.

I reuse the same canola oil from before.

Return all the mushrooms to the sauté pan, add the shallots (minced) and thyme (finely chopped), and cook for minutes , until the shallots soften. Add the butter and cook, stirring, until the liquid the mushrooms release has cooked off and they are glazed with butter, about 2 minutes . Stir in the oxtails, then pour in the reserved cooking liquid, bring to a simmer, and simmer until the liquid has reduced considerably and glazed the meat and mushrooms, about 8 minutes .

Meanwhile, preheat the broiler.

Slice off the top of the loaf of bread (rosemary ciabatta or other thick, flat bread) and reserve for another use. Brush the bread generously with olive oil , and toast under the broiler. Transfer to a serving platter.

Spoon the meat and mushrooms over the bread and scatter the onion slices over the top. Cut into pieces and serve.

recipe.cook

>> name: Oxtail
>> author: Andrew Oppenheimer
>> servings: a good amount
>> description: Andrew made this Oxtail for Hannah and I when we were visiting him in Naperville. I'd never had oxtail, and this was amazing. We've begged him to make it for us again ever since, but at least we finally got the recipe!

Preheat the oven to 400°F. Set a cooling rack over a baking sheet.

Generously season both sides of the @oxtails{I'm guessing 2.5 lb} with @salt and @pepper. Pour @canola oil{some} into a #large, ovenproof sauté pan{} that will hold the oxtails in one layer, and heat over medium-high heat until hot but not smoking.

Reduce the heat to medium, add about half the @oxtails, and cook, adjusting the heat if necessary, until the first side is richly browned, ~saute{5-7%minutes}. Flip and brown the second side, about ~saute{5%minutes} more. Transfer to the cooling rack. Repeat with the remaining oxtails.

Pour off the fat and return the oxtails to the pan. Add enough @beef stock{5%cups} to come 3/4 up the oxtails. Bring to a simmer, cover, transfer to the oven, and braise until the oxtails are tender, ~braise{2.5-3%hours}.

Turn the oxtails over and let rest on top of the stove for at least ~{30%minutes}, or up to ~{1%hour}.

Remove the oxtails from the cooking liquid, and strain the cooking liquid through a fine-mesh conical strainer; set aside. Remove the meat from the bones, discarding the fat and tough connective tissue, and put the meat in a bowl. Season to taste with @salt and @pepper.

> The meat can be covered with the strained cooking liquid and refrigerated overnight. The cooking liquid will solidify; when you reheat the oxtails, it may be necessary to add about 1/4 c water.

Heat @canola oil{some} in a large sauté pan over medium heat until hot but not smoking. Add half the @oyster mushrooms{2.5%c}(sliced into 1 1/2in pieces) and cook, without moving them, until golden brown, about ~{2%minutes}. Stir and cook for another ~{2%minutes}, or until browned on all sides. Season with salt and pepper and transfer to a bowl. Cook the remaining @oyster mushrooms{2.5%c} in the same way.

> I reuse the same canola oil from before.

Return all the mushrooms to the sauté pan, add the @shallots{2%Tbsp}(minced) and @thyme{1%tsp}(finely chopped), and cook for ~{1-2%minutes}, until the shallots soften. Add the @butter{2%Tbsp} and cook, stirring, until the liquid the mushrooms release has cooked off and they are glazed with butter, about ~{2%minutes}. Stir in the oxtails, then pour in the reserved cooking liquid, bring to a simmer, and simmer until the liquid has reduced considerably and glazed the meat and mushrooms, about ~{8%minutes}.

Meanwhile, preheat the broiler.

Slice off the top of the @loaf of bread{1%lb}(rosemary ciabatta or other thick, flat bread) and reserve for another use. #Brush the bread generously with @olive oil{}, and toast under the broiler. Transfer to a serving platter.

Spoon the meat and mushrooms over the bread and scatter the @onion slices{1/2%c} over the top. Cut into pieces and serve.

  1. (/^```cooklang$\n(?<recipe>.+?)^```$/m

  2. app/views/application/_recipe.html.erb
    <article class="h-recipe recipe">
      <h2 class="p-name"><%= recipe.dig(:metadata, :map, :name) %></h2>
      <% recipe[:metadata].delete(:map) %>
      <% if recipe[:metadata].any? %>
        <dl>
      <% recipe[:metadata].each do |key, value| %>
        <% case [key, value] %>
        <% in [Symbol, { name:, url: }] %>
        <dt><%= key.to_s.humanize %></dt>
        <dd class="h-cite p-author">
          <a class="h-card u-url p-name" href="<%= url %>"><%= name %></a>
        </dd>
        <% in [Symbol, { name: }] %>
        <dt><%= key.to_s.humanize %></dt>
        <dd class="h-cite p-author">
          <span class="h-card p-name"><%= name %></span>
        </dd>
        <% in [:description, *] %>
        <% in [Symbol, String] %>
          <p><%= key.to_s.humanize %>: <%= value %></p>
        <% else %>
        <% end %>
      <% end %>
        </dl>
      <% end %>
      <% if recipe[:ingredients].any? %>
        <h3>Ingredients</h3>
      <ul>
        <% recipe[:ingredients].each do |ingredient| %>
          <li class="p-ingredient">
            <span class="p-quantity">
              <% case ingredient[:quantity] %>
              <% in { value: { type: "text", value: } } %>
                <%= value  if value != "some" %>
              <% in { value: { type: "number", value: { type: "regular", value: amount } }, unit: } %>
                <%= number_to_human amount %>
                <%= unit %>
              <% in { value: { type: "number", value: { type: "fraction", value: { whole:, num:, den: } } }, unit: } %>
                <data value="<%= whole + (num.to_f / den.to_f) %>"><%= number_to_human(whole) if whole != 0 %><sup><%= num.to_i %></sup>&frasl;<sub><%= den.to_i %></sub></data>
                <%= unit %>
              <% else %>
                <%= number_to_human ingredient[:quantity] %>
              <% end %>
            </span>
            <span class="p-name"><%= ingredient[:name] %></span>
          </li>
        <% end %>
        <% recipe[:cookware].each do |cookware| %>
          <li><%= cookware[:name] %></li>
        <% end %>
      </ul>
      <% end %>
      <div class="e-instructions">
        <h3>Instructions</h3>
        <% recipe[:sections].each do |section| %>
          <% if section[:name].present? %>
            <h4><%= section[:name] %></h4>
          <% end %>
          <% section[:steps].each do |step| %>
            <p><% step[:items].each do |item| -%>
              <%- case item -%>
              <%- in { type: "text", value: } -%>
                <%= value -%>
              <%- in { type: "ingredient", index: } -%>
              <%= recipe[:ingredients][index][:name] -%>
              <%- in { type: "cookware", index: } -%>
                <%= recipe[:cookware][index][:name] -%>
              <%- in { type: "timer", index: } -%>
                <% case recipe[:timers][index] -%>
                <% in { quantity: { value: { type: "number", value: { type: "regular", value: amount } }, unit: } } -%>
                  <%= number_to_human amount -%>
                  <%= unit -%>
                <% in { quantity: { value: { type: "text", value: }, unit: } } -%>
                  <%= number_to_human amount -%>
                  <%= unit -%>
                <% else %>
                  <%= recipe[:timers][index] -%>
                <% end %>
              <%- else -%>
                <%= item -%>
              <% end -%>
            <% end -%></p>
          <% end %>
        <% end %>
      </div>
    </article>
    <details>
      <summary><code><%= recipe[:name].presence || recipe.dig(:metadata, :map, :name).presence || "recipe" %>.cook</code></summary>
        <%= simple_format cooklang %>
    </details>
    

Read the original on calebhearth.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.